diff options
author | Brad King <brad.king@kitware.com> | 2024-01-31 15:02:45 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2024-01-31 15:02:45 (GMT) |
commit | e28ea90c1be2e7d3963effa3f7d80f2c8bf3d1d4 (patch) | |
tree | 61d94e6d1e595c9ab06bac9e70a153162f523ab8 | |
parent | 692463d271c5afae5c48b8f05117a9a2e035d6be (diff) | |
parent | 851cc904a0f3615c9560a2eeba83e04556c0090b (diff) | |
download | CMake-e28ea90c1be2e7d3963effa3f7d80f2c8bf3d1d4.zip CMake-e28ea90c1be2e7d3963effa3f7d80f2c8bf3d1d4.tar.gz CMake-e28ea90c1be2e7d3963effa3f7d80f2c8bf3d1d4.tar.bz2 |
Merge branch 'upstream-curl' into update-curl
* upstream-curl:
curl 2024-01-31 (5ce164e0)
137 files changed, 7357 insertions, 4573 deletions
diff --git a/Utilities/cmcurl/CMake/CurlTests.c b/Utilities/cmcurl/CMake/CurlTests.c index e546286..83d743d 100644 --- a/Utilities/cmcurl/CMake/CurlTests.c +++ b/Utilities/cmcurl/CMake/CurlTests.c @@ -164,14 +164,11 @@ int main(void) { ; return 0; } #ifdef HAVE_IOCTLSOCKET /* includes start */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif -# ifdef HAVE_WINSOCK2_H -# include <winsock2.h> -# endif -# include <windows.h> +# include <winsock2.h> #endif int main(void) { @@ -186,14 +183,11 @@ int main(void) #ifdef HAVE_IOCTLSOCKET_CAMEL /* includes start */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif -# ifdef HAVE_WINSOCK2_H -# include <winsock2.h> -# endif -# include <windows.h> +# include <winsock2.h> #endif int main(void) { @@ -207,14 +201,11 @@ int main(void) #ifdef HAVE_IOCTLSOCKET_CAMEL_FIONBIO /* includes start */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif -# ifdef HAVE_WINSOCK2_H -# include <winsock2.h> -# endif -# include <windows.h> +# include <winsock2.h> #endif int main(void) { @@ -229,14 +220,11 @@ int main(void) #ifdef HAVE_IOCTLSOCKET_FIONBIO /* includes start */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif -# ifdef HAVE_WINSOCK2_H -# include <winsock2.h> -# endif -# include <windows.h> +# include <winsock2.h> #endif int main(void) { @@ -307,14 +295,11 @@ int main(void) #ifdef HAVE_SETSOCKOPT_SO_NONBLOCK /* includes start */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif -# ifdef HAVE_WINSOCK2_H -# include <winsock2.h> -# endif -# include <windows.h> +# include <winsock2.h> #endif /* includes start */ #ifdef HAVE_SYS_TYPES_H diff --git a/Utilities/cmcurl/CMake/Macros.cmake b/Utilities/cmcurl/CMake/Macros.cmake index 7ad2f5c..9ff62ea 100644 --- a/Utilities/cmcurl/CMake/Macros.cmake +++ b/Utilities/cmcurl/CMake/Macros.cmake @@ -45,7 +45,7 @@ macro(curl_internal_test CURL_TEST) "-DLINK_LIBRARIES:STRING=${CMAKE_REQUIRED_LIBRARIES}") endif() - message(STATUS "Performing Curl Test ${CURL_TEST}") + message(STATUS "Performing Test ${CURL_TEST}") try_compile(${CURL_TEST} ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c @@ -54,15 +54,15 @@ macro(curl_internal_test CURL_TEST) OUTPUT_VARIABLE OUTPUT) if(${CURL_TEST}) set(${CURL_TEST} 1 CACHE INTERNAL "Curl test ${FUNCTION}") - message(STATUS "Performing Curl Test ${CURL_TEST} - Success") + message(STATUS "Performing Test ${CURL_TEST} - Success") file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log - "Performing Curl Test ${CURL_TEST} passed with the following output:\n" + "Performing Test ${CURL_TEST} passed with the following output:\n" "${OUTPUT}\n") else() - message(STATUS "Performing Curl Test ${CURL_TEST} - Failed") + message(STATUS "Performing Test ${CURL_TEST} - Failed") set(${CURL_TEST} "" CACHE INTERNAL "Curl test ${FUNCTION}") file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log - "Performing Curl Test ${CURL_TEST} failed with the following output:\n" + "Performing Test ${CURL_TEST} failed with the following output:\n" "${OUTPUT}\n") endif() endif() diff --git a/Utilities/cmcurl/CMake/OtherTests.cmake b/Utilities/cmcurl/CMake/OtherTests.cmake index a613f6e..7701c0e 100644 --- a/Utilities/cmcurl/CMake/OtherTests.cmake +++ b/Utilities/cmcurl/CMake/OtherTests.cmake @@ -23,115 +23,89 @@ ########################################################################### include(CheckCSourceCompiles) include(CheckCSourceRuns) - -# The begin of the sources (macros and includes) -set(_source_epilogue "#undef inline") +include(CheckTypeSize) macro(add_header_include check header) if(${check}) - set(_source_epilogue "${_source_epilogue}\n#include <${header}>") + set(_source_epilogue "${_source_epilogue} + #include <${header}>") endif() endmacro() -set(signature_call_conv) -if(HAVE_WINDOWS_H) - set(_source_epilogue - "${_source_epilogue}\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif") - add_header_include(HAVE_WINSOCK2_H "winsock2.h") - add_header_include(HAVE_WINDOWS_H "windows.h") - set(signature_call_conv "PASCAL") +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +if(NOT DEFINED HAVE_STRUCT_SOCKADDR_STORAGE) + set(CMAKE_EXTRA_INCLUDE_FILES) if(WIN32) + set(CMAKE_EXTRA_INCLUDE_FILES "winsock2.h") + set(CMAKE_REQUIRED_DEFINITIONS "-DWIN32_LEAN_AND_MEAN") set(CMAKE_REQUIRED_LIBRARIES "ws2_32") + elseif(HAVE_SYS_SOCKET_H) + set(CMAKE_EXTRA_INCLUDE_FILES "sys/socket.h") endif() -else() + check_type_size("struct sockaddr_storage" SIZEOF_STRUCT_SOCKADDR_STORAGE) + set(HAVE_STRUCT_SOCKADDR_STORAGE ${HAVE_SIZEOF_STRUCT_SOCKADDR_STORAGE}) +endif() + +if(NOT WIN32) + set(_source_epilogue "#undef inline") add_header_include(HAVE_SYS_TYPES_H "sys/types.h") add_header_include(HAVE_SYS_SOCKET_H "sys/socket.h") + check_c_source_compiles("${_source_epilogue} + int main(void) + { + int flag = MSG_NOSIGNAL; + (void)flag; + return 0; + }" HAVE_MSG_NOSIGNAL) endif() -set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) - +set(_source_epilogue "#undef inline") +add_header_include(HAVE_SYS_TIME_H "sys/time.h") check_c_source_compiles("${_source_epilogue} - int main(void) { - int flag = MSG_NOSIGNAL; - (void)flag; + #include <time.h> + int main(void) + { + struct timeval ts; + ts.tv_sec = 0; + ts.tv_usec = 0; + (void)ts; return 0; - }" HAVE_MSG_NOSIGNAL) - -if(NOT HAVE_WINDOWS_H) - add_header_include(HAVE_SYS_TIME_H "sys/time.h") -endif() -check_c_source_compiles("${_source_epilogue} -#include <time.h> -int main(void) { - struct timeval ts; - ts.tv_sec = 0; - ts.tv_usec = 0; - (void)ts; - return 0; -}" HAVE_STRUCT_TIMEVAL) - -if(HAVE_WINDOWS_H) - set(CMAKE_EXTRA_INCLUDE_FILES "winsock2.h") -else() - set(CMAKE_EXTRA_INCLUDE_FILES) - if(HAVE_SYS_SOCKET_H) - set(CMAKE_EXTRA_INCLUDE_FILES "sys/socket.h") - endif() -endif() - -check_type_size("struct sockaddr_storage" SIZEOF_STRUCT_SOCKADDR_STORAGE) -if(HAVE_SIZEOF_STRUCT_SOCKADDR_STORAGE) - set(HAVE_STRUCT_SOCKADDR_STORAGE 1) -endif() + }" HAVE_STRUCT_TIMEVAL) unset(CMAKE_TRY_COMPILE_TARGET_TYPE) -if(NOT CMAKE_CROSSCOMPILING) - if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "iOS") - # only try this on non-apple platforms - - # if not cross-compilation... - set(CMAKE_REQUIRED_FLAGS "") - if(HAVE_SYS_POLL_H) - set(CMAKE_REQUIRED_FLAGS "-DHAVE_SYS_POLL_H") - elseif(HAVE_POLL_H) - set(CMAKE_REQUIRED_FLAGS "-DHAVE_POLL_H") - endif() - check_c_source_runs(" - #include <stdlib.h> - #include <sys/time.h> - - #ifdef HAVE_SYS_POLL_H - # include <sys/poll.h> - #elif HAVE_POLL_H - # include <poll.h> - #endif - - int main(void) - { - if(0 != poll(0, 0, 10)) { - return 1; /* fail */ - } - else { - /* detect the 10.12 poll() breakage */ - struct timeval before, after; - int rc; - size_t us; - - gettimeofday(&before, NULL); - rc = poll(NULL, 0, 500); - gettimeofday(&after, NULL); - - us = (after.tv_sec - before.tv_sec) * 1000000 + - (after.tv_usec - before.tv_usec); - - if(us < 400000) { - return 1; - } - } - return 0; +if(NOT CMAKE_CROSSCOMPILING AND NOT APPLE) + set(_source_epilogue "#undef inline") + add_header_include(HAVE_SYS_POLL_H "sys/poll.h") + add_header_include(HAVE_POLL_H "poll.h") + check_c_source_runs("${_source_epilogue} + #include <stdlib.h> + #include <sys/time.h> + int main(void) + { + if(0 != poll(0, 0, 10)) { + return 1; /* fail */ + } + else { + /* detect the 10.12 poll() breakage */ + struct timeval before, after; + int rc; + size_t us; + + gettimeofday(&before, NULL); + rc = poll(NULL, 0, 500); + gettimeofday(&after, NULL); + + us = (after.tv_sec - before.tv_sec) * 1000000 + + (after.tv_usec - before.tv_usec); + + if(us < 400000) { + return 1; + } + } + return 0; }" HAVE_POLL_FINE) - endif() endif() # Detect HAVE_GETADDRINFO_THREADSAFE @@ -140,8 +114,8 @@ if(WIN32) set(HAVE_GETADDRINFO_THREADSAFE ${HAVE_GETADDRINFO}) elseif(NOT HAVE_GETADDRINFO) set(HAVE_GETADDRINFO_THREADSAFE FALSE) -elseif(CMAKE_SYSTEM_NAME STREQUAL "AIX" OR - CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR +elseif(APPLE OR + CMAKE_SYSTEM_NAME STREQUAL "AIX" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "HP-UX" OR CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR @@ -153,14 +127,10 @@ elseif(CMAKE_SYSTEM_NAME MATCHES "BSD") endif() if(NOT DEFINED HAVE_GETADDRINFO_THREADSAFE) - - set(_save_epilogue "${_source_epilogue}") set(_source_epilogue "#undef inline") - add_header_include(HAVE_SYS_SOCKET_H "sys/socket.h") add_header_include(HAVE_SYS_TIME_H "sys/time.h") add_header_include(HAVE_NETDB_H "netdb.h") - check_c_source_compiles("${_source_epilogue} int main(void) { @@ -197,17 +167,12 @@ if(NOT DEFINED HAVE_GETADDRINFO_THREADSAFE) if(HAVE_H_ERRNO OR HAVE_H_ERRNO_ASSIGNABLE OR HAVE_H_ERRNO_SBS_ISSUE_7) set(HAVE_GETADDRINFO_THREADSAFE TRUE) endif() - - set(_source_epilogue "${_save_epilogue}") endif() if(NOT WIN32 AND NOT DEFINED HAVE_CLOCK_GETTIME_MONOTONIC_RAW) - set(_save_epilogue "${_source_epilogue}") set(_source_epilogue "#undef inline") - add_header_include(HAVE_SYS_TYPES_H "sys/types.h") add_header_include(HAVE_SYS_TIME_H "sys/time.h") - check_c_source_compiles("${_source_epilogue} #include <time.h> int main(void) @@ -216,6 +181,4 @@ if(NOT WIN32 AND NOT DEFINED HAVE_CLOCK_GETTIME_MONOTONIC_RAW) (void)clock_gettime(CLOCK_MONOTONIC_RAW, &ts); return 0; }" HAVE_CLOCK_GETTIME_MONOTONIC_RAW) - - set(_source_epilogue "${_save_epilogue}") endif() diff --git a/Utilities/cmcurl/CMake/PickyWarnings.cmake b/Utilities/cmcurl/CMake/PickyWarnings.cmake index 5a0d156..d82bbb1 100644 --- a/Utilities/cmcurl/CMake/PickyWarnings.cmake +++ b/Utilities/cmcurl/CMake/PickyWarnings.cmake @@ -23,6 +23,12 @@ ########################################################################### include(CheckCCompilerFlag) +unset(WPICKY) + +if(CURL_WERROR AND CMAKE_COMPILER_IS_GNUCC AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0) + set(WPICKY "${WPICKY} -pedantic-errors") +endif() + if(PICKY_COMPILER) if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang") @@ -83,11 +89,12 @@ if(PICKY_COMPILER) -Wmissing-field-initializers # clang 2.7 gcc 4.1 -Wmissing-noreturn # clang 2.7 gcc 4.1 -Wno-format-nonliteral # clang 1.0 gcc 2.96 (3.0) - -Wno-sign-conversion # clang 2.9 gcc 4.3 -Wno-system-headers # clang 1.0 gcc 3.0 # -Wpadded # clang 2.9 gcc 4.1 # Not used because we cannot change public structs - -Wredundant-decls # clang 2.7 gcc 4.1 -Wold-style-definition # clang 2.7 gcc 3.4 + -Wredundant-decls # clang 2.7 gcc 4.1 + -Wsign-conversion # clang 2.9 gcc 4.3 + -Wno-error=sign-conversion # FIXME -Wstrict-prototypes # clang 1.0 gcc 3.3 # -Wswitch-enum # clang 2.7 gcc 4.1 # Not used because this basically disallows default case -Wtype-limits # clang 2.7 gcc 4.3 @@ -110,6 +117,7 @@ if(PICKY_COMPILER) -Wshift-sign-overflow # clang 2.9 -Wshorten-64-to-32 # clang 1.0 -Wlanguage-extension-token # clang 3.0 + -Wformat=2 # clang 3.0 gcc 4.8 ) # Enable based on compiler version if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.6) OR @@ -135,6 +143,12 @@ if(PICKY_COMPILER) -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)) + list(APPEND WPICKY_ENABLE + -Wimplicit-fallthrough # clang 4.0 gcc 7.0 appleclang 12.4 # we have silencing markup for clang 10.0 and above only + ) + endif() else() # gcc list(APPEND WPICKY_DETECT ${WPICKY_COMMON} @@ -147,6 +161,7 @@ if(PICKY_COMPILER) -Wmissing-parameter-type # gcc 4.3 -Wold-style-declaration # gcc 4.3 -Wstrict-aliasing=3 # gcc 4.0 + -Wtrampolines # gcc 4.3 ) endif() if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.5 AND MINGW) @@ -156,7 +171,7 @@ if(PICKY_COMPILER) endif() if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8) list(APPEND WPICKY_ENABLE - -Wformat=2 # clang 3.0 gcc 4.8 (clang part-default, enabling it fully causes -Wformat-nonliteral warnings) + -Wformat=2 # clang 3.0 gcc 4.8 ) endif() if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0) @@ -179,6 +194,7 @@ if(PICKY_COMPILER) -Wduplicated-branches # gcc 7.0 -Wformat-overflow=2 # gcc 7.0 -Wformat-truncation=2 # gcc 7.0 + -Wimplicit-fallthrough # clang 4.0 gcc 7.0 -Wrestrict # gcc 7.0 ) endif() @@ -191,8 +207,6 @@ if(PICKY_COMPILER) # - unset(WPICKY) - foreach(_CCOPT IN LISTS WPICKY_ENABLE) set(WPICKY "${WPICKY} ${_CCOPT}") endforeach() @@ -209,8 +223,10 @@ if(PICKY_COMPILER) set(WPICKY "${WPICKY} ${_CCOPT}") endif() endforeach() - - message(STATUS "Picky compiler options:${WPICKY}") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WPICKY}") endif() endif() + +if(WPICKY) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WPICKY}") + message(STATUS "Picky compiler options:${WPICKY}") +endif() diff --git a/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake b/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake index ec09fd4..d3391d9 100644 --- a/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake +++ b/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake @@ -171,6 +171,7 @@ set(HAVE_POSIX_STRERROR_R 0) set(HAVE_BUILTIN_AVAILABLE 0) set(HAVE_MSG_NOSIGNAL 0) set(HAVE_STRUCT_TIMEVAL 1) +set(HAVE_STRUCT_SOCKADDR_STORAGE 1) set(HAVE_GETHOSTBYNAME_R_3 0) set(HAVE_GETHOSTBYNAME_R_3_REENTRANT 0) diff --git a/Utilities/cmcurl/CMakeLists.txt b/Utilities/cmcurl/CMakeLists.txt index e6c4f1b..3dd24f1 100644 --- a/Utilities/cmcurl/CMakeLists.txt +++ b/Utilities/cmcurl/CMakeLists.txt @@ -179,25 +179,8 @@ endif() # SPDX-License-Identifier: curl # ########################################################################### -# curl/libcurl CMake script # by Tetetest and Sukender (Benoit Neil) -# TODO: -# The output .so file lacks the soname number which we currently have within the lib/Makefile.am file -# Add full (4 or 5 libs) SSL support -# Add INSTALL target (EXTRA_DIST variables in Makefile.am may be moved to Makefile.inc so that CMake/CPack is aware of what's to include). -# Check on all possible platforms -# Test with as many configurations possible (With or without any option) -# Create scripts that help keeping the CMake build system up to date (to reduce maintenance). According to Tetetest: -# - lists of headers that 'configure' checks for; -# - curl-specific tests (the ones that are in m4/curl-*.m4 files); -# - (most obvious thing:) curl version numbers. -# Add documentation subproject -# -# To check: -# (From Daniel Stenberg) The cmake build selected to run gcc with -fPIC on my box while the plain configure script did not. -# (From Daniel Stenberg) The gcc command line use neither -g nor any -O options. As a developer, I also treasure our configure scripts's --enable-debug option that sets a long range of "picky" compiler options. - # 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 @@ -262,7 +245,7 @@ option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(BUILD_STATIC_LIBS "Build static libraries" OFF) option(BUILD_STATIC_CURL "Build curl executable with static libcurl" OFF) option(ENABLE_ARES "Set to ON to enable c-ares support" OFF) -option(CURL_DISABLE_INSTALL "Set to ON to disable instalation targets" OFF) +option(CURL_DISABLE_INSTALL "Set to ON to disable installation targets" OFF) if(WIN32) option(CURL_STATIC_CRT "Set to ON to build libcurl with static CRT on Windows (/MT)." OFF) @@ -489,18 +472,22 @@ if(ENABLE_IPV6 AND NOT WIN32) endif() if(0) # This code not needed for building within CMake. -if(USE_MANUAL) - #nroff is currently only used when USE_MANUAL is set, so we can prevent the warning of no *NROFF if USE_MANUAL is OFF (or not defined), by not even looking for NROFF.. - curl_nroff_check() -endif() find_package(Perl) -cmake_dependent_option(ENABLE_MANUAL "to provide the built-in manual" - ON "NROFF_USEFUL;PERL_FOUND" - OFF) +option(BUILD_LIBCURL_DOCS "to build libcurl man pages" ON) +# curl source release tarballs come with the curl man page pre-built. +option(ENABLE_CURL_MANUAL "to build the man page for curl and enable its -M/--manual option" OFF) -if(ENABLE_MANUAL) - set(USE_MANUAL ON) +if(ENABLE_CURL_MANUAL OR BUILD_LIBCURL_DOCS) + if(PERL_FOUND) + curl_nroff_check() + if(NROFF_USEFUL) + set(HAVE_MANUAL_TOOLS ON) + endif() + endif() + if(NOT HAVE_MANUAL_TOOLS) + message(WARNING "Perl not found, or nroff not useful. Will not build manuals.") + endif() endif() endif() @@ -537,9 +524,6 @@ include(CheckCSourceCompiles) # On windows preload settings if(WIN32) - set(HAVE_WINDOWS_H 1) - set(HAVE_WS2TCPIP_H 1) - set(HAVE_WINSOCK2_H 1) include(${CMAKE_CURRENT_SOURCE_DIR}/CMake/Platforms/WindowsCache.cmake) endif() @@ -917,6 +901,10 @@ if(USE_MSH3) list(APPEND CURL_LIBS ${MSH3_LIBRARIES}) endif() +if(CURL_WITH_MULTI_SSL AND (USE_NGTCP2 OR USE_QUICHE OR USE_MSH3)) + message(FATAL_ERROR "MultiSSL cannot be enabled with HTTP/3 and vice versa.") +endif() + if(NOT CURL_DISABLE_SRP AND (HAVE_GNUTLS_SRP OR HAVE_OPENSSL_SRP)) set(USE_TLS_SRP 1) endif() @@ -968,7 +956,7 @@ if(NOT CURL_DISABLE_LDAP) endif() set(NEED_LBER_H ON) set(_HEADER_LIST) - if(HAVE_WINDOWS_H) + if(WIN32) list(APPEND _HEADER_LIST "windows.h") endif() if(HAVE_SYS_TYPES_H) @@ -1108,10 +1096,8 @@ if(CURL_USE_GSSAPI) check_include_file_concat("gssapi/gssapi_generic.h" HAVE_GSSAPI_GSSAPI_GENERIC_H) check_include_file_concat("gssapi/gssapi_krb5.h" HAVE_GSSAPI_GSSAPI_KRB5_H) - if(GSS_FLAVOUR STREQUAL "Heimdal") - set(HAVE_GSSHEIMDAL ON) - else() # MIT - set(HAVE_GSSMIT ON) + if(NOT GSS_FLAVOUR STREQUAL "Heimdal") + # MIT set(_INCLUDE_LIST "") if(HAVE_GSSAPI_GSSAPI_H) list(APPEND _INCLUDE_LIST "gssapi/gssapi.h") @@ -1253,13 +1239,9 @@ endif() # Check for header files if(WIN32) - check_include_file_concat("winsock2.h" HAVE_WINSOCK2_H) - check_include_file_concat("ws2tcpip.h" HAVE_WS2TCPIP_H) - check_include_file_concat("windows.h" HAVE_WINDOWS_H) -else() - set(HAVE_WINSOCK2_H 0) - set(HAVE_WS2TCPIP_H 0) - set(HAVE_WINDOWS_H 0) + set(CURL_INCLUDES ${CURL_INCLUDES} "winsock2.h") + set(CURL_INCLUDES ${CURL_INCLUDES} "ws2tcpip.h") + set(CURL_INCLUDES ${CURL_INCLUDES} "windows.h") endif() if(WIN32) @@ -1439,7 +1421,7 @@ set(HAVE_SA_FAMILY_T ${HAVE_SIZEOF_SA_FAMILY_T}) set(CMAKE_EXTRA_INCLUDE_FILES "") if(WIN32) - set(CMAKE_EXTRA_INCLUDE_FILES "ws2def.h") + set(CMAKE_EXTRA_INCLUDE_FILES "winsock2.h") check_type_size("ADDRESS_FAMILY" SIZEOF_ADDRESS_FAMILY) set(HAVE_ADDRESS_FAMILY ${HAVE_SIZEOF_ADDRESS_FAMILY}) set(CMAKE_EXTRA_INCLUDE_FILES "") @@ -1581,15 +1563,6 @@ if(CMAKE_COMPILER_IS_GNUCC AND APPLE) endif() endif() -# TODO test which of these headers are required -if(WIN32) - set(CURL_PULL_WS2TCPIP_H ${HAVE_WS2TCPIP_H}) -else() - set(CURL_PULL_SYS_TYPES_H ${HAVE_SYS_TYPES_H}) - set(CURL_PULL_SYS_SOCKET_H ${HAVE_SYS_SOCKET_H}) - set(CURL_PULL_SYS_POLL_H ${HAVE_SYS_POLL_H}) -endif() - include(CMake/OtherTests.cmake) add_definitions(-DHAVE_CONFIG_H) @@ -1697,7 +1670,7 @@ set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake") set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") -if(USE_MANUAL) +if(HAVE_MANUAL_TOOLS) add_subdirectory(docs) endif() @@ -1726,7 +1699,6 @@ if(NOT CURL_DISABLE_INSTALL) endmacro() # NTLM support requires crypto function adaptions from various SSL libs - # TODO alternative SSL libs tests for SSP1, GnuTLS, NSS if(NOT (CURL_DISABLE_NTLM) AND (USE_OPENSSL OR USE_MBEDTLS OR USE_DARWINSSL OR USE_WIN32_CRYPTO OR USE_GNUTLS)) set(use_curl_ntlm_core ON) @@ -1744,26 +1716,20 @@ if(NOT CURL_DISABLE_INSTALL) _add_if("IDN" HAVE_LIBIDN2 OR USE_WIN32_IDN) _add_if("Largefile" (SIZEOF_CURL_OFF_T GREATER 4) AND ((SIZEOF_OFF_T GREATER 4) OR USE_WIN32_LARGE_FILES)) - # TODO SSP1 (Schannel) check is missing _add_if("SSPI" USE_WINDOWS_SSPI) _add_if("GSS-API" HAVE_GSSAPI) _add_if("alt-svc" NOT CURL_DISABLE_ALTSVC) _add_if("HSTS" NOT CURL_DISABLE_HSTS) - # TODO SSP1 missing for SPNEGO _add_if("SPNEGO" NOT CURL_DISABLE_NEGOTIATE_AUTH AND (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) _add_if("Kerberos" NOT CURL_DISABLE_KERBEROS_AUTH AND (HAVE_GSSAPI OR USE_WINDOWS_SSPI)) - # NTLM support requires crypto function adaptions from various SSL libs - # TODO alternative SSL libs tests for SSP1, GnuTLS, NSS _add_if("NTLM" NOT (CURL_DISABLE_NTLM) AND (use_curl_ntlm_core OR USE_WINDOWS_SSPI)) - # TODO missing option (autoconf: --enable-ntlm-wb) _add_if("NTLM_WB" NOT (CURL_DISABLE_NTLM) AND (use_curl_ntlm_core OR USE_WINDOWS_SSPI) AND NOT CURL_DISABLE_HTTP AND NTLM_WB_ENABLED) _add_if("TLS-SRP" USE_TLS_SRP) - # TODO option --with-nghttp2 tests for nghttp2 lib and nghttp2/nghttp2.h header _add_if("HTTP2" USE_NGHTTP2) _add_if("HTTP3" USE_NGTCP2 OR USE_QUICHE) _add_if("MultiSSL" CURL_WITH_MULTI_SSL) @@ -1782,6 +1748,8 @@ if(NOT CURL_DISABLE_INSTALL) # Clear list and try to detect available protocols set(_items) _add_if("HTTP" NOT CURL_DISABLE_HTTP) + _add_if("IPFS" NOT CURL_DISABLE_HTTP) + _add_if("IPNS" NOT CURL_DISABLE_HTTP) _add_if("HTTPS" NOT CURL_DISABLE_HTTP AND SSL_ENABLED) _add_if("FTP" NOT CURL_DISABLE_FTP) _add_if("FTPS" NOT CURL_DISABLE_FTP AND SSL_ENABLED) diff --git a/Utilities/cmcurl/COPYING b/Utilities/cmcurl/COPYING index d1eab3e..d9e7e0b 100644 --- a/Utilities/cmcurl/COPYING +++ b/Utilities/cmcurl/COPYING @@ -1,6 +1,6 @@ COPYRIGHT AND PERMISSION NOTICE -Copyright (c) 1996 - 2023, Daniel Stenberg, <daniel@haxx.se>, and many +Copyright (c) 1996 - 2024, Daniel Stenberg, <daniel@haxx.se>, and many contributors, see the THANKS file. All rights reserved. diff --git a/Utilities/cmcurl/include/curl/curl.h b/Utilities/cmcurl/include/curl/curl.h index 58de4b5..5b9bcc6 100644 --- a/Utilities/cmcurl/include/curl/curl.h +++ b/Utilities/cmcurl/include/curl/curl.h @@ -631,6 +631,7 @@ typedef enum { CURLE_PROXY, /* 97 - proxy handshake error */ CURLE_SSL_CLIENTCERT, /* 98 - client-side certificate required */ CURLE_UNRECOVERABLE_POLL, /* 99 - poll/select returned fatal error */ + CURLE_TOO_LARGE, /* 100 - a value/data met its maximum */ CURL_LAST /* never use! */ } CURLcode; @@ -1845,7 +1846,8 @@ typedef enum { /* allow GSSAPI credential delegation */ CURLOPT(CURLOPT_GSSAPI_DELEGATION, CURLOPTTYPE_VALUES, 210), - /* Set the name servers to use for DNS resolution */ + /* Set the name servers to use for DNS resolution. + * Only supported by the c-ares DNS backend */ CURLOPT(CURLOPT_DNS_SERVERS, CURLOPTTYPE_STRINGPOINT, 211), /* Time-out accept operations (currently for FTP only) after this amount @@ -2201,6 +2203,9 @@ typedef enum { /* set a specific client IP for HAProxy PROXY protocol header? */ CURLOPT(CURLOPT_HAPROXY_CLIENT_IP, CURLOPTTYPE_STRINGPOINT, 323), + /* millisecond version */ + CURLOPT(CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, CURLOPTTYPE_LONG, 324), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; @@ -2932,7 +2937,8 @@ typedef enum { CURLINFO_CAPATH = CURLINFO_STRING + 62, CURLINFO_XFER_ID = CURLINFO_OFF_T + 63, CURLINFO_CONN_ID = CURLINFO_OFF_T + 64, - CURLINFO_LASTONE = 64 + CURLINFO_QUEUE_TIME_T = CURLINFO_OFF_T + 65, + CURLINFO_LASTONE = 65 } CURLINFO; /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as diff --git a/Utilities/cmcurl/include/curl/curlver.h b/Utilities/cmcurl/include/curl/curlver.h index 097317a..85008e1 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.5.0" +#define LIBCURL_VERSION "8.6.0" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 8 -#define LIBCURL_VERSION_MINOR 5 +#define LIBCURL_VERSION_MINOR 6 #define LIBCURL_VERSION_PATCH 0 /* 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 0x080500 +#define LIBCURL_VERSION_NUM 0x080600 /* * This is the date and time when the full source package was created. The diff --git a/Utilities/cmcurl/include/curl/mprintf.h b/Utilities/cmcurl/include/curl/mprintf.h index dc5664b..4f70454 100644 --- a/Utilities/cmcurl/include/curl/mprintf.h +++ b/Utilities/cmcurl/include/curl/mprintf.h @@ -34,19 +34,27 @@ extern "C" { #if (defined(__GNUC__) || defined(__clang__)) && \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__MINGW32__) && !defined(CURL_NO_FMT_CHECKS) -#define CURL_TEMP_PRINTF(a,b) __attribute__ ((format(printf, a, b))) + !defined(CURL_NO_FMT_CHECKS) +#if defined(__MINGW32__) && !defined(__clang__) +#define CURL_TEMP_PRINTF(fmt, arg) \ + __attribute__((format(gnu_printf, fmt, arg))) #else -#define CURL_TEMP_PRINTF(a,b) +#define CURL_TEMP_PRINTF(fmt, arg) \ + __attribute__((format(printf, fmt, arg))) +#endif +#else +#define CURL_TEMP_PRINTF(fmt, arg) #endif -CURL_EXTERN int curl_mprintf(const char *format, ...) CURL_TEMP_PRINTF(1, 2); +CURL_EXTERN int curl_mprintf(const char *format, ...) + CURL_TEMP_PRINTF(1, 2); CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...) CURL_TEMP_PRINTF(2, 3); CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...) CURL_TEMP_PRINTF(2, 3); CURL_EXTERN int curl_msnprintf(char *buffer, size_t maxlength, - const char *format, ...) CURL_TEMP_PRINTF(3, 4); + const char *format, ...) + CURL_TEMP_PRINTF(3, 4); CURL_EXTERN int curl_mvprintf(const char *format, va_list args) CURL_TEMP_PRINTF(1, 0); CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args) diff --git a/Utilities/cmcurl/include/curl/system.h b/Utilities/cmcurl/include/curl/system.h index f2554b4..81a1b81 100644 --- a/Utilities/cmcurl/include/curl/system.h +++ b/Utilities/cmcurl/include/curl/system.h @@ -184,9 +184,8 @@ # define CURL_FORMAT_CURL_OFF_TU PRIu64 # define CURL_SUFFIX_CURL_OFF_T LL # define CURL_SUFFIX_CURL_OFF_TU ULL -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_TYPEOF_CURL_SOCKLEN_T int # define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_WS2TCPIP_H 1 #elif defined(__VMS) # if defined(__VAX) @@ -417,15 +416,6 @@ #define CURL_PULL_SYS_POLL_H #endif - -/* CURL_PULL_WS2TCPIP_H is defined above when inclusion of header file */ -/* ws2tcpip.h is required here to properly make type definitions below. */ -#ifdef CURL_PULL_WS2TCPIP_H -# include <winsock2.h> -# include <windows.h> -# include <ws2tcpip.h> -#endif - /* CURL_PULL_SYS_TYPES_H is defined above when inclusion of header file */ /* sys/types.h is required here to properly make type definitions below. */ #ifdef CURL_PULL_SYS_TYPES_H diff --git a/Utilities/cmcurl/include/curl/urlapi.h b/Utilities/cmcurl/include/curl/urlapi.h index 88cdeb3..91f8c45 100644 --- a/Utilities/cmcurl/include/curl/urlapi.h +++ b/Utilities/cmcurl/include/curl/urlapi.h @@ -63,6 +63,7 @@ typedef enum { CURLUE_BAD_SLASHES, /* 28 */ CURLUE_BAD_USER, /* 29 */ CURLUE_LACKS_IDN, /* 30 */ + CURLUE_TOO_LARGE, /* 31 */ CURLUE_LAST } CURLUcode; diff --git a/Utilities/cmcurl/lib/Makefile.inc b/Utilities/cmcurl/lib/Makefile.inc index e568ef9..627148a 100644 --- a/Utilities/cmcurl/lib/Makefile.inc +++ b/Utilities/cmcurl/lib/Makefile.inc @@ -78,15 +78,19 @@ LIB_VTLS_HFILES = \ LIB_VQUIC_CFILES = \ vquic/curl_msh3.c \ vquic/curl_ngtcp2.c \ + vquic/curl_osslq.c \ vquic/curl_quiche.c \ - vquic/vquic.c + vquic/vquic.c \ + vquic/vquic-tls.c LIB_VQUIC_HFILES = \ vquic/curl_msh3.h \ vquic/curl_ngtcp2.h \ + vquic/curl_osslq.h \ vquic/curl_quiche.h \ vquic/vquic.h \ - vquic/vquic_int.h + vquic/vquic_int.h \ + vquic/vquic-tls.h LIB_VSSH_CFILES = \ vssh/libssh.c \ diff --git a/Utilities/cmcurl/lib/altsvc.c b/Utilities/cmcurl/lib/altsvc.c index 35450d6..e9f62bf 100644 --- a/Utilities/cmcurl/lib/altsvc.c +++ b/Utilities/cmcurl/lib/altsvc.c @@ -106,9 +106,11 @@ static struct altsvc *altsvc_createid(const char *srchost, dlen = strlen(dsthost); DEBUGASSERT(hlen); DEBUGASSERT(dlen); - if(!hlen || !dlen) + if(!hlen || !dlen) { /* bad input */ + free(as); return NULL; + } if((hlen > 2) && srchost[0] == '[') { /* IPv6 address, strip off brackets */ srchost++; @@ -123,11 +125,11 @@ static struct altsvc *altsvc_createid(const char *srchost, dlen -= 2; } - as->src.host = Curl_strndup(srchost, hlen); + as->src.host = Curl_memdup0(srchost, hlen); if(!as->src.host) goto error; - as->dst.host = Curl_strndup(dsthost, dlen); + as->dst.host = Curl_memdup0(dsthost, dlen); if(!as->dst.host) goto error; @@ -333,9 +335,6 @@ CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file) CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl) { DEBUGASSERT(asi); - if(!ctrl) - /* unexpected */ - return CURLE_BAD_FUNCTION_ARGUMENT; asi->flags = ctrl; return CURLE_OK; } diff --git a/Utilities/cmcurl/lib/asyn-ares.c b/Utilities/cmcurl/lib/asyn-ares.c index 437c933..76efba7 100644 --- a/Utilities/cmcurl/lib/asyn-ares.c +++ b/Utilities/cmcurl/lib/asyn-ares.c @@ -173,10 +173,26 @@ CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) int status; struct ares_options options; int optmask = ARES_OPT_SOCK_STATE_CB; + static int ares_ver = 0; options.sock_state_cb = sock_state_cb; options.sock_state_cb_data = easy; - options.timeout = CARES_TIMEOUT_PER_ATTEMPT; - optmask |= ARES_OPT_TIMEOUTMS; + if(ares_ver == 0) + ares_version(&ares_ver); + + if(ares_ver < 0x011400) { /* c-ares included similar change since 1.20.0 */ + options.timeout = CARES_TIMEOUT_PER_ATTEMPT; + optmask |= ARES_OPT_TIMEOUTMS; + } + + /* + if c ares < 1.20.0: curl set timeout to CARES_TIMEOUT_PER_ATTEMPT (2s) + + if c-ares >= 1.20.0 it already has the timeout to 2s, curl does not need + to set the timeout value; + + if c-ares >= 1.24.0, user can set the timeout via /etc/resolv.conf to + overwrite c-ares' timeout. + */ status = ares_init_options((ares_channel*)resolver, &options, optmask); if(status != ARES_SUCCESS) { @@ -228,9 +244,9 @@ static void destroy_async_data(struct Curl_async *async); void Curl_resolver_cancel(struct Curl_easy *data) { DEBUGASSERT(data); - if(data->conn->resolve_async.resolver) - ares_cancel((ares_channel)data->conn->resolve_async.resolver); - destroy_async_data(&data->conn->resolve_async); + if(data->state.async.resolver) + ares_cancel((ares_channel)data->state.async.resolver); + destroy_async_data(&data->state.async); } /* @@ -278,14 +294,14 @@ int Curl_resolver_getsock(struct Curl_easy *data, struct timeval timebuf; struct timeval *timeout; long milli; - int max = ares_getsock((ares_channel)data->conn->resolve_async.resolver, + int max = ares_getsock((ares_channel)data->state.async.resolver, (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); maxtime.tv_sec = CURL_TIMEOUT_RESOLVE; maxtime.tv_usec = 0; - timeout = ares_timeout((ares_channel)data->conn->resolve_async.resolver, - &maxtime, &timebuf); + timeout = ares_timeout((ares_channel)data->state.async.resolver, &maxtime, + &timebuf); milli = (long)curlx_tvtoms(timeout); if(milli == 0) milli += 10; @@ -313,8 +329,8 @@ static int waitperform(struct Curl_easy *data, timediff_t timeout_ms) int i; int num = 0; - bitmask = ares_getsock((ares_channel)data->conn->resolve_async.resolver, - socks, ARES_GETSOCK_MAXNUM); + bitmask = ares_getsock((ares_channel)data->state.async.resolver, socks, + ARES_GETSOCK_MAXNUM); for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) { pfd[i].events = 0; @@ -344,12 +360,12 @@ static int waitperform(struct Curl_easy *data, timediff_t timeout_ms) if(!nfds) /* Call ares_process() unconditionally here, even if we simply timed out above, as otherwise the ares name resolve won't timeout! */ - ares_process_fd((ares_channel)data->conn->resolve_async.resolver, - ARES_SOCKET_BAD, ARES_SOCKET_BAD); + ares_process_fd((ares_channel)data->state.async.resolver, ARES_SOCKET_BAD, + ARES_SOCKET_BAD); else { /* move through the descriptors and ask for processing on them */ for(i = 0; i < num; i++) - ares_process_fd((ares_channel)data->conn->resolve_async.resolver, + ares_process_fd((ares_channel)data->state.async.resolver, (pfd[i].revents & (POLLRDNORM|POLLIN))? pfd[i].fd:ARES_SOCKET_BAD, (pfd[i].revents & (POLLWRNORM|POLLOUT))? @@ -368,7 +384,7 @@ static int waitperform(struct Curl_easy *data, timediff_t timeout_ms) CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dns) { - struct thread_data *res = data->conn->resolve_async.tdata; + struct thread_data *res = data->state.async.tdata; CURLcode result = CURLE_OK; DEBUGASSERT(dns); @@ -397,7 +413,7 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, 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->conn->resolve_async.resolver); + ares_cancel((ares_channel)data->state.async.resolver); DEBUGASSERT(res->num_pending == 0); } #endif @@ -408,12 +424,12 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, them */ res->temp_ai = NULL; - if(!data->conn->resolve_async.dns) + if(!data->state.async.dns) result = Curl_resolver_error(data); else - *dns = data->conn->resolve_async.dns; + *dns = data->state.async.dns; - destroy_async_data(&data->conn->resolve_async); + destroy_async_data(&data->state.async); } return result; @@ -464,8 +480,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->conn->resolve_async.resolver, - &store, &tv); + tvp = ares_timeout((ares_channel)data->state.async.resolver, &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 @@ -479,7 +494,7 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, return CURLE_UNRECOVERABLE_POLL; result = Curl_resolver_is_resolved(data, entry); - if(result || data->conn->resolve_async.done) + if(result || data->state.async.done) break; if(Curl_pgrsUpdate(data)) @@ -500,12 +515,12 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, } if(result) /* failure, so we cancel the ares operation */ - ares_cancel((ares_channel)data->conn->resolve_async.resolver); + ares_cancel((ares_channel)data->state.async.resolver); /* Operation complete, if the lookup was successful we now have the entry in the cache. */ if(entry) - *entry = data->conn->resolve_async.dns; + *entry = data->state.async.dns; if(result) /* close the connection, since we can't return failure here without @@ -572,13 +587,12 @@ static void query_completed_cb(void *arg, /* (struct connectdata *) */ be valid so only defer it when we know the 'status' says its fine! */ return; - res = data->conn->resolve_async.tdata; + res = data->state.async.tdata; if(res) { res->num_pending--; if(CURL_ASYNC_SUCCESS == status) { - struct Curl_addrinfo *ai = Curl_he2ai(hostent, - data->conn->resolve_async.port); + struct Curl_addrinfo *ai = Curl_he2ai(hostent, data->state.async.port); if(ai) { compound_results(res, ai); } @@ -729,16 +743,14 @@ static void addrinfo_cb(void *arg, int status, int timeouts, struct ares_addrinfo *result) { struct Curl_easy *data = (struct Curl_easy *)arg; - if(data->conn) { - struct thread_data *res = data->conn->resolve_async.tdata; - (void)timeouts; - if(ARES_SUCCESS == status) { - res->temp_ai = ares2addr(result->nodes); - res->last_status = CURL_ASYNC_SUCCESS; - ares_freeaddrinfo(result); - } - res->num_pending--; + struct thread_data *res = data->state.async.tdata; + (void)timeouts; + if(ARES_SUCCESS == status) { + res->temp_ai = ares2addr(result->nodes); + res->last_status = CURL_ASYNC_SUCCESS; + ares_freeaddrinfo(result); } + res->num_pending--; } #endif @@ -762,12 +774,12 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, res = calloc(1, sizeof(struct thread_data) + namelen); if(res) { strcpy(res->hostname, hostname); - data->conn->resolve_async.hostname = res->hostname; - data->conn->resolve_async.port = port; - data->conn->resolve_async.done = FALSE; /* not done */ - data->conn->resolve_async.status = 0; /* clear */ - data->conn->resolve_async.dns = NULL; /* clear */ - data->conn->resolve_async.tdata = res; + 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; /* initial status - failed */ res->last_status = ARES_ENOTFOUND; @@ -797,8 +809,8 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, hints.ai_flags = ARES_AI_NUMERICSERV; msnprintf(service, sizeof(service), "%d", port); res->num_pending = 1; - ares_getaddrinfo((ares_channel)data->conn->resolve_async.resolver, - hostname, service, &hints, addrinfo_cb, data); + ares_getaddrinfo((ares_channel)data->state.async.resolver, hostname, + service, &hints, addrinfo_cb, data); } #else @@ -808,10 +820,10 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, res->num_pending = 2; /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->conn->resolve_async.resolver, - hostname, PF_INET, query_completed_cb, data); - ares_gethostbyname((ares_channel)data->conn->resolve_async.resolver, - hostname, PF_INET6, query_completed_cb, data); + 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 @@ -819,7 +831,7 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, res->num_pending = 1; /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->conn->resolve_async.resolver, + ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, PF_INET, query_completed_cb, data); } @@ -833,7 +845,6 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, char *servers) { CURLcode result = CURLE_NOT_BUILT_IN; - ares_channel channel, lchannel = NULL; int ares_result; /* If server is NULL or empty, this would purge all DNS servers @@ -846,23 +857,11 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, return CURLE_OK; #ifdef HAVE_CARES_SERVERS_CSV - if(data->conn) - channel = data->conn->resolve_async.resolver; - else { - /* we are called by setopt on a data without a connection (yet). In that - * case we set the value on a local instance for checking. - * The configured data options are set when the connection for this - * transfer is created. */ - result = Curl_resolver_init(data, (void **)&lchannel); - if(result) - goto out; - channel = lchannel; - } - #ifdef HAVE_CARES_PORTS_CSV - ares_result = ares_set_servers_ports_csv(channel, servers); + ares_result = ares_set_servers_ports_csv(data->state.async.resolver, + servers); #else - ares_result = ares_set_servers_csv(channel, servers); + ares_result = ares_set_servers_csv(data->state.async.resolver, servers); #endif switch(ares_result) { case ARES_SUCCESS: @@ -875,12 +874,10 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, case ARES_ENODATA: case ARES_EBADSTR: default: + DEBUGF(infof(data, "bad servers set")); result = CURLE_BAD_FUNCTION_ARGUMENT; break; } -out: - if(lchannel) - Curl_resolver_cleanup(lchannel); #else /* too old c-ares version! */ (void)data; (void)(ares_result); @@ -892,14 +889,11 @@ CURLcode Curl_set_dns_interface(struct Curl_easy *data, const char *interf) { #ifdef HAVE_CARES_LOCAL_DEV - if(data->conn) { - /* not a setopt test run, set the value */ - if(!interf) - interf = ""; + if(!interf) + interf = ""; + + ares_set_local_dev((ares_channel)data->state.async.resolver, interf); - ares_set_local_dev((ares_channel)data->conn->resolve_async.resolver, - interf); - } return CURLE_OK; #else /* c-ares version too old! */ (void)data; @@ -919,15 +913,13 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, } else { if(Curl_inet_pton(AF_INET, local_ip4, &a4) != 1) { + DEBUGF(infof(data, "bad DNS IPv4 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } - if(data->conn) { - /* not a setopt test run, set the value */ - ares_set_local_ip4((ares_channel)data->conn->resolve_async.resolver, - ntohl(a4.s_addr)); - } + ares_set_local_ip4((ares_channel)data->state.async.resolver, + ntohl(a4.s_addr)); return CURLE_OK; #else /* c-ares version too old! */ @@ -949,14 +941,12 @@ CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, } else { if(Curl_inet_pton(AF_INET6, local_ip6, a6) != 1) { + DEBUGF(infof(data, "bad DNS IPv6 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } - if(data->conn) { - /* not a setopt test run, set the value */ - ares_set_local_ip6((ares_channel)data->conn->resolve_async.resolver, a6); - } + ares_set_local_ip6((ares_channel)data->state.async.resolver, a6); return CURLE_OK; #else /* c-ares version too old! */ diff --git a/Utilities/cmcurl/lib/asyn-thread.c b/Utilities/cmcurl/lib/asyn-thread.c index 63414b6..d4d382a 100644 --- a/Utilities/cmcurl/lib/asyn-thread.c +++ b/Utilities/cmcurl/lib/asyn-thread.c @@ -54,6 +54,7 @@ # define RESOLVER_ENOMEM ENOMEM #endif +#include "system_win32.h" #include "urldata.h" #include "sendf.h" #include "hostip.h" @@ -136,7 +137,7 @@ static void destroy_async_data(struct Curl_async *); */ void Curl_resolver_cancel(struct Curl_easy *data) { - destroy_async_data(&data->conn->resolve_async); + destroy_async_data(&data->state.async); } /* This function is used to init a threaded resolve */ @@ -144,9 +145,22 @@ static bool init_resolve_thread(struct Curl_easy *data, const char *hostname, int port, const struct addrinfo *hints); +#ifdef _WIN32 +/* Thread sync data used by GetAddrInfoExW for win8+ */ +struct thread_sync_data_w8 +{ + OVERLAPPED overlapped; + ADDRINFOEXW_ *res; + HANDLE cancel_ev; + ADDRINFOEXW_ hints; +}; +#endif /* Data for synchronization between resolver thread and its parent */ struct thread_sync_data { +#ifdef _WIN32 + struct thread_sync_data_w8 w8; +#endif curl_mutex_t *mtx; int done; int port; @@ -165,6 +179,9 @@ struct thread_sync_data { }; struct thread_data { +#ifdef _WIN32 + HANDLE complete_ev; +#endif curl_thread_t thread_hnd; unsigned int poll_interval; timediff_t interval_end; @@ -173,7 +190,7 @@ struct thread_data { static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data) { - return &(data->conn->resolve_async.tdata->tsd); + return &(data->state.async.tdata->tsd); } /* Destroy resolver thread synchronization data */ @@ -276,6 +293,151 @@ static CURLcode getaddrinfo_complete(struct Curl_easy *data) return result; } +#ifdef _WIN32 +static VOID WINAPI +query_complete(DWORD err, DWORD bytes, LPWSAOVERLAPPED overlapped) +{ + size_t ss_size; + const ADDRINFOEXW_ *ai; + struct Curl_addrinfo *ca; + struct Curl_addrinfo *cafirst = NULL; + struct Curl_addrinfo *calast = NULL; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-align" +#endif + struct thread_sync_data *tsd = + CONTAINING_RECORD(overlapped, struct thread_sync_data, w8.overlapped); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + struct thread_data *td = tsd->td; + const ADDRINFOEXW_ *res = tsd->w8.res; + int error = (int)err; + (void)bytes; + + if(error == ERROR_SUCCESS) { + /* traverse the addrinfo list */ + + for(ai = res; ai != NULL; ai = ai->ai_next) { + size_t namelen = ai->ai_canonname ? wcslen(ai->ai_canonname) + 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); +#ifdef ENABLE_IPV6 + else if(ai->ai_family == AF_INET6) + ss_size = sizeof(struct sockaddr_in6); +#endif + 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 Curl_addrinfo) + ss_size + namelen); + if(!ca) { + error = EAI_MEMORY; + break; + } + + /* 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 Curl_addrinfo)); + memcpy(ca->ai_addr, ai->ai_addr, ss_size); + + if(namelen) { + size_t i; + ca->ai_canonname = (void *)((char *)ca->ai_addr + ss_size); + for(i = 0; i < namelen; ++i) /* convert wide string to ascii */ + ca->ai_canonname[i] = (char)ai->ai_canonname[i]; + ca->ai_canonname[namelen] = '\0'; + } + + /* 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; + } + + /* if we failed, also destroy the Curl_addrinfo list */ + if(error) { + Curl_freeaddrinfo(cafirst); + cafirst = NULL; + } + else if(!cafirst) { +#ifdef EAI_NONAME + /* rfc3493 conformant */ + error = EAI_NONAME; +#else + /* rfc3493 obsoleted */ + error = EAI_NODATA; +#endif +#ifdef USE_WINSOCK + SET_SOCKERRNO(error); +#endif + } + tsd->res = cafirst; + } + + if(tsd->w8.res) { + Curl_FreeAddrInfoExW(tsd->w8.res); + tsd->w8.res = NULL; + } + + if(error) { + tsd->sock_error = SOCKERRNO?SOCKERRNO:error; + 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 + char buf[1]; + if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { + /* DNS has been resolved, signal client task */ + buf[0] = 1; + if(swrite(tsd->sock_pair[1], buf, sizeof(buf)) < 0) { + /* update sock_erro to errno */ + tsd->sock_error = SOCKERRNO; + } + } +#endif + tsd->done = 1; + Curl_mutex_release(tsd->mtx); + if(td->complete_ev) + SetEvent(td->complete_ev); /* Notify caller that the query completed */ + } +} +#endif #ifdef HAVE_GETADDRINFO @@ -391,9 +553,21 @@ static void destroy_async_data(struct Curl_async *async) Curl_mutex_release(td->tsd.mtx); if(!done) { +#ifdef _WIN32 + if(td->complete_ev) + CloseHandle(td->complete_ev); + else +#endif Curl_thread_destroy(td->thread_hnd); } else { +#ifdef _WIN32 + if(td->complete_ev) { + Curl_GetAddrInfoExCancel(&td->tsd.w8.cancel_ev); + WaitForSingleObject(td->complete_ev, INFINITE); + CloseHandle(td->complete_ev); + } +#endif if(td->thread_hnd != curl_thread_t_null) Curl_thread_join(&td->thread_hnd); @@ -428,9 +602,9 @@ static bool init_resolve_thread(struct Curl_easy *data, { struct thread_data *td = calloc(1, sizeof(struct thread_data)); int err = ENOMEM; - struct Curl_async *asp = &data->conn->resolve_async; + struct Curl_async *asp = &data->state.async; - data->conn->resolve_async.tdata = td; + data->state.async.tdata = td; if(!td) goto errno_exit; @@ -439,6 +613,9 @@ static bool init_resolve_thread(struct Curl_easy *data, asp->status = 0; asp->dns = NULL; td->thread_hnd = curl_thread_t_null; +#ifdef _WIN32 + td->complete_ev = NULL; +#endif if(!init_thread_sync_data(td, hostname, port, hints)) { asp->tdata = NULL; @@ -454,6 +631,41 @@ static bool init_resolve_thread(struct Curl_easy *data, /* The thread will set this to 1 when complete. */ td->tsd.done = 0; +#ifdef _WIN32 + if(Curl_isWindows8OrGreater && Curl_FreeAddrInfoExW && + Curl_GetAddrInfoExCancel && Curl_GetAddrInfoExW) { +#define MAX_NAME_LEN 256 /* max domain name is 253 chars */ +#define MAX_PORT_LEN 8 + WCHAR namebuf[MAX_NAME_LEN]; + WCHAR portbuf[MAX_PORT_LEN]; + /* calculate required length */ + int w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, hostname, + -1, NULL, 0); + if((w_len > 0) && (w_len < MAX_NAME_LEN)) { + /* do utf8 conversion */ + w_len = MultiByteToWideChar(CP_UTF8, 0, hostname, -1, namebuf, w_len); + if((w_len > 0) && (w_len < MAX_NAME_LEN)) { + swprintf(portbuf, MAX_PORT_LEN, L"%d", port); + td->tsd.w8.hints.ai_family = hints->ai_family; + td->tsd.w8.hints.ai_socktype = hints->ai_socktype; + td->complete_ev = CreateEvent(NULL, TRUE, FALSE, NULL); + if(!td->complete_ev) { + /* failed to start, mark it as done here for proper cleanup. */ + td->tsd.done = 1; + goto err_exit; + } + err = Curl_GetAddrInfoExW(namebuf, portbuf, NS_DNS, + NULL, &td->tsd.w8.hints, &td->tsd.w8.res, + NULL, &td->tsd.w8.overlapped, + &query_complete, &td->tsd.w8.cancel_ev); + if(err != WSA_IO_PENDING) + query_complete(err, 0, &td->tsd.w8.overlapped); + return TRUE; + } + } + } +#endif + #ifdef HAVE_GETADDRINFO td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd); #else @@ -488,11 +700,24 @@ static CURLcode thread_wait_resolv(struct Curl_easy *data, CURLcode result = CURLE_OK; DEBUGASSERT(data); - td = data->conn->resolve_async.tdata; + td = data->state.async.tdata; DEBUGASSERT(td); +#ifdef _WIN32 + DEBUGASSERT(td->complete_ev || td->thread_hnd != curl_thread_t_null); +#else DEBUGASSERT(td->thread_hnd != curl_thread_t_null); +#endif /* wait for the thread to resolve the name */ +#ifdef _WIN32 + if(td->complete_ev) { + WaitForSingleObject(td->complete_ev, INFINITE); + CloseHandle(td->complete_ev); + if(entry) + result = getaddrinfo_complete(data); + } + else +#endif if(Curl_thread_join(&td->thread_hnd)) { if(entry) result = getaddrinfo_complete(data); @@ -500,18 +725,18 @@ static CURLcode thread_wait_resolv(struct Curl_easy *data, else DEBUGASSERT(0); - data->conn->resolve_async.done = TRUE; + data->state.async.done = TRUE; if(entry) - *entry = data->conn->resolve_async.dns; + *entry = data->state.async.dns; - if(!data->conn->resolve_async.dns && report) + if(!data->state.async.dns && report) /* a name was not resolved, report error */ result = Curl_resolver_error(data); - destroy_async_data(&data->conn->resolve_async); + destroy_async_data(&data->state.async); - if(!data->conn->resolve_async.dns && report) + if(!data->state.async.dns && report) connclose(data->conn, "asynch resolve failed"); return result; @@ -524,7 +749,7 @@ static CURLcode thread_wait_resolv(struct Curl_easy *data, */ void Curl_resolver_kill(struct Curl_easy *data) { - struct thread_data *td = data->conn->resolve_async.tdata; + struct thread_data *td = data->state.async.tdata; /* If we're still resolving, we must wait for the threads to fully clean up, unfortunately. Otherwise, we can simply cancel to clean up any resolver @@ -563,7 +788,7 @@ CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **entry) { - struct thread_data *td = data->conn->resolve_async.tdata; + struct thread_data *td = data->state.async.tdata; int done = 0; DEBUGASSERT(entry); @@ -581,13 +806,13 @@ CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, if(done) { getaddrinfo_complete(data); - if(!data->conn->resolve_async.dns) { + if(!data->state.async.dns) { CURLcode result = Curl_resolver_error(data); - destroy_async_data(&data->conn->resolve_async); + destroy_async_data(&data->state.async); return result; } - destroy_async_data(&data->conn->resolve_async); - *entry = data->conn->resolve_async.dns; + destroy_async_data(&data->state.async); + *entry = data->state.async.dns; } else { /* poll for name lookup done with exponential backoff up to 250ms */ @@ -619,9 +844,9 @@ 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->conn->resolve_async.resolver; + struct resdata *reslv = (struct resdata *)data->state.async.resolver; #ifndef CURL_DISABLE_SOCKETPAIR - struct thread_data *td = data->conn->resolve_async.tdata; + struct thread_data *td = data->state.async.tdata; #else (void)socks; #endif @@ -662,7 +887,7 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, int port, int *waitp) { - struct resdata *reslv = (struct resdata *)data->conn->resolve_async.resolver; + struct resdata *reslv = (struct resdata *)data->state.async.resolver; *waitp = 0; /* default to synchronous response */ @@ -691,7 +916,7 @@ struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, { struct addrinfo hints; int pf = PF_INET; - struct resdata *reslv = (struct resdata *)data->conn->resolve_async.resolver; + struct resdata *reslv = (struct resdata *)data->state.async.resolver; *waitp = 0; /* default to synchronous response */ diff --git a/Utilities/cmcurl/lib/bufref.c b/Utilities/cmcurl/lib/bufref.c index ce686b6..f0a0e2a 100644 --- a/Utilities/cmcurl/lib/bufref.c +++ b/Utilities/cmcurl/lib/bufref.c @@ -25,6 +25,7 @@ #include "curl_setup.h" #include "urldata.h" #include "bufref.h" +#include "strdup.h" #include "curl_memory.h" #include "memdebug.h" @@ -116,12 +117,9 @@ CURLcode Curl_bufref_memdup(struct bufref *br, const void *ptr, size_t len) DEBUGASSERT(len <= CURL_MAX_INPUT_LENGTH); if(ptr) { - cpy = malloc(len + 1); + cpy = Curl_memdup0(ptr, len); if(!cpy) return CURLE_OUT_OF_MEMORY; - if(len) - memcpy(cpy, ptr, len); - cpy[len] = '\0'; } Curl_bufref_set(br, cpy, len, curl_free); diff --git a/Utilities/cmcurl/lib/c-hyper.c b/Utilities/cmcurl/lib/c-hyper.c index 787d6bb..d02ecd7 100644 --- a/Utilities/cmcurl/lib/c-hyper.c +++ b/Utilities/cmcurl/lib/c-hyper.c @@ -148,7 +148,7 @@ static int hyper_each_header(void *userdata, if(name_len + value_len + 2 > CURL_MAX_HTTP_HEADER) { failf(data, "Too long response header"); - data->state.hresult = CURLE_OUT_OF_MEMORY; + data->state.hresult = CURLE_TOO_LARGE; return HYPER_ITER_BREAK; } @@ -325,6 +325,9 @@ static CURLcode empty_header(struct Curl_easy *data) CURLE_WRITE_ERROR : CURLE_OK; if(result) failf(data, "hyperstream: couldn't pass blank header"); + /* Hyper does chunked decoding itself. If it was added during + * response header processing, remove it again. */ + Curl_cwriter_remove_by_name(data, "chunked"); } return result; } diff --git a/Utilities/cmcurl/lib/cf-h1-proxy.c b/Utilities/cmcurl/lib/cf-h1-proxy.c index 2e23b0b..167e531 100644 --- a/Utilities/cmcurl/lib/cf-h1-proxy.c +++ b/Utilities/cmcurl/lib/cf-h1-proxy.c @@ -70,6 +70,7 @@ struct h1_tunnel_state { struct dynbuf request_data; size_t nsent; size_t headerlines; + struct Curl_chunker ch; enum keeponval { KEEPON_DONE, KEEPON_CONNECT, @@ -133,6 +134,7 @@ static CURLcode tunnel_init(struct Curl_cfilter *cf, Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); Curl_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); + Curl_httpchunk_init(data, &ts->ch, TRUE); *pts = ts; connkeep(cf->conn, "HTTP proxy CONNECT"); @@ -146,14 +148,6 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, { if(ts->tunnel_state == new_state) return; - /* leaving this one */ - switch(ts->tunnel_state) { - case H1_TUNNEL_CONNECT: - data->req.ignorebody = FALSE; - break; - default: - break; - } /* entering this one */ switch(new_state) { case H1_TUNNEL_INIT: @@ -183,7 +177,7 @@ static void h1_tunnel_go_state(struct Curl_cfilter *cf, infof(data, "CONNECT phase completed"); data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; - /* FALLTHROUGH */ + FALLTHROUGH(); case H1_TUNNEL_FAILED: if(new_state == H1_TUNNEL_FAILED) CURL_TRC_CF(data, cf, "new tunnel state 'failed'"); @@ -212,6 +206,7 @@ static void tunnel_free(struct Curl_cfilter *cf, h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); Curl_dyn_free(&ts->rcvbuf); Curl_dyn_free(&ts->request_data); + Curl_httpchunk_free(data, &ts->ch); free(ts); cf->ctx = NULL; } @@ -344,8 +339,8 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, STRCONST("chunked"))) { infof(data, "CONNECT responded chunked"); ts->chunked_encoding = TRUE; - /* init our chunky engine */ - Curl_httpchunk_init(data); + /* reset our chunky engine */ + Curl_httpchunk_reset(data, &ts->ch, TRUE); } } else if(Curl_compareheader(header, @@ -373,7 +368,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, struct SingleRequest *k = &data->req; curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data); char *linep; - size_t perline; + size_t line_len; int error, writetype; #define SELECT_OK 0 @@ -432,17 +427,17 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, break; } } - else { + else if(ts->chunked_encoding) { /* chunked-encoded body, so we need to do the chunked dance properly to know when the end of the body is reached */ - CHUNKcode r; - CURLcode extra; size_t consumed = 0; /* now parse the chunked piece of data so that we can properly tell when the stream ends */ - r = Curl_httpchunk_read(data, &byte, 1, &consumed, &extra); - if(r == CHUNKE_STOP) { + result = Curl_httpchunk_read(data, &ts->ch, &byte, 1, &consumed); + if(result) + return result; + if(Curl_httpchunk_is_done(data, &ts->ch)) { /* we're done reading chunks! */ infof(data, "chunk reading DONE"); ts->keepon = KEEPON_DONE; @@ -462,19 +457,19 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, ts->headerlines++; linep = Curl_dyn_ptr(&ts->rcvbuf); - perline = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ + line_len = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ /* output debug if that is requested */ - Curl_debug(data, CURLINFO_HEADER_IN, linep, perline); + Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); /* send the header to the callback */ writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); - result = Curl_client_write(data, writetype, linep, perline); + result = Curl_client_write(data, writetype, linep, line_len); if(result) return result; - result = Curl_bump_headersize(data, perline, TRUE); + result = Curl_bump_headersize(data, line_len, TRUE); if(result) return result; @@ -497,29 +492,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, " bytes of response-body", ts->cl); } else if(ts->chunked_encoding) { - CHUNKcode r; - CURLcode extra; - size_t consumed = 0; - infof(data, "Ignore chunked response-body"); - - /* We set ignorebody true here since the chunked decoder - function will acknowledge that. Pay attention so that this is - cleared again when this function returns! */ - k->ignorebody = TRUE; - - if(linep[1] == '\n') - /* this can only be a LF if the letter at index 0 was a CR */ - linep++; - - /* now parse the chunked piece of data so that we can properly - tell when the stream ends */ - r = Curl_httpchunk_read(data, linep + 1, 1, &consumed, &extra); - if(r == CHUNKE_STOP) { - /* we're done reading chunks! */ - infof(data, "chunk reading DONE"); - ts->keepon = KEEPON_DONE; - } } else { /* without content-length or chunked encoding, we @@ -752,7 +725,7 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, } if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) && - data->set.str[STRING_USERAGENT]) { + data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { struct dynbuf ua; Curl_dyn_init(&ua, DYN_HTTP_REQUEST); result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n", @@ -912,7 +885,7 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, if(result) goto out; h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data); - /* FALLTHROUGH */ + FALLTHROUGH(); case H1_TUNNEL_CONNECT: /* see that the request is completely sent */ @@ -921,7 +894,7 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, if(result || !done) goto out; h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data); - /* FALLTHROUGH */ + FALLTHROUGH(); case H1_TUNNEL_RECEIVE: /* read what is there */ @@ -936,7 +909,7 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, goto out; /* got it */ h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data); - /* FALLTHROUGH */ + FALLTHROUGH(); case H1_TUNNEL_RESPONSE: CURL_TRC_CF(data, cf, "CONNECT response"); @@ -1030,6 +1003,14 @@ out: *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx); if(*done) { cf->connected = TRUE; + /* Restore `data->req` fields that may habe been touched */ + data->req.header = TRUE; /* assume header */ + data->req.bytecount = 0; + data->req.ignorebody = FALSE; + Curl_client_cleanup(data); + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + tunnel_free(cf, data); } return result; diff --git a/Utilities/cmcurl/lib/cf-h2-proxy.c b/Utilities/cmcurl/lib/cf-h2-proxy.c index 147acdc..f8f2f3c 100644 --- a/Utilities/cmcurl/lib/cf-h2-proxy.c +++ b/Utilities/cmcurl/lib/cf-h2-proxy.c @@ -155,7 +155,7 @@ static void h2_tunnel_go_state(struct Curl_cfilter *cf, infof(data, "CONNECT phase completed"); data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; - /* FALLTHROUGH */ + FALLTHROUGH(); case H2_TUNNEL_FAILED: if(new_state == H2_TUNNEL_FAILED) CURL_TRC_CF(data, cf, "[%d] new tunnel state 'failed'", ts->stream_id); @@ -221,10 +221,10 @@ static void drain_tunnel(struct Curl_cfilter *cf, bits = CURL_CSELECT_IN; if(!tunnel->closed && !tunnel->reset && tunnel->upload_blocked_len) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x", + if(data->state.select_bits != bits) { + CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x", tunnel->stream_id, bits); - data->state.dselect_bits = bits; + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } @@ -1033,7 +1033,7 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, if(result) goto out; h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data); - /* FALLTHROUGH */ + FALLTHROUGH(); case H2_TUNNEL_CONNECT: /* see that the request is completely sent */ @@ -1052,7 +1052,7 @@ static CURLcode H2_CONNECT(struct Curl_cfilter *cf, result = CURLE_OK; goto out; } - /* FALLTHROUGH */ + FALLTHROUGH(); case H2_TUNNEL_RESPONSE: DEBUGASSERT(ts->has_final_response); diff --git a/Utilities/cmcurl/lib/cf-haproxy.c b/Utilities/cmcurl/lib/cf-haproxy.c index 1ca4393..c062887 100644 --- a/Utilities/cmcurl/lib/cf-haproxy.c +++ b/Utilities/cmcurl/lib/cf-haproxy.c @@ -125,7 +125,7 @@ static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, if(result) goto out; ctx->state = HAPROXY_SEND; - /* FALLTHROUGH */ + FALLTHROUGH(); case HAPROXY_SEND: len = Curl_dyn_len(&ctx->data_out); if(len > 0) { @@ -141,7 +141,7 @@ static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, } } ctx->state = HAPROXY_DONE; - /* FALLTHROUGH */ + FALLTHROUGH(); default: Curl_dyn_free(&ctx->data_out); break; diff --git a/Utilities/cmcurl/lib/cf-https-connect.c b/Utilities/cmcurl/lib/cf-https-connect.c index b4f33c8..b23fa05 100644 --- a/Utilities/cmcurl/lib/cf-https-connect.c +++ b/Utilities/cmcurl/lib/cf-https-connect.c @@ -266,7 +266,7 @@ static CURLcode cf_hc_connect(struct Curl_cfilter *cf, cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", cf->conn->transport); ctx->state = CF_HC_CONNECT; - /* FALLTHROUGH */ + FALLTHROUGH(); case CF_HC_CONNECT: if(cf_hc_baller_is_active(&ctx->h3_baller)) { diff --git a/Utilities/cmcurl/lib/cf-socket.c b/Utilities/cmcurl/lib/cf-socket.c index e42b4a8..742902f 100644 --- a/Utilities/cmcurl/lib/cf-socket.c +++ b/Utilities/cmcurl/lib/cf-socket.c @@ -137,14 +137,14 @@ static void nosigpipe(struct Curl_easy *data, #define nosigpipe(x,y) Curl_nop_stmt #endif -#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H) +#if defined(__DragonFly__) || defined(USE_WINSOCK) /* DragonFlyBSD and Windows use millisecond units */ #define KEEPALIVE_FACTOR(x) (x *= 1000) #else #define KEEPALIVE_FACTOR(x) #endif -#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS) +#if defined(USE_WINSOCK) && !defined(SIO_KEEPALIVE_VALS) #define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4) struct tcp_keepalive { @@ -163,7 +163,9 @@ 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) { - infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd); + infof(data, "Failed to set SO_KEEPALIVE on fd " + "%" CURL_FORMAT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } else { #if defined(SIO_KEEPALIVE_VALS) @@ -178,8 +180,9 @@ tcpkeepalive(struct Curl_easy *data, vals.keepaliveinterval = optval; if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID) &vals, sizeof(vals), NULL, 0, &dummy, NULL, NULL) != 0) { - infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd %d: %d", - (int)sockfd, WSAGetLastError()); + infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd " + "%" CURL_FORMAT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } #else #ifdef TCP_KEEPIDLE @@ -187,7 +190,9 @@ tcpkeepalive(struct Curl_easy *data, KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd); + infof(data, "Failed to set TCP_KEEPIDLE on fd " + "%" CURL_FORMAT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } #elif defined(TCP_KEEPALIVE) /* Mac OS X style */ @@ -195,7 +200,9 @@ tcpkeepalive(struct Curl_easy *data, KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd); + infof(data, "Failed to set TCP_KEEPALIVE on fd " + "%" CURL_FORMAT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } #endif #ifdef TCP_KEEPINTVL @@ -203,7 +210,9 @@ tcpkeepalive(struct Curl_easy *data, KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPINTVL on fd %d", sockfd); + infof(data, "Failed to set TCP_KEEPINTVL on fd " + "%" CURL_FORMAT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); } #endif #endif @@ -783,6 +792,7 @@ struct cf_socket_ctx { #endif BIT(got_first_byte); /* if first byte was received */ BIT(accepted); /* socket was accepted, not connected */ + BIT(sock_connected); /* socket is "connected", e.g. in UDP */ BIT(active); BIT(buffer_recv); }; @@ -983,20 +993,14 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, if(result) goto out; -#ifndef CURL_DISABLE_VERBOSE_STRINGS - { - const char *ipmsg; #ifdef ENABLE_IPV6 - if(ctx->addr.family == AF_INET6) { - set_ipv6_v6only(ctx->sock, 0); - ipmsg = " Trying [%s]:%d..."; - } - else -#endif - ipmsg = " Trying %s:%d..."; - infof(data, ipmsg, ctx->r_ip, ctx->r_port); + if(ctx->addr.family == AF_INET6) { + set_ipv6_v6only(ctx->sock, 0); + infof(data, " Trying [%s]:%d...", ctx->r_ip, ctx->r_port); } + else #endif + infof(data, " Trying %s:%d...", ctx->r_ip, ctx->r_port); #ifdef ENABLE_IPV6 is_tcp = (ctx->addr.family == AF_INET @@ -1054,7 +1058,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, /* set socket non-blocking */ (void)curlx_nonblock(ctx->sock, TRUE); - + ctx->sock_connected = (ctx->addr.socktype != SOCK_DGRAM); out: if(result) { if(ctx->sock != CURL_SOCKET_BAD) { @@ -1242,11 +1246,14 @@ static void cf_socket_adjust_pollset(struct Curl_cfilter *cf, struct cf_socket_ctx *ctx = cf->ctx; if(ctx->sock != CURL_SOCKET_BAD) { - if(!cf->connected) + if(!cf->connected) { Curl_pollset_set_out_only(data, ps, ctx->sock); - else + CURL_TRC_CF(data, cf, "adjust_pollset(!connected) -> %d socks", ps->num); + } + else if(!ctx->active) { Curl_pollset_add_in(data, ps, ctx->sock); - CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); + CURL_TRC_CF(data, cf, "adjust_pollset(!active) -> %d socks", ps->num); + } } } @@ -1429,36 +1436,11 @@ out: static void conn_set_primary_ip(struct Curl_cfilter *cf, struct Curl_easy *data) { -#ifdef HAVE_GETPEERNAME struct cf_socket_ctx *ctx = cf->ctx; - if(!(data->conn->handler->protocol & CURLPROTO_TFTP)) { - /* TFTP does not connect the endpoint: getpeername() failed with errno - 107: Transport endpoint is not connected */ - - char buffer[STRERROR_LEN]; - struct Curl_sockaddr_storage ssrem; - curl_socklen_t plen; - int port; - plen = sizeof(ssrem); - memset(&ssrem, 0, plen); - if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) { - int error = SOCKERRNO; - failf(data, "getpeername() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return; - } - if(!Curl_addr2string((struct sockaddr*)&ssrem, plen, - cf->conn->primary_ip, &port)) { - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - return; - } - } -#else - cf->conn->primary_ip[0] = 0; (void)data; -#endif + DEBUGASSERT(sizeof(ctx->r_ip) == sizeof(cf->conn->primary_ip)); + memcpy(cf->conn->primary_ip, ctx->r_ip, sizeof(cf->conn->primary_ip)); } static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -1574,7 +1556,7 @@ static CURLcode cf_socket_query(struct Curl_cfilter *cf, *when = ctx->first_byte_at; break; } - /* FALLTHROUGH */ + FALLTHROUGH(); default: *when = ctx->connected_at; break; @@ -1648,10 +1630,17 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, /* QUIC needs a connected socket, nonblocking */ DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD); +#if defined(__APPLE__) && defined(USE_OPENSSL_QUIC) + (void)rc; + /* On macOS OpenSSL QUIC fails on connected sockets. + * see: <https://github.com/openssl/openssl/issues/23251> */ +#else rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); if(-1 == rc) { return socket_connect_result(data, ctx->r_ip, SOCKERRNO); } + ctx->sock_connected = TRUE; +#endif set_local_ip(cf, data); CURL_TRC_CF(data, cf, "%s socket %" CURL_FORMAT_SOCKET_T " connected: [%s:%d] -> [%s:%d]", diff --git a/Utilities/cmcurl/lib/cf-socket.h b/Utilities/cmcurl/lib/cf-socket.h index 1d40df7..87e0f30 100644 --- a/Utilities/cmcurl/lib/cf-socket.h +++ b/Utilities/cmcurl/lib/cf-socket.h @@ -34,23 +34,6 @@ struct Curl_easy; struct connectdata; struct Curl_sockaddr_ex; -#ifndef SIZEOF_CURL_SOCKET_T -/* configure and cmake check and set the define */ -# ifdef _WIN64 -# define SIZEOF_CURL_SOCKET_T 8 -# else -/* default guess */ -# define SIZEOF_CURL_SOCKET_T 4 -# endif -#endif - -#if SIZEOF_CURL_SOCKET_T < 8 -# define CURL_FORMAT_SOCKET_T "d" -#else -# define CURL_FORMAT_SOCKET_T "qd" -#endif - - /* * The Curl_sockaddr_ex structure is basically libcurl's external API * curl_sockaddr structure with enough space available to directly hold any diff --git a/Utilities/cmcurl/lib/cfilters.c b/Utilities/cmcurl/lib/cfilters.c index e78ecd7..823e90c 100644 --- a/Utilities/cmcurl/lib/cfilters.c +++ b/Utilities/cmcurl/lib/cfilters.c @@ -760,25 +760,11 @@ static void ps_add(struct Curl_easy *data, struct easy_pollset *ps, void Curl_pollset_add_socks(struct Curl_easy *data, struct easy_pollset *ps, int (*get_socks_cb)(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks)) { curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; int bitmap; - DEBUGASSERT(data->conn); - bitmap = get_socks_cb(data, data->conn, socks); - ps_add(data, ps, bitmap, socks); -} - -void Curl_pollset_add_socks2(struct Curl_easy *data, - struct easy_pollset *ps, - int (*get_socks_cb)(struct Curl_easy *data, - curl_socket_t *socks)) -{ - curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; - int bitmap; - bitmap = get_socks_cb(data, socks); ps_add(data, ps, bitmap, socks); } diff --git a/Utilities/cmcurl/lib/cfilters.h b/Utilities/cmcurl/lib/cfilters.h index 09a3f16..f838429 100644 --- a/Utilities/cmcurl/lib/cfilters.h +++ b/Utilities/cmcurl/lib/cfilters.h @@ -530,12 +530,7 @@ void Curl_pollset_set(struct Curl_easy *data, void Curl_pollset_add_socks(struct Curl_easy *data, struct easy_pollset *ps, int (*get_socks_cb)(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks)); -void Curl_pollset_add_socks2(struct Curl_easy *data, - struct easy_pollset *ps, - int (*get_socks_cb)(struct Curl_easy *data, - curl_socket_t *socks)); /** * Check if the pollset, as is, wants to read and/or write regarding diff --git a/Utilities/cmcurl/lib/connect.c b/Utilities/cmcurl/lib/connect.c index ec5ab71..45743e9 100644 --- a/Utilities/cmcurl/lib/connect.c +++ b/Utilities/cmcurl/lib/connect.c @@ -93,25 +93,17 @@ * transfer/connection. If the value is 0, there's no timeout (ie there's * infinite time left). If the value is negative, the timeout time has already * elapsed. - * - * If 'nowp' is non-NULL, it points to the current time. - * 'duringconnect' is FALSE if not during a connect, as then of course the - * connect timeout is not taken into account! - * + * @param data the transfer to check on + * @param nowp timestamp to use for calculdation, NULL to use Curl_now() + * @param duringconnect TRUE iff connect timeout is also taken into account. * @unittest: 1303 */ - -#define TIMEOUT_CONNECT 1 -#define TIMEOUT_MAXTIME 2 - timediff_t Curl_timeleft(struct Curl_easy *data, struct curltime *nowp, bool duringconnect) { - unsigned int timeout_set = 0; - timediff_t connect_timeout_ms = 0; - timediff_t maxtime_timeout_ms = 0; - timediff_t timeout_ms = 0; + timediff_t timeleft_ms = 0; + timediff_t ctimeleft_ms = 0; struct curltime now; /* The duration of a connect and the total transfer are calculated from two @@ -119,43 +111,35 @@ timediff_t Curl_timeleft(struct Curl_easy *data, before the connect timeout expires and we must acknowledge whichever timeout that is reached first. The total timeout is set per entire operation, while the connect timeout is set per connect. */ - - if(data->set.timeout > 0) { - timeout_set = TIMEOUT_MAXTIME; - maxtime_timeout_ms = data->set.timeout; - } - if(duringconnect) { - timeout_set |= TIMEOUT_CONNECT; - connect_timeout_ms = (data->set.connecttimeout > 0) ? - data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; - } - if(!timeout_set) - /* no timeout */ - return 0; + if(data->set.timeout <= 0 && !duringconnect) + return 0; /* no timeout in place or checked, return "no limit" */ if(!nowp) { now = Curl_now(); nowp = &now; } - if(timeout_set & TIMEOUT_MAXTIME) { - maxtime_timeout_ms -= Curl_timediff(*nowp, data->progress.t_startop); - timeout_ms = maxtime_timeout_ms; + if(data->set.timeout > 0) { + timeleft_ms = data->set.timeout - + Curl_timediff(*nowp, data->progress.t_startop); + if(!timeleft_ms) + timeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ + if(!duringconnect) + return timeleft_ms; /* no connect check, this is it */ } - if(timeout_set & TIMEOUT_CONNECT) { - connect_timeout_ms -= Curl_timediff(*nowp, data->progress.t_startsingle); - - if(!(timeout_set & TIMEOUT_MAXTIME) || - (connect_timeout_ms < maxtime_timeout_ms)) - timeout_ms = connect_timeout_ms; + if(duringconnect) { + timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ? + data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; + ctimeleft_ms = ctimeout_ms - + Curl_timediff(*nowp, data->progress.t_startsingle); + if(!ctimeleft_ms) + ctimeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ + if(!timeleft_ms) + return ctimeleft_ms; /* no general timeout, this is it */ } - - if(!timeout_ms) - /* avoid returning 0 as that means no timeout! */ - return -1; - - return timeout_ms; + /* return minimal time left or max amount already expired */ + return (ctimeleft_ms < timeleft_ms)? ctimeleft_ms : timeleft_ms; } /* Copies connection info into the transfer handle to make it available when @@ -405,7 +389,7 @@ static CURLcode eyeballer_new(struct eyeballer **pballer, struct eyeballer *baller; *pballer = NULL; - baller = calloc(1, sizeof(*baller) + 1000); + baller = calloc(1, sizeof(*baller)); if(!baller) return CURLE_OUT_OF_MEMORY; @@ -908,7 +892,7 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf, if(result) return result; ctx->state = SCFST_WAITING; - /* FALLTHROUGH */ + FALLTHROUGH(); case SCFST_WAITING: result = is_connected(cf, data, done); if(!result && *done) { diff --git a/Utilities/cmcurl/lib/content_encoding.c b/Utilities/cmcurl/lib/content_encoding.c index 082e0fa..06c161d 100644 --- a/Utilities/cmcurl/lib/content_encoding.c +++ b/Utilities/cmcurl/lib/content_encoding.c @@ -365,11 +365,14 @@ static CURLcode gzip_do_init(struct Curl_easy *data, #ifdef OLD_ZLIB_SUPPORT /* Skip over the gzip header */ -static enum { +typedef enum { GZIP_OK, GZIP_BAD, GZIP_UNDERFLOW -} check_gzip_header(unsigned char const *data, ssize_t len, ssize_t *headerlen) +} gzip_status; + +static gzip_status check_gzip_header(unsigned char const *data, ssize_t len, + ssize_t *headerlen) { int method, flags; const ssize_t totallen = len; @@ -832,8 +835,8 @@ static const struct Curl_cwtype identity_encoding = { }; -/* supported content encodings table. */ -static const struct Curl_cwtype * const encodings[] = { +/* supported general content decoders. */ +static const struct Curl_cwtype * const general_unencoders[] = { &identity_encoding, #ifdef HAVE_LIBZ &deflate_encoding, @@ -848,6 +851,13 @@ static const struct Curl_cwtype * const encodings[] = { NULL }; +/* supported content decoders only for transfer encodings */ +static const struct Curl_cwtype * const transfer_unencoders[] = { +#ifndef CURL_DISABLE_HTTP + &Curl_httpchunk_unencoder, +#endif + NULL +}; /* Provide a list of comma-separated names of supported encodings. */ @@ -861,7 +871,7 @@ void Curl_all_content_encodings(char *buf, size_t blen) DEBUGASSERT(blen); buf[0] = 0; - for(cep = encodings; *cep; cep++) { + for(cep = general_unencoders; *cep; cep++) { ce = *cep; if(!strcasecompare(ce->name, CONTENT_ENCODING_DEFAULT)) len += strlen(ce->name) + 2; @@ -873,7 +883,7 @@ void Curl_all_content_encodings(char *buf, size_t blen) } else if(blen > len) { char *p = buf; - for(cep = encodings; *cep; cep++) { + for(cep = general_unencoders; *cep; cep++) { ce = *cep; if(!strcasecompare(ce->name, CONTENT_ENCODING_DEFAULT)) { strcpy(p, ce->name); @@ -931,12 +941,23 @@ static const struct Curl_cwtype error_writer = { }; /* Find the content encoding by name. */ -static const struct Curl_cwtype *find_encoding(const char *name, - size_t len) +static const struct Curl_cwtype *find_unencode_writer(const char *name, + size_t len, + Curl_cwriter_phase phase) { const struct Curl_cwtype * const *cep; - for(cep = encodings; *cep; cep++) { + if(phase == CURL_CW_TRANSFER_DECODE) { + for(cep = transfer_unencoders; *cep; cep++) { + const struct Curl_cwtype *ce = *cep; + if((strncasecompare(name, ce->name, len) && !ce->name[len]) || + (ce->alias && strncasecompare(name, ce->alias, len) + && !ce->alias[len])) + return ce; + } + } + /* look among the general decoders */ + for(cep = general_unencoders; *cep; cep++) { const struct Curl_cwtype *ce = *cep; if((strncasecompare(name, ce->name, len) && !ce->name[len]) || (ce->alias && strncasecompare(name, ce->alias, len) && !ce->alias[len])) @@ -950,7 +971,6 @@ static const struct Curl_cwtype *find_encoding(const char *name, CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, const char *enclist, int is_transfer) { - struct SingleRequest *k = &data->req; Curl_cwriter_phase phase = is_transfer? CURL_CW_TRANSFER_DECODE:CURL_CW_CONTENT_DECODE; CURLcode result; @@ -969,16 +989,14 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, if(!ISSPACE(*enclist)) namelen = enclist - name + 1; - /* Special case: chunked encoding is handled at the reader level. */ - if(is_transfer && namelen == 7 && strncasecompare(name, "chunked", 7)) { - k->chunk = TRUE; /* chunks coming our way. */ - Curl_httpchunk_init(data); /* init our chunky engine. */ - } - else if(namelen) { + if(namelen) { const struct Curl_cwtype *cwt; struct Curl_cwriter *writer; - if((is_transfer && !data->set.http_transfer_encoding) || + /* if we skip the decoding in this phase, do not look further. + * Exception is "chunked" transfer-encoding which always must happen */ + if((is_transfer && !data->set.http_transfer_encoding && + (namelen != 7 || !strncasecompare(name, "chunked", 7))) || (!is_transfer && data->set.http_ce_skip)) { /* not requested, ignore */ return CURLE_OK; @@ -990,7 +1008,7 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, return CURLE_BAD_CONTENT_ENCODING; } - cwt = find_encoding(name, namelen); + cwt = find_unencode_writer(name, namelen, phase); if(!cwt) cwt = &error_writer; /* Defer error at use. */ diff --git a/Utilities/cmcurl/lib/cookie.c b/Utilities/cmcurl/lib/cookie.c index 9095cea..dc319b6 100644 --- a/Utilities/cmcurl/lib/cookie.c +++ b/Utilities/cmcurl/lib/cookie.c @@ -365,7 +365,7 @@ static void strstore(char **str, const char *newstr, size_t len) DEBUGASSERT(newstr); DEBUGASSERT(str); free(*str); - *str = Curl_strndup(newstr, len); + *str = Curl_memdup0(newstr, len); } /* @@ -821,10 +821,8 @@ Curl_cookie_add(struct Curl_easy *data, endslash = memrchr(path, '/', (queryp - path)); if(endslash) { size_t pathlen = (endslash-path + 1); /* include end slash */ - co->path = malloc(pathlen + 1); /* one extra for the zero byte */ + co->path = Curl_memdup0(path, pathlen); if(co->path) { - memcpy(co->path, path, pathlen); - co->path[pathlen] = 0; /* null-terminate */ co->spath = sanitize_cookie_path(co->path); if(!co->spath) badcookie = TRUE; /* out of memory bad */ @@ -927,7 +925,7 @@ Curl_cookie_add(struct Curl_easy *data, if(!co->spath) badcookie = TRUE; fields++; /* add a field and fall down to secure */ - /* FALLTHROUGH */ + FALLTHROUGH(); case 3: co->secure = FALSE; if(strcasecompare(ptr, "TRUE")) { @@ -1229,7 +1227,7 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, if(data) { FILE *fp = NULL; - if(file) { + if(file && *file) { if(!strcmp(file, "-")) fp = stdin; else { diff --git a/Utilities/cmcurl/lib/curl_config.h.cmake b/Utilities/cmcurl/lib/curl_config.h.cmake index a3c5af5..98b787f 100644 --- a/Utilities/cmcurl/lib/curl_config.h.cmake +++ b/Utilities/cmcurl/lib/curl_config.h.cmake @@ -291,12 +291,6 @@ /* if you have the GNU gssapi libraries */ #cmakedefine HAVE_GSSGNU 1 -/* if you have the Heimdal gssapi libraries */ -#cmakedefine HAVE_GSSHEIMDAL 1 - -/* if you have the MIT gssapi libraries */ -#cmakedefine HAVE_GSSMIT 1 - /* Define to 1 if you have the `idna_strerror' function. */ #cmakedefine HAVE_IDNA_STRERROR 1 @@ -594,18 +588,9 @@ /* Define to 1 if you have the <utime.h> header file. */ #cmakedefine HAVE_UTIME_H 1 -/* Define to 1 if you have the windows.h header file. */ -#cmakedefine HAVE_WINDOWS_H 1 - -/* Define to 1 if you have the winsock2.h header file. */ -#cmakedefine HAVE_WINSOCK2_H 1 - /* Define this symbol if your OS supports changing the contents of argv */ #cmakedefine HAVE_WRITABLE_ARGV 1 -/* Define to 1 if you have the ws2tcpip.h header file. */ -#cmakedefine HAVE_WS2TCPIP_H 1 - /* Define to 1 if you need the lber.h header file even with ldap.h */ #cmakedefine NEED_LBER_H 1 @@ -722,9 +707,6 @@ ${SIZEOF_TIME_T_CODE} /* if libPSL is in use */ #cmakedefine USE_LIBPSL 1 -/* If you want to build curl with the built-in manual */ -#cmakedefine USE_MANUAL 1 - /* if you want to use OpenLDAP code instead of legacy ldap implementation */ #cmakedefine USE_OPENLDAP 1 diff --git a/Utilities/cmcurl/lib/curl_ntlm_wb.c b/Utilities/cmcurl/lib/curl_ntlm_wb.c index b087a37..0c7892a 100644 --- a/Utilities/cmcurl/lib/curl_ntlm_wb.c +++ b/Utilities/cmcurl/lib/curl_ntlm_wb.c @@ -266,7 +266,7 @@ static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm, size_t len_in = strlen(input), len_out = 0; struct dynbuf b; char *ptr = NULL; - unsigned char *buf = (unsigned char *)data->state.buffer; + usigned char buf[1024] Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE); while(len_in > 0) { @@ -284,7 +284,7 @@ static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm, /* Read one line */ while(1) { ssize_t size = - wakeup_read(ntlm->ntlm_auth_hlpr_socket, buf, data->set.buffer_size); + wakeup_read(ntlm->ntlm_auth_hlpr_socket, buf, sizeof(buf)); if(size == -1) { if(errno == EINTR) continue; @@ -481,7 +481,7 @@ CURLcode Curl_output_ntlm_wb(struct Curl_easy *data, struct connectdata *conn, /* connection is already authenticated, * don't send a header in future requests */ *state = NTLMSTATE_LAST; - /* FALLTHROUGH */ + FALLTHROUGH(); case NTLMSTATE_LAST: Curl_safefree(*allocuserpwd); authp->done = TRUE; diff --git a/Utilities/cmcurl/lib/curl_printf.h b/Utilities/cmcurl/lib/curl_printf.h index 46ef344..c2457d2 100644 --- a/Utilities/cmcurl/lib/curl_printf.h +++ b/Utilities/cmcurl/lib/curl_printf.h @@ -31,6 +31,10 @@ #include <curl/mprintf.h> +#define MERR_OK 0 +#define MERR_MEM 1 +#define MERR_TOO_LARGE 2 + # undef printf # undef fprintf # undef msnprintf diff --git a/Utilities/cmcurl/lib/curl_rtmp.c b/Utilities/cmcurl/lib/curl_rtmp.c index f7cf54e..147b12a 100644 --- a/Utilities/cmcurl/lib/curl_rtmp.c +++ b/Utilities/cmcurl/lib/curl_rtmp.c @@ -79,7 +79,7 @@ const struct Curl_handler Curl_handler_rtmp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ @@ -102,7 +102,7 @@ const struct Curl_handler Curl_handler_rtmpt = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ @@ -125,7 +125,7 @@ const struct Curl_handler Curl_handler_rtmpe = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ @@ -148,7 +148,7 @@ const struct Curl_handler Curl_handler_rtmpte = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ @@ -171,7 +171,7 @@ const struct Curl_handler Curl_handler_rtmps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ @@ -194,7 +194,7 @@ const struct Curl_handler Curl_handler_rtmpts = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ diff --git a/Utilities/cmcurl/lib/curl_sasl.c b/Utilities/cmcurl/lib/curl_sasl.c index 78ad298..66639cb 100644 --- a/Utilities/cmcurl/lib/curl_sasl.c +++ b/Utilities/cmcurl/lib/curl_sasl.c @@ -205,18 +205,23 @@ void Curl_sasl_init(struct SASL *sasl, struct Curl_easy *data, sasl->force_ir = FALSE; /* Respect external option */ if(auth != CURLAUTH_BASIC) { - sasl->resetprefs = FALSE; - sasl->prefmech = SASL_AUTH_NONE; + unsigned short mechs = SASL_AUTH_NONE; + + /* If some usable http authentication options have been set, determine + new defaults from them. */ if(auth & CURLAUTH_BASIC) - sasl->prefmech |= SASL_MECH_PLAIN | SASL_MECH_LOGIN; + mechs |= SASL_MECH_PLAIN | SASL_MECH_LOGIN; if(auth & CURLAUTH_DIGEST) - sasl->prefmech |= SASL_MECH_DIGEST_MD5; + mechs |= SASL_MECH_DIGEST_MD5; if(auth & CURLAUTH_NTLM) - sasl->prefmech |= SASL_MECH_NTLM; + mechs |= SASL_MECH_NTLM; if(auth & CURLAUTH_BEARER) - sasl->prefmech |= SASL_MECH_OAUTHBEARER | SASL_MECH_XOAUTH2; + mechs |= SASL_MECH_OAUTHBEARER | SASL_MECH_XOAUTH2; if(auth & CURLAUTH_GSSAPI) - sasl->prefmech |= SASL_MECH_GSSAPI; + mechs |= SASL_MECH_GSSAPI; + + if(mechs != SASL_AUTH_NONE) + sasl->prefmech = mechs; } } diff --git a/Utilities/cmcurl/lib/curl_setup.h b/Utilities/cmcurl/lib/curl_setup.h index 7fe6397..f856f07 100644 --- a/Utilities/cmcurl/lib/curl_setup.h +++ b/Utilities/cmcurl/lib/curl_setup.h @@ -28,6 +28,13 @@ #define CURL_NO_OLDIES #endif +/* FIXME: Delete this once the warnings have been fixed. */ +#if !defined(CURL_WARN_SIGN_CONVERSION) +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif +#endif + /* Set default _WIN32_WINNT */ #ifdef __MINGW32__ #include <_mingw.h> @@ -257,12 +264,39 @@ * Windows setup file includes some system headers. */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # include "setup-win32.h" #endif #include <curl/system.h> +/* curl uses its own printf() function internally. It understands the GNU + * format. Use this format, so that is matches the GNU format attribute we + * use with the mingw compiler, allowing it to verify them at compile-time. + */ +#ifdef __MINGW32__ +# undef CURL_FORMAT_CURL_OFF_T +# undef CURL_FORMAT_CURL_OFF_TU +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +#endif + +/* based on logic in "curl/mprintf.h" */ + +#if (defined(__GNUC__) || defined(__clang__)) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(CURL_NO_FMT_CHECKS) +#if defined(__MINGW32__) && !defined(__clang__) +#define CURL_PRINTF(fmt, arg) \ + __attribute__((format(gnu_printf, fmt, arg))) +#else +#define CURL_PRINTF(fmt, arg) \ + __attribute__((format(__printf__, fmt, arg))) +#endif +#else +#define CURL_PRINTF(fmt, arg) +#endif + /* * Use getaddrinfo to resolve the IPv4 address literal. If the current network * interface doesn't support IPv4, but supports IPv6, NAT64, and DNS64, @@ -423,6 +457,24 @@ #define SIZEOF_TIME_T 4 #endif +#ifndef SIZEOF_CURL_SOCKET_T +/* configure and cmake check and set the define */ +# ifdef _WIN64 +# define SIZEOF_CURL_SOCKET_T 8 +# else +/* default guess */ +# define SIZEOF_CURL_SOCKET_T 4 +# endif +#endif + +#if SIZEOF_CURL_SOCKET_T < 8 +# define CURL_FORMAT_SOCKET_T "d" +#elif defined(__MINGW32__) +# define CURL_FORMAT_SOCKET_T "zd" +#else +# define CURL_FORMAT_SOCKET_T "qd" +#endif + /* * Default sizeof(off_t) in case it hasn't been defined in config file. */ @@ -663,6 +715,17 @@ #endif #endif +/* fallthrough attribute */ + +#if !defined(FALLTHROUGH) +#if (defined(__GNUC__) && __GNUC__ >= 7) || \ + (defined(__clang__) && __clang_major__ >= 10) +# define FALLTHROUGH() __attribute__((fallthrough)) +#else +# define FALLTHROUGH() do {} while (0) +#endif +#endif + /* * Include macros and defines that should only be processed once. */ @@ -684,10 +747,7 @@ */ #if defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H) -# if defined(SOCKET) || \ - defined(USE_WINSOCK) || \ - defined(HAVE_WINSOCK2_H) || \ - defined(HAVE_WS2TCPIP_H) +# if defined(SOCKET) || defined(USE_WINSOCK) # error "WinSock and lwIP TCP/IP stack definitions shall not coexist!" # endif #endif @@ -782,7 +842,13 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, #endif #if (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ + (defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3)) || \ defined(USE_QUICHE) || defined(USE_MSH3) + +#ifdef CURL_WITH_MULTI_SSL +#error "Multi-SSL combined with QUIC is not supported" +#endif + #define ENABLE_QUIC #define USE_HTTP3 #endif diff --git a/Utilities/cmcurl/lib/curl_trc.c b/Utilities/cmcurl/lib/curl_trc.c index 0ebe40b..b8dccc4 100644 --- a/Utilities/cmcurl/lib/curl_trc.c +++ b/Utilities/cmcurl/lib/curl_trc.c @@ -157,8 +157,10 @@ static struct Curl_cftype *cf_types[] = { #endif #ifdef USE_SSL &Curl_cft_ssl, +#ifndef CURL_DISABLE_PROXY &Curl_cft_ssl_proxy, #endif +#endif #if !defined(CURL_DISABLE_PROXY) #if !defined(CURL_DISABLE_HTTP) &Curl_cft_h1_proxy, diff --git a/Utilities/cmcurl/lib/curl_trc.h b/Utilities/cmcurl/lib/curl_trc.h index ade9108..3a5387a 100644 --- a/Utilities/cmcurl/lib/curl_trc.h +++ b/Utilities/cmcurl/lib/curl_trc.h @@ -58,14 +58,7 @@ void Curl_debug(struct Curl_easy *data, curl_infotype type, * Output a failure message on registered callbacks for transfer. */ void Curl_failf(struct Curl_easy *data, -#if defined(__GNUC__) && !defined(printf) && \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__MINGW32__) - const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -#else - const char *fmt, ...); -#endif + const char *fmt, ...) CURL_PRINTF(2, 3); #define failf Curl_failf @@ -102,26 +95,14 @@ void Curl_failf(struct Curl_easy *data, * Output an informational message when transfer's verbose logging is enabled. */ void Curl_infof(struct Curl_easy *data, -#if defined(__GNUC__) && !defined(printf) && defined(CURL_HAVE_C99) && \ - !defined(__MINGW32__) - const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -#else - const char *fmt, ...); -#endif + const char *fmt, ...) CURL_PRINTF(2, 3); /** * Output an informational message when both transfer's verbose logging * and connection filters verbose logging are enabled. */ void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf, -#if defined(__GNUC__) && !defined(printf) && defined(CURL_HAVE_C99) && \ - !defined(__MINGW32__) - const char *fmt, ...) - __attribute__((format(printf, 3, 4))); -#else - const char *fmt, ...); -#endif + const char *fmt, ...) CURL_PRINTF(3, 4); #else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ /* All informational messages are not compiled in for size savings */ diff --git a/Utilities/cmcurl/lib/dict.c b/Utilities/cmcurl/lib/dict.c index 3172b38..3239848 100644 --- a/Utilities/cmcurl/lib/dict.c +++ b/Utilities/cmcurl/lib/dict.c @@ -89,7 +89,7 @@ const struct Curl_handler Curl_handler_dict = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_DICT, /* defport */ @@ -123,6 +123,9 @@ static char *unescape_word(const char *input) /* sendf() sends formatted data to the server */ static CURLcode sendf(curl_socket_t sockfd, struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(3, 4); + +static CURLcode sendf(curl_socket_t sockfd, struct Curl_easy *data, const char *fmt, ...) { ssize_t bytes_written; diff --git a/Utilities/cmcurl/lib/doh.c b/Utilities/cmcurl/lib/doh.c index 1d928e9..ef32d50 100644 --- a/Utilities/cmcurl/lib/doh.c +++ b/Utilities/cmcurl/lib/doh.c @@ -218,7 +218,6 @@ static CURLcode dohprobe(struct Curl_easy *data, struct curl_slist *headers) { struct Curl_easy *doh = NULL; - char *nurl = NULL; CURLcode result = CURLE_OK; timediff_t timeout_ms; DOHcode d = doh_encode(host, dnstype, p->dohbuffer, sizeof(p->dohbuffer), @@ -351,11 +350,9 @@ static CURLcode dohprobe(struct Curl_easy *data, } else goto error; - free(nurl); return CURLE_OK; error: - free(nurl); Curl_close(&doh); return result; } @@ -447,7 +444,7 @@ static DOHcode skipqname(const unsigned char *doh, size_t dohlen, return DOH_DNS_BAD_LABEL; if(dohlen < (*indexp + 1 + length)) return DOH_DNS_OUT_OF_RANGE; - *indexp += 1 + length; + *indexp += (unsigned int)(1 + length); } while(length); return DOH_OK; } @@ -459,14 +456,15 @@ static unsigned short get16bit(const unsigned char *doh, int index) static unsigned int get32bit(const unsigned char *doh, int index) { - /* make clang and gcc optimize this to bswap by incrementing - the pointer first. */ - doh += index; - - /* avoid undefined behavior by casting to unsigned before shifting - 24 bits, possibly into the sign bit. codegen is same, but - ub sanitizer won't be upset */ - return ( (unsigned)doh[0] << 24) | (doh[1] << 16) |(doh[2] << 8) | doh[3]; + /* make clang and gcc optimize this to bswap by incrementing + the pointer first. */ + doh += index; + + /* avoid undefined behavior by casting to unsigned before shifting + 24 bits, possibly into the sign bit. codegen is same, but + ub sanitizer won't be upset */ + return ((unsigned)doh[0] << 24) | ((unsigned)doh[1] << 16) | + ((unsigned)doh[2] << 8) | doh[3]; } static DOHcode store_a(const unsigned char *doh, int index, struct dohentry *d) @@ -904,7 +902,6 @@ UNITTEST void de_cleanup(struct dohentry *d) CURLcode Curl_doh_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dnsp) { - struct connectdata *conn = data->conn; CURLcode result; struct dohdata *dohp = data->req.doh; *dnsp = NULL; /* defaults to no response */ @@ -913,7 +910,7 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, if(!dohp->probe[DOH_PROBE_SLOT_IPADDR_V4].easy && !dohp->probe[DOH_PROBE_SLOT_IPADDR_V6].easy) { - failf(data, "Could not DoH-resolve: %s", conn->resolve_async.hostname); + failf(data, "Could not DoH-resolve: %s", data->state.async.hostname); return CONN_IS_PROXIED(data->conn)?CURLE_COULDNT_RESOLVE_PROXY: CURLE_COULDNT_RESOLVE_HOST; } @@ -976,7 +973,7 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data, Curl_freeaddrinfo(ai); } else { - conn->resolve_async.dns = dns; + data->state.async.dns = dns; *dnsp = dns; result = CURLE_OK; /* address resolution OK */ } diff --git a/Utilities/cmcurl/lib/dynbuf.c b/Utilities/cmcurl/lib/dynbuf.c index 2973d8d..a4c599d 100644 --- a/Utilities/cmcurl/lib/dynbuf.c +++ b/Utilities/cmcurl/lib/dynbuf.c @@ -81,7 +81,7 @@ static CURLcode dyn_nappend(struct dynbuf *s, if(fit > s->toobig) { Curl_dyn_free(s); - return CURLE_OUT_OF_MEMORY; + return CURLE_TOO_LARGE; } else if(!a) { DEBUGASSERT(!indx); @@ -199,6 +199,9 @@ CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) if(!rc) return CURLE_OK; + else if(rc == MERR_TOO_LARGE) + return CURLE_TOO_LARGE; + return CURLE_OUT_OF_MEMORY; #else char *str; str = vaprintf(fmt, ap); /* this allocs a new string to append */ @@ -210,8 +213,8 @@ CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) } /* If we failed, we cleanup the whole buffer and return error */ Curl_dyn_free(s); + return CURLE_OK; #endif - return CURLE_OUT_OF_MEMORY; } /* diff --git a/Utilities/cmcurl/lib/dynbuf.h b/Utilities/cmcurl/lib/dynbuf.h index 31a9130..7dbaab8 100644 --- a/Utilities/cmcurl/lib/dynbuf.h +++ b/Utilities/cmcurl/lib/dynbuf.h @@ -61,9 +61,9 @@ CURLcode Curl_dyn_addn(struct dynbuf *s, const void *mem, size_t len) CURLcode Curl_dyn_add(struct dynbuf *s, const char *str) WARN_UNUSED_RESULT; CURLcode Curl_dyn_addf(struct dynbuf *s, const char *fmt, ...) - WARN_UNUSED_RESULT; + WARN_UNUSED_RESULT CURL_PRINTF(2, 3); CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) - WARN_UNUSED_RESULT; + WARN_UNUSED_RESULT CURL_PRINTF(2, 0); void Curl_dyn_reset(struct dynbuf *s); CURLcode Curl_dyn_tail(struct dynbuf *s, size_t trail); CURLcode Curl_dyn_setlen(struct dynbuf *s, size_t set); diff --git a/Utilities/cmcurl/lib/easy.c b/Utilities/cmcurl/lib/easy.c index 322d1a4..067b6d7 100644 --- a/Utilities/cmcurl/lib/easy.c +++ b/Utilities/cmcurl/lib/easy.c @@ -480,13 +480,15 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ ev->list = nxt; free(m); m = nxt; - infof(easy, "socket cb: socket %d REMOVED", s); + infof(easy, "socket cb: socket %" CURL_FORMAT_SOCKET_T + " REMOVED", s); } else { /* The socket 's' is already being monitored, update the activity mask. Convert from libcurl bitmask to the poll one. */ m->socket.events = socketcb2poll(what); - infof(easy, "socket cb: socket %d UPDATED as %s%s", s, + infof(easy, "socket cb: socket %" CURL_FORMAT_SOCKET_T + " UPDATED as %s%s", s, (what&CURL_POLL_IN)?"IN":"", (what&CURL_POLL_OUT)?"OUT":""); } @@ -510,7 +512,8 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ m->socket.events = socketcb2poll(what); m->socket.revents = 0; ev->list = m; - infof(easy, "socket cb: socket %d ADDED as %s%s", s, + infof(easy, "socket cb: socket %" CURL_FORMAT_SOCKET_T + " ADDED as %s%s", s, (what&CURL_POLL_IN)?"IN":"", (what&CURL_POLL_OUT)?"OUT":""); } @@ -599,8 +602,9 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) if(fds[i].revents) { /* socket activity, tell libcurl */ int act = poll2cselect(fds[i].revents); /* convert */ - infof(multi->easyp, "call curl_multi_socket_action(socket %d)", - fds[i].fd); + infof(multi->easyp, + "call curl_multi_socket_action(socket " + "%" CURL_FORMAT_SOCKET_T ")", fds[i].fd); mcode = curl_multi_socket_action(multi, fds[i].fd, act, &ev->running_handles); } @@ -684,9 +688,9 @@ static CURLcode easy_transfer(struct Curl_multi *multi) /* Make sure to return some kind of error if there was a multi problem */ if(mcode) { result = (mcode == CURLM_OUT_OF_MEMORY) ? CURLE_OUT_OF_MEMORY : - /* The other multi errors should never happen, so return - something suitably generic */ - CURLE_BAD_FUNCTION_ARGUMENT; + /* The other multi errors should never happen, so return + something suitably generic */ + CURLE_BAD_FUNCTION_ARGUMENT; } return result; @@ -973,6 +977,36 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) } #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 */ + Curl_initinfo(outcurl); outcurl->magic = CURLEASY_MAGIC_NUMBER; @@ -1111,7 +1145,7 @@ CURLcode curl_easy_pause(struct Curl_easy *data, int action) if(!data->state.tempcount) /* if not pausing again, force a recv/send check of this connection as the data might've been read off the socket already */ - data->conn->cselect_bits = CURL_CSELECT_IN | CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_IN | CURL_CSELECT_OUT; if(data->multi) { if(Curl_update_timer(data->multi)) return CURLE_ABORTED_BY_CALLBACK; diff --git a/Utilities/cmcurl/lib/easyoptions.c b/Utilities/cmcurl/lib/easyoptions.c index e69c658..da4c611 100644 --- a/Utilities/cmcurl/lib/easyoptions.c +++ b/Utilities/cmcurl/lib/easyoptions.c @@ -274,6 +274,8 @@ struct curl_easyoption Curl_easyopts[] = { {"SEEKFUNCTION", CURLOPT_SEEKFUNCTION, CURLOT_FUNCTION, 0}, {"SERVER_RESPONSE_TIMEOUT", CURLOPT_SERVER_RESPONSE_TIMEOUT, CURLOT_LONG, 0}, + {"SERVER_RESPONSE_TIMEOUT_MS", CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, + CURLOT_LONG, 0}, {"SERVICE_NAME", CURLOPT_SERVICE_NAME, CURLOT_STRING, 0}, {"SHARE", CURLOPT_SHARE, CURLOT_OBJECT, 0}, {"SOCKOPTDATA", CURLOPT_SOCKOPTDATA, CURLOT_CBPTR, 0}, @@ -373,6 +375,6 @@ struct curl_easyoption Curl_easyopts[] = { */ int Curl_easyopts_check(void) { - return ((CURLOPT_LASTENTRY%10000) != (323 + 1)); + return ((CURLOPT_LASTENTRY%10000) != (324 + 1)); } #endif diff --git a/Utilities/cmcurl/lib/file.c b/Utilities/cmcurl/lib/file.c index c985071..b7ce3a8 100644 --- a/Utilities/cmcurl/lib/file.c +++ b/Utilities/cmcurl/lib/file.c @@ -113,7 +113,7 @@ const struct Curl_handler Curl_handler_file = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ file_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ 0, /* defport */ @@ -290,16 +290,15 @@ static CURLcode file_upload(struct Curl_easy *data) int fd; int mode; CURLcode result = CURLE_OK; - char *buf = data->state.buffer; + char buffer[8*1024], *uphere_save; curl_off_t bytecount = 0; struct_stat file_stat; - const char *buf2; + const char *sendbuf; /* * Since FILE: doesn't do the full init, we need to provide some extra * assignments here. */ - data->req.upload_fromhere = buf; if(!dir) return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ @@ -338,11 +337,15 @@ static CURLcode file_upload(struct Curl_easy *data) data->state.resume_from = (curl_off_t)file_stat.st_size; } + /* Yikes! Curl_fillreadbuffer uses data->req.upload_fromhere to READ + * client data to! Please, someone fix... */ + uphere_save = data->req.upload_fromhere; while(!result) { size_t nread; ssize_t nwrite; size_t readcount; - result = Curl_fillreadbuffer(data, data->set.buffer_size, &readcount); + data->req.upload_fromhere = buffer; + result = Curl_fillreadbuffer(data, sizeof(buffer), &readcount); if(result) break; @@ -356,19 +359,19 @@ static CURLcode file_upload(struct Curl_easy *data) if((curl_off_t)nread <= data->state.resume_from) { data->state.resume_from -= nread; nread = 0; - buf2 = buf; + sendbuf = buffer; } else { - buf2 = buf + data->state.resume_from; + sendbuf = buffer + data->state.resume_from; nread -= (size_t)data->state.resume_from; data->state.resume_from = 0; } } else - buf2 = buf; + sendbuf = buffer; /* write the data to the target */ - nwrite = write(fd, buf2, nread); + nwrite = write(fd, sendbuf, nread); if((size_t)nwrite != nread) { result = CURLE_SEND_ERROR; break; @@ -387,6 +390,7 @@ static CURLcode file_upload(struct Curl_easy *data) result = CURLE_ABORTED_BY_CALLBACK; close(fd); + data->req.upload_fromhere = uphere_save; return result; } @@ -413,14 +417,11 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) curl_off_t expected_size = -1; bool size_known; bool fstated = FALSE; - char *buf = data->state.buffer; int fd; struct FILEPROTO *file; *done = TRUE; /* unconditionally */ - Curl_pgrsStartNow(data); - if(data->state.upload) return file_upload(data); @@ -543,21 +544,22 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) Curl_pgrsTime(data, TIMER_STARTTRANSFER); while(!result) { + char tmpbuf[8*1024]; ssize_t nread; /* Don't fill a whole buffer if we want less than all data */ size_t bytestoread; if(size_known) { - bytestoread = (expected_size < data->set.buffer_size) ? - curlx_sotouz(expected_size) : (size_t)data->set.buffer_size; + bytestoread = (expected_size < (curl_off_t)(sizeof(tmpbuf)-1)) ? + curlx_sotouz(expected_size) : (sizeof(tmpbuf)-1); } else - bytestoread = data->set.buffer_size-1; + bytestoread = sizeof(tmpbuf)-1; - nread = read(fd, buf, bytestoread); + nread = read(fd, tmpbuf, bytestoread); if(nread > 0) - buf[nread] = 0; + tmpbuf[nread] = 0; if(nread <= 0 || (size_known && (expected_size == 0))) break; @@ -565,7 +567,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done) if(size_known) expected_size -= nread; - result = Curl_client_write(data, CLIENTWRITE_BODY, buf, nread); + result = Curl_client_write(data, CLIENTWRITE_BODY, tmpbuf, nread); if(result) return result; diff --git a/Utilities/cmcurl/lib/formdata.c b/Utilities/cmcurl/lib/formdata.c index 05dc9b5..d6a1697 100644 --- a/Utilities/cmcurl/lib/formdata.c +++ b/Utilities/cmcurl/lib/formdata.c @@ -277,7 +277,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, case CURLFORM_PTRNAME: current_form->flags |= HTTPPOST_PTRNAME; /* fall through */ - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLFORM_COPYNAME: if(current_form->name) return_value = CURL_FORMADD_OPTION_TWICE; @@ -303,7 +303,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, */ case CURLFORM_PTRCONTENTS: current_form->flags |= HTTPPOST_PTRCONTENTS; - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLFORM_COPYCONTENTS: if(current_form->value) return_value = CURL_FORMADD_OPTION_TWICE; @@ -603,7 +603,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, 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_strndup(form->name, form->namelength? + form->name = Curl_memdup0(form->name, form->namelength? form->namelength: strlen(form->name)); } @@ -779,11 +779,9 @@ static CURLcode setname(curl_mimepart *part, const char *name, size_t len) if(!name || !len) return curl_mime_name(part, name); - zname = malloc(len + 1); + zname = Curl_memdup0(name, len); if(!zname) return CURLE_OUT_OF_MEMORY; - memcpy(zname, name, len); - zname[len] = '\0'; res = curl_mime_name(part, zname); free(zname); return res; diff --git a/Utilities/cmcurl/lib/ftp.c b/Utilities/cmcurl/lib/ftp.c index 28e8ca4..58d6e1d 100644 --- a/Utilities/cmcurl/lib/ftp.c +++ b/Utilities/cmcurl/lib/ftp.c @@ -72,6 +72,7 @@ #include "warnless.h" #include "http_proxy.h" #include "socks.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -167,7 +168,7 @@ const struct Curl_handler Curl_handler_ftp = { ftp_domore_getsock, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_FTP, /* defport */ @@ -198,7 +199,7 @@ const struct Curl_handler Curl_handler_ftps = { ftp_domore_getsock, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_FTPS, /* defport */ @@ -362,10 +363,11 @@ static CURLcode ReceivedServerConnect(struct Curl_easy *data, bool *received) curl_socket_t data_sock = conn->sock[SECONDARYSOCKET]; struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; - int result; + int socketstate = 0; timediff_t timeout_ms; ssize_t nread; int ftpcode; + bool response = FALSE; *received = FALSE; @@ -378,17 +380,21 @@ static CURLcode ReceivedServerConnect(struct Curl_easy *data, bool *received) } /* First check whether there is a cached response from server */ - if(pp->cache_size && pp->cache && pp->cache[0] > '3') { + if(Curl_dyn_len(&pp->recvbuf) && (*Curl_dyn_ptr(&pp->recvbuf) > '3')) { /* Data connection could not be established, let's return */ infof(data, "There is negative response in cache while serv connect"); (void)Curl_GetFTPResponse(data, &nread, &ftpcode); return CURLE_FTP_ACCEPT_FAILED; } - result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); + if(pp->overflow) + /* there is pending control data still in the buffer to read */ + response = TRUE; + else + socketstate = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); /* see if the connection request is already here */ - switch(result) { + switch(socketstate) { case -1: /* error */ /* let's die here */ failf(data, "Error while waiting for server connect"); @@ -396,23 +402,23 @@ static CURLcode ReceivedServerConnect(struct Curl_easy *data, bool *received) case 0: /* Server connect is not received yet */ break; /* loop */ default: - - if(result & CURL_CSELECT_IN2) { + if(socketstate & CURL_CSELECT_IN2) { infof(data, "Ready to accept data connection from server"); *received = TRUE; } - else if(result & CURL_CSELECT_IN) { - infof(data, "Ctrl conn has data while waiting for data conn"); - (void)Curl_GetFTPResponse(data, &nread, &ftpcode); - - if(ftpcode/100 > 3) - return CURLE_FTP_ACCEPT_FAILED; + else if(socketstate & CURL_CSELECT_IN) + response = TRUE; + break; + } + if(response) { + infof(data, "Ctrl conn has data while waiting for data conn"); + (void)Curl_GetFTPResponse(data, &nread, &ftpcode); - return CURLE_WEIRD_SERVER_REPLY; - } + if(ftpcode/100 > 3) + return CURLE_FTP_ACCEPT_FAILED; - break; - } /* switch() */ + return CURLE_WEIRD_SERVER_REPLY; + } return CURLE_OK; } @@ -553,7 +559,7 @@ static CURLcode ftp_readresp(struct Curl_easy *data, #ifdef HAVE_GSSAPI { struct connectdata *conn = data->conn; - char * const buf = data->state.buffer; + char * const buf = Curl_dyn_ptr(&data->conn->proto.ftpc.pp.recvbuf); /* handle the security-oriented responses 6xx ***/ switch(code) { @@ -659,7 +665,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, * */ - if(pp->cache && (cache_skip < 2)) { + if(Curl_dyn_len(&pp->recvbuf) && (cache_skip < 2)) { /* * There's a cache left since before. We then skipping the wait for * socket action, unless this is the same cache like the previous round @@ -687,7 +693,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, if(result) break; - if(!nread && pp->cache) + if(!nread && Curl_dyn_len(&pp->recvbuf)) /* bump cache skip counter as on repeated skips we must wait for more data */ cache_skip++; @@ -926,6 +932,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, bool possibly_non_local = TRUE; char buffer[STRERROR_LEN]; char *addr = NULL; + size_t addrlen = 0; + char ipstr[50]; /* Step 1, figure out what is requested, * accepted format : @@ -934,32 +942,17 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(data->set.str[STRING_FTPPORT] && (strlen(data->set.str[STRING_FTPPORT]) > 1)) { - -#ifdef ENABLE_IPV6 - size_t addrlen = INET6_ADDRSTRLEN > strlen(string_ftpport) ? - INET6_ADDRSTRLEN : strlen(string_ftpport); -#else - size_t addrlen = INET_ADDRSTRLEN > strlen(string_ftpport) ? - INET_ADDRSTRLEN : strlen(string_ftpport); -#endif - char *ip_start = string_ftpport; char *ip_end = NULL; - char *port_start = NULL; - char *port_sep = NULL; - - addr = calloc(1, addrlen + 1); - if(!addr) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } #ifdef ENABLE_IPV6 if(*string_ftpport == '[') { /* [ipv6]:port(-range) */ - ip_start = string_ftpport + 1; - ip_end = strchr(string_ftpport, ']'); - if(ip_end) - strncpy(addr, ip_start, ip_end - ip_start); + char *ip_start = string_ftpport + 1; + ip_end = strchr(ip_start, ']'); + if(ip_end) { + addrlen = ip_end - ip_start; + addr = ip_start; + } } else #endif @@ -969,28 +962,27 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, } else { ip_end = strchr(string_ftpport, ':'); + addr = string_ftpport; if(ip_end) { /* either ipv6 or (ipv4|domain|interface):port(-range) */ + addrlen = ip_end - string_ftpport; #ifdef ENABLE_IPV6 if(Curl_inet_pton(AF_INET6, string_ftpport, &sa6->sin6_addr) == 1) { /* ipv6 */ port_min = port_max = 0; - strcpy(addr, string_ftpport); ip_end = NULL; /* this got no port ! */ } - else #endif - /* (ipv4|domain|interface):port(-range) */ - strncpy(addr, string_ftpport, ip_end - ip_start); } else /* ipv4|interface */ - strcpy(addr, string_ftpport); + addrlen = strlen(string_ftpport); } /* parse the port */ if(ip_end) { - port_start = strchr(ip_end, ':'); + char *port_sep = NULL; + char *port_start = strchr(ip_end, ':'); if(port_start) { port_min = curlx_ultous(strtoul(port_start + 1, NULL, 10)); port_sep = strchr(port_start, '-'); @@ -1011,22 +1003,29 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(port_min > port_max) port_min = port_max = 0; - if(*addr != '\0') { + if(addrlen) { + DEBUGASSERT(addr); + if(addrlen >= sizeof(ipstr)) + goto out; + memcpy(ipstr, addr, addrlen); + ipstr[addrlen] = 0; + /* attempt to get the address of the given interface name */ switch(Curl_if2ip(conn->remote_addr->family, #ifdef ENABLE_IPV6 Curl_ipv6_scope(&conn->remote_addr->sa_addr), conn->scope_id, #endif - addr, hbuf, sizeof(hbuf))) { + ipstr, hbuf, sizeof(hbuf))) { case IF2IP_NOT_FOUND: /* not an interface, use the given string as host name instead */ - host = addr; + host = ipstr; break; case IF2IP_AF_NOT_SUPPORTED: goto out; case IF2IP_FOUND: host = hbuf; /* use the hbuf for host name */ + break; } } else @@ -1266,7 +1265,6 @@ out: } if(portsock != CURL_SOCKET_BAD) Curl_socket_close(data, conn, portsock); - free(addr); return result; } @@ -1589,13 +1587,14 @@ static CURLcode ftp_state_ul_setup(struct Curl_easy *data, } /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : + (data->state.resume_from - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, readthisamountnow, + data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); passed += actuallyread; @@ -1828,7 +1827,9 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, struct Curl_dns_entry *addr = NULL; enum resolve_t rc; unsigned short connectport; /* the local port connect() should use! */ - char *str = &data->state.buffer[4]; /* start on the first letter */ + struct pingpong *pp = &ftpc->pp; + char *str = + Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first letter */ /* if we come here again, make sure the former name is cleared */ Curl_safefree(ftpc->newhost); @@ -2106,8 +2107,9 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the last .sss part is optional and means fractions of a second */ int year, month, day, hour, minute, second; - if(ftp_213_date(&data->state.buffer[4], - &year, &month, &day, &hour, &minute, &second)) { + struct pingpong *pp = &ftpc->pp; + char *resp = Curl_dyn_ptr(&pp->recvbuf) + 4; + if(ftp_213_date(resp, &year, &month, &day, &hour, &minute, &second)) { /* we have a time, reformat it */ char timebuf[24]; msnprintf(timebuf, sizeof(timebuf), @@ -2318,7 +2320,8 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, { CURLcode result = CURLE_OK; curl_off_t filesize = -1; - char *buf = data->state.buffer; + char *buf = Curl_dyn_ptr(&data->conn->proto.ftpc.pp.recvbuf); + size_t len = data->conn->proto.ftpc.pp.nfinal; /* get the size from the ascii string: */ if(ftpcode == 213) { @@ -2326,13 +2329,13 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, for all the digits at the end of the response and parse only those as a number. */ char *start = &buf[4]; - char *fdigit = strchr(start, '\r'); + char *fdigit = memchr(start, '\r', len); if(fdigit) { - do + fdigit--; + if(*fdigit == '\n') + fdigit--; + while(ISDIGIT(fdigit[-1]) && (fdigit > start)) fdigit--; - while(ISDIGIT(*fdigit) && (fdigit > start)); - if(!ISDIGIT(*fdigit)) - fdigit++; } else fdigit = start; @@ -2501,7 +2504,7 @@ static CURLcode ftp_state_get_resp(struct Curl_easy *data, * * Example D above makes this parsing a little tricky */ char *bytes; - char *buf = data->state.buffer; + char *buf = Curl_dyn_ptr(&conn->proto.ftpc.pp.recvbuf); bytes = strstr(buf, " bytes"); if(bytes) { long in = (long)(--bytes-buf); @@ -2770,7 +2773,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, case FTP_AUTH: /* we have gotten the response to a previous AUTH command */ - if(pp->cache_size) + if(pp->overflow) return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */ /* RFC2228 (page 5) says: @@ -2868,14 +2871,11 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, case FTP_PWD: if(ftpcode == 257) { - char *ptr = &data->state.buffer[4]; /* start on the first letter */ - const size_t buf_size = data->set.buffer_size; - char *dir; + char *ptr = Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ bool entry_extracted = FALSE; - - dir = malloc(nread + 1); - if(!dir) - return CURLE_OUT_OF_MEMORY; + struct dynbuf out; + Curl_dyn_init(&out, 1000); /* Reply format is like 257<space>[rubbish]"<directory-name>"<space><commentary> and the @@ -2887,33 +2887,30 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, */ /* scan for the first double-quote for non-standard responses */ - while(ptr < &data->state.buffer[buf_size] - && *ptr != '\n' && *ptr != '\0' && *ptr != '"') + while(*ptr != '\n' && *ptr != '\0' && *ptr != '"') ptr++; if('\"' == *ptr) { /* it started good */ - char *store; - ptr++; - for(store = dir; *ptr;) { + for(ptr++; *ptr; ptr++) { if('\"' == *ptr) { if('\"' == ptr[1]) { /* "quote-doubling" */ - *store = ptr[1]; + result = Curl_dyn_addn(&out, &ptr[1], 1); ptr++; } else { /* end of path */ - entry_extracted = TRUE; + if(Curl_dyn_len(&out)) + entry_extracted = TRUE; break; /* get out of this loop */ } } else - *store = *ptr; - store++; - ptr++; + result = Curl_dyn_addn(&out, ptr, 1); + if(result) + return result; } - *store = '\0'; /* null-terminate */ } if(entry_extracted) { /* If the path name does not look like an absolute path (i.e.: it @@ -2927,6 +2924,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, The method used here is to check the server OS: we do it only if the path name looks strange to minimize overhead on other systems. */ + char *dir = Curl_dyn_ptr(&out); if(!ftpc->server_os && dir[0] != '/') { result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST"); @@ -2951,7 +2949,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, } else { /* couldn't get the path */ - free(dir); + Curl_dyn_free(&out); infof(data, "Failed to figure out path"); } } @@ -2961,25 +2959,23 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, case FTP_SYST: if(ftpcode == 215) { - char *ptr = &data->state.buffer[4]; /* start on the first letter */ + char *ptr = Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ char *os; - char *store; - - os = malloc(nread + 1); - if(!os) - return CURLE_OUT_OF_MEMORY; + char *start; /* Reply format is like 215<space><OS-name><space><commentary> */ while(*ptr == ' ') ptr++; - for(store = os; *ptr && *ptr != ' ';) - *store++ = *ptr++; - *store = '\0'; /* null-terminate */ + for(start = ptr; *ptr && *ptr != ' '; ptr++) + ; + os = Curl_memdup0(start, ptr - start); + if(!os) + return CURLE_OUT_OF_MEMORY; /* Check for special servers here. */ - if(strcasecompare(os, "OS/400")) { /* Force OS400 name format 1. */ result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1"); @@ -3131,7 +3127,6 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, break; case FTP_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ ftp_state(data, FTP_STOP); @@ -3206,8 +3201,7 @@ static CURLcode ftp_connect(struct Curl_easy *data, conn->bits.ftp_use_control_ssl = TRUE; } - Curl_pp_setup(pp); /* once per transfer */ - Curl_pp_init(data, pp); /* init the generic pingpong data */ + Curl_pp_init(pp); /* once per transfer */ /* When we connect, we start in the state where we await the 220 response */ @@ -3258,14 +3252,13 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, case CURLE_REMOTE_FILE_NOT_FOUND: case CURLE_WRITE_ERROR: /* the connection stays alive fine even though this happened */ - /* fall-through */ case CURLE_OK: /* doesn't affect the control connection's status */ if(!premature) break; /* until we cope better with prematurely ended requests, let them * fallback as if in complete failure */ - /* FALLTHROUGH */ + FALLTHROUGH(); default: /* by default, an error means the control connection is wedged and should not be used anymore */ ftpc->ctl_valid = FALSE; @@ -4178,13 +4171,12 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) return CURLE_OUT_OF_MEMORY; } - ftpc->dirs[0] = calloc(1, dirlen + 1); + ftpc->dirs[0] = Curl_memdup0(rawPath, dirlen); if(!ftpc->dirs[0]) { free(rawPath); return CURLE_OUT_OF_MEMORY; } - strncpy(ftpc->dirs[0], rawPath, dirlen); ftpc->dirdepth = 1; /* we consider it to be a single dir */ fileName = slashPos + 1; /* rest is file name */ } @@ -4223,12 +4215,11 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data) CWD requires a parameter and a non-existent parameter a) doesn't work on many servers and b) has no effect on the others. */ if(compLen > 0) { - char *comp = calloc(1, compLen + 1); + char *comp = Curl_memdup0(curPos, compLen); if(!comp) { free(rawPath); return CURLE_OUT_OF_MEMORY; } - strncpy(comp, curPos, compLen); ftpc->dirs[ftpc->dirdepth++] = comp; } curPos = slashPos + 1; diff --git a/Utilities/cmcurl/lib/getinfo.c b/Utilities/cmcurl/lib/getinfo.c index f1574e0..2f74629 100644 --- a/Utilities/cmcurl/lib/getinfo.c +++ b/Utilities/cmcurl/lib/getinfo.c @@ -409,6 +409,9 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, case CURLINFO_STARTTRANSFER_TIME_T: *param_offt = data->progress.t_starttransfer; break; + case CURLINFO_QUEUE_TIME_T: + *param_offt = data->progress.t_postqueue; + break; case CURLINFO_REDIRECT_TIME_T: *param_offt = data->progress.t_redirect; break; @@ -420,7 +423,7 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, break; case CURLINFO_CONN_ID: *param_offt = data->conn? - data->conn->connection_id : data->state.recent_conn_id; + data->conn->connection_id : data->state.recent_conn_id; break; default: return CURLE_UNKNOWN_OPTION; diff --git a/Utilities/cmcurl/lib/gopher.c b/Utilities/cmcurl/lib/gopher.c index 61e41b7..9ca0828 100644 --- a/Utilities/cmcurl/lib/gopher.c +++ b/Utilities/cmcurl/lib/gopher.c @@ -75,7 +75,7 @@ const struct Curl_handler Curl_handler_gopher = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_GOPHER, /* defport */ @@ -99,7 +99,7 @@ const struct Curl_handler Curl_handler_gophers = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_GOPHER, /* defport */ diff --git a/Utilities/cmcurl/lib/headers.c b/Utilities/cmcurl/lib/headers.c index 3ff4d5e..8a3264a 100644 --- a/Utilities/cmcurl/lib/headers.c +++ b/Utilities/cmcurl/lib/headers.c @@ -185,7 +185,7 @@ struct curl_header *curl_easy_nextheader(CURL *easy, } static CURLcode namevalue(char *header, size_t hlen, unsigned int type, - char **name, char **value) + char **name, char **value) { char *end = header + hlen - 1; /* point to the last byte */ DEBUGASSERT(hlen); @@ -292,9 +292,10 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, if(!end) { end = strchr(header, '\n'); if(!end) - return CURLE_BAD_FUNCTION_ARGUMENT; + /* neither CR nor LF as terminator is not a valid header */ + return CURLE_WEIRD_SERVER_REPLY; } - hlen = end - header + 1; + hlen = end - header; if((header[0] == ' ') || (header[0] == '\t')) { if(data->state.prevhead) @@ -319,21 +320,19 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, hs->buffer[hlen] = 0; /* nul terminate */ result = namevalue(hs->buffer, hlen, type, &name, &value); - if(result) - goto fail; - - hs->name = name; - hs->value = value; - hs->type = type; - hs->request = data->state.requests; - - /* insert this node into the list of headers */ - Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, - hs, &hs->node); - data->state.prevhead = hs; - return CURLE_OK; -fail: - free(hs); + if(!result) { + hs->name = name; + hs->value = value; + hs->type = type; + hs->request = data->state.requests; + + /* insert this node into the list of headers */ + Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail, + hs, &hs->node); + data->state.prevhead = hs; + } + else + free(hs); return result; } diff --git a/Utilities/cmcurl/lib/hostasyn.c b/Utilities/cmcurl/lib/hostasyn.c index faf01c5..2f6762c 100644 --- a/Utilities/cmcurl/lib/hostasyn.c +++ b/Utilities/cmcurl/lib/hostasyn.c @@ -67,11 +67,10 @@ CURLcode Curl_addrinfo_callback(struct Curl_easy *data, int status, struct Curl_addrinfo *ai) { - struct connectdata *conn = data->conn; struct Curl_dns_entry *dns = NULL; CURLcode result = CURLE_OK; - conn->resolve_async.status = status; + data->state.async.status = status; if(CURL_ASYNC_SUCCESS == status) { if(ai) { @@ -79,8 +78,8 @@ CURLcode Curl_addrinfo_callback(struct Curl_easy *data, Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); dns = Curl_cache_addr(data, ai, - conn->resolve_async.hostname, 0, - conn->resolve_async.port); + data->state.async.hostname, 0, + data->state.async.port); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -95,12 +94,12 @@ CURLcode Curl_addrinfo_callback(struct Curl_easy *data, } } - conn->resolve_async.dns = dns; + 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 */ - conn->resolve_async.done = TRUE; + data->state.async.done = TRUE; /* IPv4: The input hostent struct will be freed by ares when we return from this function */ diff --git a/Utilities/cmcurl/lib/hostip.c b/Utilities/cmcurl/lib/hostip.c index e7c318a..4f44d34 100644 --- a/Utilities/cmcurl/lib/hostip.c +++ b/Utilities/cmcurl/lib/hostip.c @@ -741,7 +741,7 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, Curl_set_in_callback(data, true); st = data->set.resolver_start( #ifdef USE_CURL_ASYNC - conn->resolve_async.resolver, + data->state.async.resolver, #else NULL, #endif @@ -754,16 +754,22 @@ enum resolve_t Curl_resolv(struct Curl_easy *data, #ifndef USE_RESOLVE_ON_IPS /* First check if this is an IPv4 address string */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) + if(Curl_inet_pton(AF_INET, hostname, &in) > 0) { /* This is a dotted IP address 123.123.123.123-style */ addr = Curl_ip2addr(AF_INET, &in, hostname, port); + if(!addr) + return CURLRESOLV_ERROR; + } #ifdef ENABLE_IPV6 - if(!addr) { + else { struct in6_addr in6; /* check if this is an IPv6 address string */ - if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) + if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) { /* This is an IPv6 address literal */ addr = Curl_ip2addr(AF_INET6, &in6, hostname, port); + if(!addr) + return CURLRESOLV_ERROR; + } } #endif /* ENABLE_IPV6 */ @@ -1415,9 +1421,9 @@ CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done) struct connectdata *conn = data->conn; #ifdef USE_CURL_ASYNC - if(conn->resolve_async.dns) { - conn->dns_entry = conn->resolve_async.dns; - conn->resolve_async.dns = NULL; + if(data->state.async.dns) { + conn->dns_entry = data->state.async.dns; + data->state.async.dns = NULL; } #endif @@ -1439,11 +1445,11 @@ CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done) #ifdef USE_CURL_ASYNC CURLcode Curl_resolver_error(struct Curl_easy *data) { - struct connectdata *conn = data->conn; const char *host_or_proxy; CURLcode result; #ifndef CURL_DISABLE_PROXY + struct connectdata *conn = data->conn; if(conn->bits.httpproxy) { host_or_proxy = "proxy"; result = CURLE_COULDNT_RESOLVE_PROXY; @@ -1456,7 +1462,7 @@ CURLcode Curl_resolver_error(struct Curl_easy *data) } failf(data, "Could not resolve %s: %s", host_or_proxy, - conn->resolve_async.hostname); + data->state.async.hostname); return result; } diff --git a/Utilities/cmcurl/lib/hsts.c b/Utilities/cmcurl/lib/hsts.c index 9314be2..8725a35 100644 --- a/Utilities/cmcurl/lib/hsts.c +++ b/Utilities/cmcurl/lib/hsts.c @@ -117,8 +117,6 @@ static CURLcode hsts_create(struct hsts *h, bool subdomains, curl_off_t expires) { - struct stsentry *sts; - char *duphost; size_t hlen; DEBUGASSERT(h); DEBUGASSERT(hostname); @@ -127,24 +125,23 @@ static CURLcode hsts_create(struct hsts *h, if(hlen && (hostname[hlen - 1] == '.')) /* strip off any trailing dot */ --hlen; - if(!hlen) - /* no host name left */ - return CURLE_BAD_FUNCTION_ARGUMENT; - - sts = hsts_entry(); - if(!sts) - return CURLE_OUT_OF_MEMORY; + if(hlen) { + char *duphost; + struct stsentry *sts = hsts_entry(); + if(!sts) + return CURLE_OUT_OF_MEMORY; + + duphost = Curl_memdup0(hostname, hlen); + if(!duphost) { + free(sts); + return CURLE_OUT_OF_MEMORY; + } - duphost = Curl_strndup(hostname, hlen); - if(!duphost) { - free(sts); - return CURLE_OUT_OF_MEMORY; + sts->host = duphost; + sts->expires = expires; + sts->includeSubDomains = subdomains; + Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node); } - - sts->host = duphost; - sts->expires = expires; - sts->includeSubDomains = subdomains; - Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node); return CURLE_OK; } @@ -481,6 +478,7 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) if(sc == CURLSTS_OK) { time_t expires; CURLcode result; + DEBUGASSERT(e.name[0]); if(!e.name[0]) /* bail out if no name was stored */ return CURLE_BAD_FUNCTION_ARGUMENT; diff --git a/Utilities/cmcurl/lib/http.c b/Utilities/cmcurl/lib/http.c index be6d442..679931e 100644 --- a/Utilities/cmcurl/lib/http.c +++ b/Utilities/cmcurl/lib/http.c @@ -100,24 +100,14 @@ * Forward declarations. */ -static int http_getsock_do(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks); static bool http_should_fail(struct Curl_easy *data); -static CURLcode http_setup_conn(struct Curl_easy *data, - struct connectdata *conn); -#ifdef USE_WEBSOCKETS -static CURLcode ws_setup_conn(struct Curl_easy *data, - struct connectdata *conn); -#endif - /* * HTTP handler interface. */ const struct Curl_handler Curl_handler_http = { "HTTP", /* scheme */ - http_setup_conn, /* setup_connection */ + Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ ZERO_NULL, /* do_more */ @@ -125,11 +115,11 @@ const struct Curl_handler Curl_handler_http = { ZERO_NULL, /* connecting */ ZERO_NULL, /* doing */ ZERO_NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ + Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + Curl_http_write_resp, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTP, /* defport */ @@ -139,39 +129,13 @@ const struct Curl_handler Curl_handler_http = { PROTOPT_USERPWDCTRL }; -#ifdef USE_WEBSOCKETS -const struct Curl_handler Curl_handler_ws = { - "WS", /* scheme */ - ws_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - Curl_ws_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_WS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL -}; -#endif - #ifdef USE_SSL /* * HTTPS handler interface. */ const struct Curl_handler Curl_handler_https = { "HTTPS", /* scheme */ - http_setup_conn, /* setup_connection */ + Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ ZERO_NULL, /* do_more */ @@ -179,11 +143,11 @@ const struct Curl_handler Curl_handler_https = { NULL, /* connecting */ ZERO_NULL, /* doing */ NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ + Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + Curl_http_write_resp, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_HTTPS, /* defport */ @@ -193,36 +157,10 @@ const struct Curl_handler Curl_handler_https = { PROTOPT_USERPWDCTRL }; -#ifdef USE_WEBSOCKETS -const struct Curl_handler Curl_handler_wss = { - "WSS", /* scheme */ - ws_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - NULL, /* connecting */ - ZERO_NULL, /* doing */ - NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - Curl_ws_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTPS, /* defport */ - CURLPROTO_WSS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL -}; -#endif - #endif -static CURLcode http_setup_conn(struct Curl_easy *data, - struct connectdata *conn) +CURLcode Curl_http_setup_conn(struct Curl_easy *data, + struct connectdata *conn) { /* allocate the HTTP-specific struct for the Curl_easy, only to survive during this request */ @@ -245,16 +183,6 @@ static CURLcode http_setup_conn(struct Curl_easy *data, return CURLE_OK; } -#ifdef USE_WEBSOCKETS -static CURLcode ws_setup_conn(struct Curl_easy *data, - struct connectdata *conn) -{ - /* websockets is 1.1 only (for now) */ - data->state.httpwant = CURL_HTTP_VERSION_1_1; - return http_setup_conn(data, conn); -} -#endif - #ifndef CURL_DISABLE_PROXY /* * checkProxyHeaders() checks the linked list of custom proxy headers @@ -297,7 +225,6 @@ char *Curl_copy_header_value(const char *header) { const char *start; const char *end; - char *value; size_t len; /* Find the end of the header name */ @@ -330,14 +257,7 @@ char *Curl_copy_header_value(const char *header) /* get length of the type */ len = end - start + 1; - value = malloc(len + 1); - if(!value) - return NULL; - - memcpy(value, start, len); - value[len] = 0; /* null-terminate */ - - return value; + return Curl_memdup0(start, len); } #ifndef CURL_DISABLE_HTTP_AUTH @@ -1597,9 +1517,9 @@ CURLcode Curl_http_connect(struct Curl_easy *data, bool *done) /* this returns the socket to wait for in the DO and DOING state for the multi interface and then we're always _sending_ a request and thus we wait for the single socket to become writable only */ -static int http_getsock_do(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +int Curl_http_getsock_do(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) { /* write mode */ (void)conn; @@ -2103,6 +2023,7 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, switch(data->set.timecondition) { default: + DEBUGF(infof(data, "invalid time condition")); return CURLE_BAD_FUNCTION_ARGUMENT; case CURL_TIMECOND_IFMODSINCE: @@ -2271,7 +2192,7 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) } #endif - if(strcmp("Host:", ptr)) { + if(!strcasecompare("Host:", ptr)) { aptr->host = aprintf("Host:%s\r\n", &ptr[5]); if(!aptr->host) return CURLE_OUT_OF_MEMORY; @@ -2359,9 +2280,7 @@ CURLcode Curl_http_target(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } } - /* Extract the URL to use in the request. Store in STRING_TEMP_URL for - clean-up reasons if the function returns before the free() further - down. */ + /* Extract the URL to use in the request. */ uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT); if(uc) { curl_url_cleanup(h); @@ -3021,13 +2940,14 @@ CURLcode Curl_http_resume(struct Curl_easy *data, } /* when seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : + (data->state.resume_from - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, readthisamountnow, + data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); passed += actuallyread; @@ -3062,6 +2982,7 @@ CURLcode Curl_http_firstwrite(struct Curl_easy *data, { struct SingleRequest *k = &data->req; + *done = FALSE; if(data->req.newurl) { if(conn->bits.close) { /* Abort after the headers if "follow Location" is set @@ -3187,7 +3108,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) ) { result = Curl_http2_switch(data, conn, FIRSTSOCKET); if(result) - return result; + goto fail; } else #endif @@ -3202,7 +3123,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) DEBUGF(infof(data, "HTTP/2 over clean TCP")); result = Curl_http2_switch(data, conn, FIRSTSOCKET); if(result) - return result; + goto fail; } break; } @@ -3212,11 +3133,11 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) result = Curl_http_host(data, conn); if(result) - return result; + goto fail; result = Curl_http_useragent(data); if(result) - return result; + goto fail; Curl_http_method(data, conn, &request, &httpreq); @@ -3232,7 +3153,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) (pq ? pq : data->state.up.path), FALSE); free(pq); if(result) - return result; + goto fail; } Curl_safefree(data->state.aptr.ref); @@ -3257,23 +3178,23 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) /* we only consider transfer-encoding magic if libz support is built-in */ result = Curl_transferencode(data); if(result) - return result; + goto fail; #endif result = Curl_http_body(data, conn, httpreq, &te); if(result) - return result; + goto fail; p_accept = Curl_checkheaders(data, STRCONST("Accept"))?NULL:"Accept: */*\r\n"; result = Curl_http_resume(data, conn, httpreq); if(result) - return result; + goto fail; result = Curl_http_range(data, httpreq); if(result) - return result; + goto fail; httpstring = get_http_string(data, conn); @@ -3291,7 +3212,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) result = Curl_http_target(data, conn, &req); if(result) { Curl_dyn_free(&req); - return result; + goto fail; } #ifndef CURL_DISABLE_ALTSVC @@ -3362,7 +3283,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) if(result) { Curl_dyn_free(&req); - return result; + goto fail; } if(!(conn->handler->flags&PROTOPT_SSL) && @@ -3398,7 +3319,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) } if(result) { Curl_dyn_free(&req); - return result; + goto fail; } if((http->postsize > -1) && @@ -3434,6 +3355,9 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) but is disabled here again to avoid that the chunked encoded version is actually used when sending the request body over h2 */ data->req.upload_chunky = FALSE; +fail: + if(CURLE_TOO_LARGE == result) + failf(data, "HTTP request too large"); return result; } @@ -3896,7 +3820,7 @@ CURLcode Curl_http_statusline(struct Curl_easy *data, * fields. */ if(data->set.timecondition) data->info.timecond = TRUE; - /* FALLTHROUGH */ + FALLTHROUGH(); case 204: /* (quote from RFC2616, section 10.2.5): The server has * fulfilled the request but does not need to return an @@ -3995,15 +3919,16 @@ CURLcode Curl_bump_headersize(struct Curl_easy *data, /* * Read any HTTP header lines from the server and pass them to the client app. */ -CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, - struct connectdata *conn, - const char *buf, size_t blen, - size_t *pconsumed) +static CURLcode http_rw_headers(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed) { - CURLcode result; + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; char *headp; char *end_ptr; + bool leftover_body = FALSE; /* header line within buffer loop */ *pconsumed = 0; @@ -4032,12 +3957,12 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, if(st == STATUS_BAD) { /* this is not the beginning of a protocol first header line */ k->header = FALSE; - k->badheader = TRUE; streamclose(conn, "bad HTTP: No end-of-message indicator"); if(!data->set.http09_allowed) { failf(data, "Received HTTP/0.9 when not allowed"); return CURLE_UNSUPPORTED_PROTOCOL; } + leftover_body = TRUE; goto out; } } @@ -4071,15 +3996,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, return CURLE_UNSUPPORTED_PROTOCOL; } k->header = FALSE; - if(blen) - /* since there's more, this is a partial bad header */ - k->badheader = TRUE; - else { - /* this was all we read so it's all a bad header */ - k->badheader = TRUE; - return CURLE_OK; - } - break; + leftover_body = TRUE; + goto out; } } @@ -4088,6 +4006,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, headp = Curl_dyn_ptr(&data->state.headerb); if((0x0a == *headp) || (0x0d == *headp)) { size_t headerlen; + bool switch_to_h2 = FALSE; /* Zero-length header line means end of headers! */ if('\r' == *headp) @@ -4117,42 +4036,40 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, } break; case 101: - /* Switching Protocols */ - if(k->upgr101 == UPGR101_H2) { - /* Switching to HTTP/2 */ - DEBUGASSERT(conn->httpversion < 20); - infof(data, "Received 101, Switching to HTTP/2"); - k->upgr101 = UPGR101_RECEIVED; - - /* we'll get more headers (HTTP/2 response) */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - - /* switch to http2 now. The bytes after response headers - are also processed here, otherwise they are lost. */ - result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); - if(result) - return result; - *pconsumed += blen; - blen = 0; - } + if(conn->httpversion == 11) { + /* Switching Protocols only allowed from HTTP/1.1 */ + if(k->upgr101 == UPGR101_H2) { + /* Switching to HTTP/2 */ + infof(data, "Received 101, Switching to HTTP/2"); + k->upgr101 = UPGR101_RECEIVED; + + /* we'll get more headers (HTTP/2 response) */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + switch_to_h2 = TRUE; + } #ifdef USE_WEBSOCKETS - else if(k->upgr101 == UPGR101_WS) { - /* verify the response */ - result = Curl_ws_accept(data, buf, blen); - if(result) - return result; - k->header = FALSE; /* no more header to parse! */ - if(data->set.connect_only) { - k->keepon &= ~KEEP_RECV; /* read no more content */ - *pconsumed += blen; + else if(k->upgr101 == UPGR101_WS) { + /* verify the response */ + result = Curl_ws_accept(data, buf, blen); + if(result) + return result; + k->header = FALSE; /* no more header to parse! */ + *pconsumed += blen; /* ws accept handled the data */ blen = 0; + if(data->set.connect_only) + k->keepon &= ~KEEP_RECV; /* read no more content */ } - } #endif + else { + /* Not switching to another protocol */ + k->header = FALSE; /* no more header to parse! */ + } + } else { - /* Not switching to another protocol */ - k->header = FALSE; /* no more header to parse! */ + /* invalid for other HTTP versions */ + failf(data, "unexpected 101 response code"); + return CURLE_WEIRD_SERVER_REPLY; } break; default: @@ -4359,16 +4276,6 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, */ if(data->req.no_body) k->download_done = TRUE; -#ifndef CURL_DISABLE_RTSP - else if((conn->handler->protocol & CURLPROTO_RTSP) && - (data->set.rtspreq == RTSPREQ_DESCRIBE) && - (k->size <= -1)) - /* Respect section 4.4 of rfc2326: If the Content-Length header is - absent, a length 0 must be assumed. It will prevent libcurl from - hanging on DESCRIBE request that got refused for whatever - reason */ - k->download_done = TRUE; -#endif /* If max download size is *zero* (nothing) we already have nothing and can safely return ok now! But for HTTP/2, we'd @@ -4388,6 +4295,17 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, /* We continue reading headers, reset the line-based header */ Curl_dyn_reset(&data->state.headerb); + if(switch_to_h2) { + /* Having handled the headers, we can do the HTTP/2 switch. + * Any remaining `buf` bytes are already HTTP/2 and passed to + * be processed. */ + result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); + if(result) + return result; + *pconsumed += blen; + blen = 0; + } + continue; } @@ -4578,9 +4496,78 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, there might be a non-header part left in the end of the read buffer. */ out: + if(!k->header && !leftover_body) { + Curl_dyn_free(&data->state.headerb); + } return CURLE_OK; } +/* + * HTTP protocol `write_resp` implementation. Will parse headers + * when not done yet and otherwise return without consuming data. + */ +CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed, + bool *done) +{ + *done = FALSE; + if(!data->req.header) { + *pconsumed = 0; + return CURLE_OK; + } + else { + CURLcode result; + + result = http_rw_headers(data, buf, blen, pconsumed); + if(!result && !data->req.header) { + /* we have successfully finished parsing the HEADERs */ + result = Curl_http_firstwrite(data, data->conn, done); + + if(!data->req.no_body && Curl_dyn_len(&data->state.headerb)) { + /* leftover from parsing something that turned out not + * to be a header, only happens if we allow for + * HTTP/0.9 like responses */ + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb)); + } + Curl_dyn_free(&data->state.headerb); + } + return result; + } +} + +CURLcode Curl_http_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos, + bool *done) +{ + CURLcode result; + size_t consumed; + int flags; + + *done = FALSE; + result = Curl_http_write_resp_hds(data, buf, blen, &consumed, done); + if(result || *done) + goto out; + + DEBUGASSERT(consumed <= blen); + blen -= consumed; + buf += consumed; + /* either all was consumed in header parsing, or we have data left + * and are done with heders, e.g. it is BODY data */ + DEBUGASSERT(!blen || !data->req.header); + if(!data->req.header && (blen || is_eos)) { + /* BODY data after header been parsed, write and consume */ + flags = CLIENTWRITE_BODY; + if(is_eos) + flags |= CLIENTWRITE_EOS; + result = Curl_client_write(data, flags, (char *)buf, blen); + } +out: + return result; +} /* Decode HTTP status code string. */ CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len) @@ -4617,7 +4604,7 @@ CURLcode Curl_http_req_make(struct httpreq **preq, CURLcode result = CURLE_OUT_OF_MEMORY; DEBUGASSERT(method); - if(m_len + 1 >= sizeof(req->method)) + if(m_len + 1 > sizeof(req->method)) return CURLE_BAD_FUNCTION_ARGUMENT; req = calloc(1, sizeof(*req)); @@ -4625,17 +4612,17 @@ CURLcode Curl_http_req_make(struct httpreq **preq, goto out; memcpy(req->method, method, m_len); if(scheme) { - req->scheme = Curl_strndup(scheme, s_len); + req->scheme = Curl_memdup0(scheme, s_len); if(!req->scheme) goto out; } if(authority) { - req->authority = Curl_strndup(authority, a_len); + req->authority = Curl_memdup0(authority, a_len); if(!req->authority) goto out; } if(path) { - req->path = Curl_strndup(path, p_len); + req->path = Curl_memdup0(path, p_len); if(!req->path) goto out; } @@ -4773,7 +4760,7 @@ CURLcode Curl_http_req_make2(struct httpreq **preq, CURLUcode uc; DEBUGASSERT(method); - if(m_len + 1 >= sizeof(req->method)) + if(m_len + 1 > sizeof(req->method)) return CURLE_BAD_FUNCTION_ARGUMENT; req = calloc(1, sizeof(*req)); diff --git a/Utilities/cmcurl/lib/http.h b/Utilities/cmcurl/lib/http.h index 56b0913..ad2697c 100644 --- a/Utilities/cmcurl/lib/http.h +++ b/Utilities/cmcurl/lib/http.h @@ -54,14 +54,6 @@ extern const struct Curl_handler Curl_handler_http; extern const struct Curl_handler Curl_handler_https; #endif -#ifdef USE_WEBSOCKETS -extern const struct Curl_handler Curl_handler_ws; - -#ifdef USE_SSL -extern const struct Curl_handler Curl_handler_wss; -#endif -#endif /* websockets */ - struct dynhds; CURLcode Curl_bump_headersize(struct Curl_easy *data, @@ -147,9 +139,17 @@ CURLcode Curl_http_firstwrite(struct Curl_easy *data, bool *done); /* protocol-specific functions set up to be called by the main engine */ +CURLcode Curl_http_setup_conn(struct Curl_easy *data, + struct connectdata *conn); CURLcode Curl_http(struct Curl_easy *data, bool *done); CURLcode Curl_http_done(struct Curl_easy *data, CURLcode, bool premature); CURLcode Curl_http_connect(struct Curl_easy *data, bool *done); +int Curl_http_getsock_do(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks); +CURLcode Curl_http_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos, + bool *done); /* These functions are in http.c */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, @@ -225,10 +225,10 @@ struct HTTP { CURLcode Curl_http_size(struct Curl_easy *data); -CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, - struct connectdata *conn, - const char *buf, size_t blen, - size_t *pconsumed); +CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed, + bool *done); /** * Curl_http_output_auth() setups the authentication headers for the diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c index 9738484..c3157d1 100644 --- a/Utilities/cmcurl/lib/http2.c +++ b/Utilities/cmcurl/lib/http2.c @@ -219,10 +219,10 @@ static void drain_stream(struct Curl_cfilter *cf, if(!stream->send_closed && (stream->upload_left || stream->upload_blocked_len)) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x", + if(data->state.select_bits != bits) { + CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x", stream->id, bits); - data->state.dselect_bits = bits; + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } @@ -283,13 +283,20 @@ static void http2_data_done(struct Curl_cfilter *cf, return; if(ctx->h2) { + bool flush_egress = FALSE; + /* returns error if stream not known, which is fine here */ + (void)nghttp2_session_set_stream_user_data(ctx->h2, stream->id, NULL); + if(!stream->closed && stream->id > 0) { /* RST_STREAM */ CURL_TRC_CF(data, cf, "[%d] premature DATA_DONE, RST stream", stream->id); - if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, - stream->id, NGHTTP2_STREAM_CLOSED)) - (void)nghttp2_session_send(ctx->h2); + stream->closed = TRUE; + stream->reset = TRUE; + stream->send_closed = TRUE; + nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED); + flush_egress = TRUE; } if(!Curl_bufq_is_empty(&stream->recvbuf)) { /* Anything in the recvbuf is still being counted @@ -299,19 +306,11 @@ static void http2_data_done(struct Curl_cfilter *cf, nghttp2_session_consume(ctx->h2, stream->id, Curl_bufq_len(&stream->recvbuf)); /* give WINDOW_UPATE a chance to be sent, but ignore any error */ - (void)h2_progress_egress(cf, data); + flush_egress = TRUE; } - /* -1 means unassigned and 0 means cleared */ - if(nghttp2_session_get_stream_user_data(ctx->h2, stream->id)) { - int rv = nghttp2_session_set_stream_user_data(ctx->h2, - stream->id, 0); - if(rv) { - infof(data, "http/2: failed to clear user_data for stream %u", - stream->id); - DEBUGASSERT(0); - } - } + if(flush_egress) + nghttp2_session_send(ctx->h2); } Curl_bufq_free(&stream->sendbuf); @@ -1316,26 +1315,43 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *userp) { struct Curl_cfilter *cf = userp; - struct Curl_easy *data_s; + struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf); struct stream_ctx *stream; int rv; (void)session; + DEBUGASSERT(call_data); /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ data_s = stream_id? nghttp2_session_get_stream_user_data(session, stream_id) : NULL; if(!data_s) { + CURL_TRC_CF(call_data, cf, + "[%d] on_stream_close, no easy set on stream", stream_id); return 0; } + if(!GOOD_EASY_HANDLE(data_s)) { + /* nghttp2 still has an easy registered for the stream which has + * been freed be libcurl. This points to a code path that does not + * trigger DONE or DETACH events as it must. */ + CURL_TRC_CF(call_data, cf, + "[%d] on_stream_close, not a GOOD easy on stream", stream_id); + (void)nghttp2_session_set_stream_user_data(session, stream_id, 0); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } stream = H2_STREAM_CTX(data_s); - if(!stream) + if(!stream) { + CURL_TRC_CF(data_s, cf, + "[%d] on_stream_close, GOOD easy but no stream", stream_id); return NGHTTP2_ERR_CALLBACK_FAILURE; + } stream->closed = TRUE; stream->error = error_code; - if(stream->error) + if(stream->error) { stream->reset = TRUE; + stream->send_closed = TRUE; + } if(stream->error) CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %d)", @@ -2315,18 +2331,22 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { struct cf_h2_ctx *ctx = cf->ctx; - bool want_recv = CURL_WANT_RECV(data); - bool want_send = CURL_WANT_SEND(data); + curl_socket_t sock; + bool want_recv, want_send; + + if(!ctx->h2) + return; - if(ctx->h2 && (want_recv || want_send)) { + sock = Curl_conn_cf_get_socket(cf, data); + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); + if(want_recv || want_send) { struct stream_ctx *stream = H2_STREAM_CTX(data); - curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); struct cf_call_data save; bool c_exhaust, s_exhaust; CF_DATA_SAVE(save, cf, data); - c_exhaust = !nghttp2_session_get_remote_window_size(ctx->h2); - s_exhaust = stream && stream->id >= 0 && + c_exhaust = want_send && !nghttp2_session_get_remote_window_size(ctx->h2); + s_exhaust = want_send && stream && stream->id >= 0 && !nghttp2_session_get_stream_remote_window_size(ctx->h2, stream->id); want_recv = (want_recv || c_exhaust || s_exhaust); diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.c b/Utilities/cmcurl/lib/http_aws_sigv4.c index b673055..c938291 100644 --- a/Utilities/cmcurl/lib/http_aws_sigv4.c +++ b/Utilities/cmcurl/lib/http_aws_sigv4.c @@ -247,7 +247,7 @@ static CURLcode make_headers(struct Curl_easy *data, } else { char *value; - + char *endp; value = strchr(*date_header, ':'); if(!value) { *date_header = NULL; @@ -256,8 +256,17 @@ static CURLcode make_headers(struct Curl_easy *data, ++value; while(ISBLANK(*value)) ++value; - strncpy(timestamp, value, TIMESTAMP_SIZE - 1); - timestamp[TIMESTAMP_SIZE - 1] = 0; + endp = value; + while(*endp && ISALNUM(*endp)) + ++endp; + /* 16 bytes => "19700101T000000Z" */ + if((endp - value) == TIMESTAMP_SIZE - 1) { + memcpy(timestamp, value, TIMESTAMP_SIZE - 1); + timestamp[TIMESTAMP_SIZE - 1] = 0; + } + else + /* bad timestamp length */ + timestamp[0] = 0; *date_header = NULL; } @@ -605,7 +614,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) result = CURLE_URL_MALFORMAT; goto fail; } - strncpy(service, hostname, len); + memcpy(service, hostname, len); service[len] = '\0'; infof(data, "aws_sigv4: picked service %s from host", service); @@ -624,7 +633,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) result = CURLE_URL_MALFORMAT; goto fail; } - strncpy(region, reg, len); + memcpy(region, reg, len); region[len] = '\0'; infof(data, "aws_sigv4: picked region %s from host", region); } diff --git a/Utilities/cmcurl/lib/http_chunks.c b/Utilities/cmcurl/lib/http_chunks.c index acdb108..039c179 100644 --- a/Utilities/cmcurl/lib/http_chunks.c +++ b/Utilities/cmcurl/lib/http_chunks.c @@ -75,47 +75,67 @@ */ -void Curl_httpchunk_init(struct Curl_easy *data) +void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body) { - struct connectdata *conn = data->conn; - struct Curl_chunker *chunk = &conn->chunk; - chunk->hexindex = 0; /* start at 0 */ - chunk->state = CHUNK_HEX; /* we get hex first! */ - Curl_dyn_init(&conn->trailer, DYN_H1_TRAILER); + (void)data; + ch->hexindex = 0; /* start at 0 */ + ch->state = CHUNK_HEX; /* we get hex first! */ + ch->last_code = CHUNKE_OK; + Curl_dyn_init(&ch->trailer, DYN_H1_TRAILER); + ch->ignore_body = ignore_body; } -/* - * chunk_read() returns a OK for normal operations, or a positive return code - * for errors. STOP means this sequence of chunks is complete. The 'wrote' - * argument is set to tell the caller how many bytes we actually passed to the - * client (for byte-counting and whatever). - * - * The states and the state-machine is further explained in the header file. - * - * This function always uses ASCII hex values to accommodate non-ASCII hosts. - * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. - */ -CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, - char *buf, - size_t blen, - size_t *pconsumed, - CURLcode *extrap) +void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body) +{ + (void)data; + ch->hexindex = 0; /* start at 0 */ + ch->state = CHUNK_HEX; /* we get hex first! */ + ch->last_code = CHUNKE_OK; + Curl_dyn_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); +} + +bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch) +{ + (void)data; + return ch->state == CHUNK_DONE; +} + +static CURLcode httpchunk_readwrite(struct Curl_easy *data, + struct Curl_chunker *ch, + struct Curl_cwriter *cw_next, + const char *buf, size_t blen, + size_t *pconsumed) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct Curl_chunker *ch = &conn->chunk; - struct SingleRequest *k = &data->req; size_t piece; *pconsumed = 0; /* nothing's written yet */ + /* first check terminal states that will not progress anywhere */ + if(ch->state == CHUNK_DONE) + return CURLE_OK; + if(ch->state == CHUNK_FAILED) + return CURLE_RECV_ERROR; /* the original data is written to the client, but we go on with the chunk read process, to properly calculate the content length */ - if(data->set.http_te_skip && !k->ignorebody) { - result = Curl_client_write(data, CLIENTWRITE_BODY, buf, blen); + if(data->set.http_te_skip && !ch->ignore_body) { + if(cw_next) + result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, buf, blen); + else + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen); if(result) { - *extrap = result; - return CHUNKE_PASSTHRU_ERROR; + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } } @@ -123,28 +143,35 @@ CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, switch(ch->state) { case CHUNK_HEX: if(ISXDIGIT(*buf)) { - if(ch->hexindex < CHUNK_MAXNUM_LEN) { - ch->hexbuffer[ch->hexindex] = *buf; - buf++; - blen--; - ch->hexindex++; - } - else { - return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */ + if(ch->hexindex >= CHUNK_MAXNUM_LEN) { + failf(data, "chunk hex-length longer than %d", CHUNK_MAXNUM_LEN); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_TOO_LONG_HEX; /* longer than we support */ + return CURLE_RECV_ERROR; } + ch->hexbuffer[ch->hexindex++] = *buf; + buf++; + blen--; } else { char *endptr; - if(0 == ch->hexindex) + if(0 == ch->hexindex) { /* This is illegal data, we received junk where we expected a hexadecimal digit. */ - return CHUNKE_ILLEGAL_HEX; + failf(data, "chunk hex-length char not a hex digit: 0x%x", *buf); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_ILLEGAL_HEX; + return CURLE_RECV_ERROR; + } /* blen and buf are unmodified */ ch->hexbuffer[ch->hexindex] = 0; - - if(curlx_strtoofft(ch->hexbuffer, &endptr, 16, &ch->datasize)) - return CHUNKE_ILLEGAL_HEX; + if(curlx_strtoofft(ch->hexbuffer, &endptr, 16, &ch->datasize)) { + failf(data, "chunk hex-length not valid: '%s'", ch->hexbuffer); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_ILLEGAL_HEX; + return CURLE_RECV_ERROR; + } ch->state = CHUNK_LF; /* now wait for the CRLF */ } break; @@ -173,12 +200,17 @@ CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, piece = curlx_sotouz(ch->datasize); /* Write the data portion available */ - if(!data->set.http_te_skip && !k->ignorebody) { - result = Curl_client_write(data, CLIENTWRITE_BODY, buf, piece); - + if(!data->set.http_te_skip && !ch->ignore_body) { + if(cw_next) + result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, + buf, piece); + else + result = Curl_client_write(data, CLIENTWRITE_BODY, + (char *)buf, piece); if(result) { - *extrap = result; - return CHUNKE_PASSTHRU_ERROR; + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } } @@ -195,38 +227,51 @@ CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, case CHUNK_POSTLF: if(*buf == 0x0a) { /* The last one before we go back to hex state and start all over. */ - Curl_httpchunk_init(data); /* sets state back to CHUNK_HEX */ + Curl_httpchunk_reset(data, ch, ch->ignore_body); + } + else if(*buf != 0x0d) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return CURLE_RECV_ERROR; } - else if(*buf != 0x0d) - return CHUNKE_BAD_CHUNK; buf++; blen--; break; case CHUNK_TRAILER: if((*buf == 0x0d) || (*buf == 0x0a)) { - char *tr = Curl_dyn_ptr(&conn->trailer); + char *tr = Curl_dyn_ptr(&ch->trailer); /* this is the end of a trailer, but if the trailer was zero bytes there was no trailer and we move on */ if(tr) { size_t trlen; - result = Curl_dyn_addn(&conn->trailer, (char *)STRCONST("\x0d\x0a")); - if(result) - return CHUNKE_OUT_OF_MEMORY; - - tr = Curl_dyn_ptr(&conn->trailer); - trlen = Curl_dyn_len(&conn->trailer); + result = Curl_dyn_addn(&ch->trailer, (char *)STRCONST("\x0d\x0a")); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_OUT_OF_MEMORY; + return result; + } + tr = Curl_dyn_ptr(&ch->trailer); + trlen = Curl_dyn_len(&ch->trailer); if(!data->set.http_te_skip) { - result = Curl_client_write(data, - CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER, - tr, trlen); + if(cw_next) + result = Curl_cwriter_write(data, cw_next, + CLIENTWRITE_HEADER| + CLIENTWRITE_TRAILER, + tr, trlen); + else + result = Curl_client_write(data, + CLIENTWRITE_HEADER| + CLIENTWRITE_TRAILER, + tr, trlen); if(result) { - *extrap = result; - return CHUNKE_PASSTHRU_ERROR; + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } } - Curl_dyn_reset(&conn->trailer); + Curl_dyn_reset(&ch->trailer); ch->state = CHUNK_TRAILER_CR; if(*buf == 0x0a) /* already on the LF */ @@ -239,9 +284,12 @@ CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, } } else { - result = Curl_dyn_addn(&conn->trailer, buf, 1); - if(result) - return CHUNKE_OUT_OF_MEMORY; + result = Curl_dyn_addn(&ch->trailer, buf, 1); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_OUT_OF_MEMORY; + return result; + } } buf++; blen--; @@ -253,8 +301,11 @@ CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, buf++; blen--; } - else - return CHUNKE_BAD_CHUNK; + else { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return CURLE_RECV_ERROR; + } break; case CHUNK_TRAILER_POSTCR: @@ -277,21 +328,29 @@ CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, case CHUNK_STOP: if(*buf == 0x0a) { blen--; - /* Record the length of any data left in the end of the buffer even if there's no more chunks to read */ ch->datasize = blen; - - return CHUNKE_STOP; /* return stop */ + ch->state = CHUNK_DONE; + return CURLE_OK; } - else - return CHUNKE_BAD_CHUNK; + else { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return CURLE_RECV_ERROR; + } + case CHUNK_DONE: + return CURLE_OK; + + case CHUNK_FAILED: + return CURLE_RECV_ERROR; } + } - return CHUNKE_OK; + return CURLE_OK; } -const char *Curl_chunked_strerror(CHUNKcode code) +static const char *Curl_chunked_strerror(CHUNKcode code) { switch(code) { default: @@ -303,8 +362,7 @@ const char *Curl_chunked_strerror(CHUNKcode code) case CHUNKE_BAD_CHUNK: return "Malformed encoding found"; case CHUNKE_PASSTHRU_ERROR: - DEBUGASSERT(0); /* never used */ - return ""; + return "Error writing data to client"; case CHUNKE_BAD_ENCODING: return "Bad content-encoding found"; case CHUNKE_OUT_OF_MEMORY: @@ -312,4 +370,86 @@ const char *Curl_chunked_strerror(CHUNKcode code) } } +CURLcode Curl_httpchunk_read(struct Curl_easy *data, + struct Curl_chunker *ch, + char *buf, size_t blen, + size_t *pconsumed) +{ + return httpchunk_readwrite(data, ch, NULL, buf, blen, pconsumed); +} + +struct chunked_writer { + struct Curl_cwriter super; + struct Curl_chunker ch; +}; + +static CURLcode cw_chunked_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct chunked_writer *ctx = (struct chunked_writer *)writer; + + data->req.chunk = TRUE; /* chunks coming our way. */ + Curl_httpchunk_init(data, &ctx->ch, FALSE); + return CURLE_OK; +} + +static void cw_chunked_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct chunked_writer *ctx = (struct chunked_writer *)writer; + Curl_httpchunk_free(data, &ctx->ch); +} + +static CURLcode cw_chunked_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + struct chunked_writer *ctx = (struct chunked_writer *)writer; + CURLcode result; + size_t consumed; + + if(!(type & CLIENTWRITE_BODY)) + return Curl_cwriter_write(data, writer->next, type, buf, blen); + + consumed = 0; + result = httpchunk_readwrite(data, &ctx->ch, writer->next, buf, blen, + &consumed); + + if(result) { + if(CHUNKE_PASSTHRU_ERROR == ctx->ch.last_code) { + failf(data, "Failed reading the chunked-encoded stream"); + } + else { + failf(data, "%s in chunked-encoding", + Curl_chunked_strerror(ctx->ch.last_code)); + } + return result; + } + + blen -= consumed; + if(CHUNK_DONE == ctx->ch.state) { + /* chunks read successfully, download is complete */ + data->req.download_done = TRUE; + if(blen) { + infof(data, "Leftovers after chunking: %zu bytes", blen); + } + } + else if((type & CLIENTWRITE_EOS) && !data->req.no_body) { + failf(data, "transfer closed with outstanding read data remaining"); + return CURLE_PARTIAL_FILE; + } + + return CURLE_OK; +} + +/* HTTP chunked Transfer-Encoding decoder */ +const struct Curl_cwtype Curl_httpchunk_unencoder = { + "chunked", + NULL, + cw_chunked_init, + cw_chunked_write, + cw_chunked_close, + sizeof(struct chunked_writer) +}; + #endif /* CURL_DISABLE_HTTP */ diff --git a/Utilities/cmcurl/lib/http_chunks.h b/Utilities/cmcurl/lib/http_chunks.h index 0a36f37..07f2984 100644 --- a/Utilities/cmcurl/lib/http_chunks.h +++ b/Utilities/cmcurl/lib/http_chunks.h @@ -24,6 +24,10 @@ * ***************************************************************************/ +#ifndef CURL_DISABLE_HTTP + +#include "dynbuf.h" + struct connectdata; /* @@ -67,34 +71,68 @@ typedef enum { signalled If this is an empty trailer CHUNKE_STOP will be signalled. Otherwise the trailer will be broadcasted via Curl_client_write() and the next state will be CHUNK_TRAILER */ - CHUNK_TRAILER_POSTCR + CHUNK_TRAILER_POSTCR, + + /* Successfully de-chunked everything */ + CHUNK_DONE, + + /* Failed on seeing a bad or not correctly terminated chunk */ + CHUNK_FAILED } ChunkyState; typedef enum { - CHUNKE_STOP = -1, CHUNKE_OK = 0, CHUNKE_TOO_LONG_HEX = 1, CHUNKE_ILLEGAL_HEX, CHUNKE_BAD_CHUNK, CHUNKE_BAD_ENCODING, CHUNKE_OUT_OF_MEMORY, - CHUNKE_PASSTHRU_ERROR, /* Curl_httpchunk_read() returns a CURLcode to use */ - CHUNKE_LAST + CHUNKE_PASSTHRU_ERROR /* Curl_httpchunk_read() returns a CURLcode to use */ } CHUNKcode; -const char *Curl_chunked_strerror(CHUNKcode code); - struct Curl_chunker { curl_off_t datasize; ChunkyState state; + CHUNKcode last_code; + struct dynbuf trailer; /* for chunked-encoded trailer */ unsigned char hexindex; - char hexbuffer[ CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ + char hexbuffer[CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ + BIT(ignore_body); /* never write response body data */ }; /* The following functions are defined in http_chunks.c */ -void Curl_httpchunk_init(struct Curl_easy *data); -CHUNKcode Curl_httpchunk_read(struct Curl_easy *data, char *buf, - size_t blen, size_t *pconsumed, - CURLcode *passthru); +void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body); +void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch); +void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body); + +/* + * Read BODY bytes in HTTP/1.1 chunked encoding from `buf` and return + * the amount of bytes consumed. The actual response bytes and trailer + * headers are written out to the client. + * On success, this will consume all bytes up to the end of the response, + * e.g. the last chunk, has been processed. + * @param data the transfer involved + * @param ch the chunker instance keeping state across calls + * @param buf the response data + * @param blen amount of bytes in `buf` + * @param pconsumed on successful return, the number of bytes in `buf` + * consumed + * + * This function always uses ASCII hex values to accommodate non-ASCII hosts. + * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. + */ +CURLcode Curl_httpchunk_read(struct Curl_easy *data, struct Curl_chunker *ch, + char *buf, size_t blen, size_t *pconsumed); + +/** + * @return TRUE iff chunked decoded has finished successfully. + */ +bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch); + +extern const struct Curl_cwtype Curl_httpchunk_unencoder; + +#endif /* !CURL_DISABLE_HTTP */ #endif /* HEADER_CURL_HTTP_CHUNKS_H */ diff --git a/Utilities/cmcurl/lib/http_proxy.c b/Utilities/cmcurl/lib/http_proxy.c index 8e18325..113c43a 100644 --- a/Utilities/cmcurl/lib/http_proxy.c +++ b/Utilities/cmcurl/lib/http_proxy.c @@ -131,8 +131,8 @@ CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, goto out; } - if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) - && data->set.str[STRING_USERAGENT]) { + if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) && + data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { result = Curl_dynhds_cadd(&req->headers, "User-Agent", data->set.str[STRING_USERAGENT]); if(result) diff --git a/Utilities/cmcurl/lib/imap.c b/Utilities/cmcurl/lib/imap.c index 47cff48..f9211d9 100644 --- a/Utilities/cmcurl/lib/imap.c +++ b/Utilities/cmcurl/lib/imap.c @@ -97,7 +97,8 @@ static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done); static CURLcode imap_setup_connection(struct Curl_easy *data, struct connectdata *conn); static char *imap_atom(const char *str, bool escape_only); -static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...); +static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) + CURL_PRINTF(2, 3); static CURLcode imap_parse_url_options(struct connectdata *conn); static CURLcode imap_parse_url_path(struct Curl_easy *data); static CURLcode imap_parse_custom_request(struct Curl_easy *data); @@ -129,7 +130,7 @@ const struct Curl_handler Curl_handler_imap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_IMAP, /* defport */ @@ -158,7 +159,7 @@ const struct Curl_handler Curl_handler_imaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_IMAPS, /* defport */ @@ -354,8 +355,8 @@ static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, */ static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out) { - char *message = data->state.buffer; - size_t len = strlen(message); + char *message = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + size_t len = data->conn->proto.imapc.pp.nfinal; if(len > 2) { /* Find the start of the message */ @@ -895,7 +896,7 @@ static CURLcode imap_state_capability_resp(struct Curl_easy *data, CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct imap_conn *imapc = &conn->proto.imapc; - const char *line = data->state.buffer; + const char *line = Curl_dyn_ptr(&imapc->pp.recvbuf); (void)instate; /* no use for this yet */ @@ -981,7 +982,7 @@ static CURLcode imap_state_starttls_resp(struct Curl_easy *data, (void)instate; /* no use for this yet */ /* Pipelining in response is forbidden. */ - if(data->conn->proto.imapc.pp.cache_size) + if(data->conn->proto.imapc.pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(imapcode != IMAP_RESP_OK) { @@ -1057,17 +1058,13 @@ static CURLcode imap_state_listsearch_resp(struct Curl_easy *data, imapstate instate) { CURLcode result = CURLE_OK; - char *line = data->state.buffer; - size_t len = strlen(line); + char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + size_t len = data->conn->proto.imapc.pp.nfinal; (void)instate; /* No use for this yet */ - if(imapcode == '*') { - /* Temporarily add the LF character back and send as body to the client */ - line[len] = '\n'; - result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1); - line[len] = '\0'; - } + if(imapcode == '*') + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); else if(imapcode != IMAP_RESP_OK) result = CURLE_QUOTE_ERROR; else @@ -1085,7 +1082,7 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, struct connectdata *conn = data->conn; struct IMAP *imap = data->req.p.imap; struct imap_conn *imapc = &conn->proto.imapc; - const char *line = data->state.buffer; + const char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); (void)instate; /* no use for this yet */ @@ -1144,7 +1141,8 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, CURLcode result = CURLE_OK; struct imap_conn *imapc = &conn->proto.imapc; struct pingpong *pp = &imapc->pp; - const char *ptr = data->state.buffer; + const char *ptr = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + size_t len = data->conn->proto.imapc.pp.nfinal; bool parsed = FALSE; curl_off_t size = 0; @@ -1158,16 +1156,12 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse the continuation data contained within the curly brackets */ - while(*ptr && (*ptr != '{')) - ptr++; - - if(*ptr == '{') { + ptr = memchr(ptr, '{', len); + if(ptr) { char *endptr; - if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size)) { - if(endptr - ptr > 1 && endptr[0] == '}' && - endptr[1] == '\r' && endptr[2] == '\0') - parsed = TRUE; - } + if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size) && + (endptr - ptr > 1 && *endptr == '}')) + parsed = TRUE; } if(parsed) { @@ -1175,11 +1169,15 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, size); Curl_pgrsSetDownloadSize(data, size); - if(pp->cache) { - /* At this point there is a bunch of data in the header "cache" that is - actually body content, send it as body and then skip it. Do note - that there may even be additional "headers" after the body. */ - size_t chunk = pp->cache_size; + if(pp->overflow) { + /* At this point there is a data in the receive buffer that is body + content, send it as body and then skip it. Do note that there may + even be additional "headers" after the body. */ + size_t chunk = pp->overflow; + + /* keep only the overflow */ + Curl_dyn_tail(&pp->recvbuf, chunk); + pp->nfinal = 0; /* done */ if(chunk > (size_t)size) /* The conversion from curl_off_t to size_t is always fine here */ @@ -1190,25 +1188,24 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, imap_state(data, IMAP_STOP); return CURLE_OK; } - result = Curl_client_write(data, CLIENTWRITE_BODY, pp->cache, chunk); + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&pp->recvbuf), chunk); if(result) return result; infof(data, "Written %zu bytes, %" CURL_FORMAT_CURL_OFF_TU " bytes are left for transfer", chunk, size - chunk); - /* Have we used the entire cache or just part of it?*/ - if(pp->cache_size > chunk) { - /* Only part of it so shrink the cache to fit the trailing data */ - memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk); - pp->cache_size -= chunk; + /* Have we used the entire overflow or just part of it?*/ + if(pp->overflow > chunk) { + /* remember the remaining trailing overflow data */ + pp->overflow -= chunk; + Curl_dyn_tail(&pp->recvbuf, pp->overflow); } else { + pp->overflow = 0; /* handled */ /* Free the cache */ - Curl_safefree(pp->cache); - - /* Reset the cache size */ - pp->cache_size = 0; + Curl_dyn_reset(&pp->recvbuf); } } @@ -1220,7 +1217,7 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, data->req.maxdownload = size; /* force a recv/send check of this connection, as the data might've been read off the socket already */ - data->conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; Curl_setup_transfer(data, FIRSTSOCKET, size, FALSE, -1); } } @@ -1376,7 +1373,6 @@ static CURLcode imap_statemachine(struct Curl_easy *data, break; case IMAP_LOGOUT: - /* fallthrough, just stop! */ default: /* internal error */ imap_state(data, IMAP_STOP); @@ -1472,9 +1468,7 @@ static CURLcode imap_connect(struct Curl_easy *data, bool *done) Curl_sasl_init(&imapc->sasl, data, &saslimap); Curl_dyn_init(&imapc->dyn, DYN_IMAP_CMD); - /* Initialise the pingpong layer */ - Curl_pp_setup(pp); - Curl_pp_init(data, pp); + Curl_pp_init(pp); /* Parse the URL options */ result = imap_parse_url_options(conn); @@ -1795,7 +1789,14 @@ static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) if(!result) { va_list ap; va_start(ap, fmt); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif result = Curl_pp_vsendf(data, &imapc->pp, Curl_dyn_ptr(&imapc->dyn), ap); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif va_end(ap); } return result; diff --git a/Utilities/cmcurl/lib/inet_pton.c b/Utilities/cmcurl/lib/inet_pton.c index 7d3c698..176cc95 100644 --- a/Utilities/cmcurl/lib/inet_pton.c +++ b/Utilities/cmcurl/lib/inet_pton.c @@ -112,7 +112,8 @@ inet_pton4(const char *src, unsigned char *dst) pch = strchr(digits, ch); if(pch) { - unsigned int val = *tp * 10 + (unsigned int)(pch - digits); + unsigned int val = (unsigned int)(*tp * 10) + + (unsigned int)(pch - digits); if(saw_digit && *tp == 0) return (0); diff --git a/Utilities/cmcurl/lib/inet_pton.h b/Utilities/cmcurl/lib/inet_pton.h index 82fde7e..f8562fa 100644 --- a/Utilities/cmcurl/lib/inet_pton.h +++ b/Utilities/cmcurl/lib/inet_pton.h @@ -31,9 +31,6 @@ int Curl_inet_pton(int, const char *, void *); #ifdef HAVE_INET_PTON #ifdef HAVE_ARPA_INET_H #include <arpa/inet.h> -#elif defined(HAVE_WS2TCPIP_H) -/* inet_pton() exists in Vista or later */ -#include <ws2tcpip.h> #endif #define Curl_inet_pton(x,y,z) inet_pton(x,y,z) #endif diff --git a/Utilities/cmcurl/lib/krb5.c b/Utilities/cmcurl/lib/krb5.c index a1102e5..4db19fb 100644 --- a/Utilities/cmcurl/lib/krb5.c +++ b/Utilities/cmcurl/lib/krb5.c @@ -1,6 +1,6 @@ /* GSSAPI/krb5 support for FTP - loosely based on old krb4.c * - * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan + * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * Copyright (C) Daniel Stenberg * All rights reserved. @@ -75,8 +75,7 @@ static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn, unsigned char data_sec = conn->data_prot; #endif - if(!cmd) - return CURLE_BAD_FUNCTION_ARGUMENT; + DEBUGASSERT(cmd); write_len = strlen(cmd); if(!write_len || write_len > (sizeof(s) -3)) @@ -236,9 +235,12 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) if(Curl_GetFTPResponse(data, &nread, NULL)) return -1; - - if(data->state.buffer[0] != '3') - return -1; + else { + struct pingpong *pp = &conn->proto.ftpc.pp; + char *line = Curl_dyn_ptr(&pp->recvbuf); + if(line[0] != '3') + return -1; + } } stringp = aprintf("%s@%s", service, host); @@ -322,15 +324,19 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) ret = -1; break; } - - if(data->state.buffer[0] != '2' && data->state.buffer[0] != '3') { - infof(data, "Server didn't accept auth data"); - ret = AUTH_ERROR; - break; + else { + struct pingpong *pp = &conn->proto.ftpc.pp; + size_t len = Curl_dyn_len(&pp->recvbuf); + p = Curl_dyn_ptr(&pp->recvbuf); + if((len < 4) || (p[0] != '2' && p[0] != '3')) { + infof(data, "Server didn't accept auth data"); + ret = AUTH_ERROR; + break; + } } _gssresp.value = NULL; /* make sure it is initialized */ - p = data->state.buffer + 4; + p += 4; /* over '789 ' */ p = strstr(p, "ADAT="); if(p) { result = Curl_base64_decode(p + 5, @@ -417,7 +423,6 @@ static char level_to_char(int level) case PROT_PRIVATE: return 'P'; case PROT_CMD: - /* Fall through */ default: /* Those 2 cases should not be reached! */ break; @@ -430,6 +435,9 @@ static char level_to_char(int level) /* Send an FTP command defined by |message| and the optional arguments. The function returns the ftp_code. If an error occurs, -1 is returned. */ static int ftp_send_command(struct Curl_easy *data, const char *message, ...) + CURL_PRINTF(2, 3); + +static int ftp_send_command(struct Curl_easy *data, const char *message, ...) { int ftp_code; ssize_t nread = 0; @@ -750,6 +758,8 @@ static int sec_set_protection_level(struct Curl_easy *data) if(level) { char *pbsz; unsigned int buffer_size = 1 << 20; /* 1048576 */ + struct pingpong *pp = &conn->proto.ftpc.pp; + char *line; code = ftp_send_command(data, "PBSZ %u", buffer_size); if(code < 0) @@ -761,10 +771,11 @@ static int sec_set_protection_level(struct Curl_easy *data) } conn->buffer_size = buffer_size; - pbsz = strstr(data->state.buffer, "PBSZ="); + line = Curl_dyn_ptr(&pp->recvbuf); + pbsz = strstr(line, "PBSZ="); if(pbsz) { /* stick to default value if the check fails */ - if(!strncmp(pbsz, "PBSZ=", 5) && ISDIGIT(pbsz[5])) + if(ISDIGIT(pbsz[5])) buffer_size = atoi(&pbsz[5]); if(buffer_size < conn->buffer_size) conn->buffer_size = buffer_size; diff --git a/Utilities/cmcurl/lib/ldap.c b/Utilities/cmcurl/lib/ldap.c index eb5fe795..4c04647 100644 --- a/Utilities/cmcurl/lib/ldap.c +++ b/Utilities/cmcurl/lib/ldap.c @@ -137,7 +137,7 @@ static void _ldap_free_urldesc(LDAPURLDesc *ludp); _ldap_trace x; \ } while(0) - static void _ldap_trace(const char *fmt, ...); + static void _ldap_trace(const char *fmt, ...) CURL_PRINTF(1, 2); #else #define LDAP_TRACE(x) Curl_nop_stmt #endif @@ -177,7 +177,7 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ @@ -205,7 +205,7 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAPS, /* defport */ diff --git a/Utilities/cmcurl/lib/md4.c b/Utilities/cmcurl/lib/md4.c index 486e5fa..067c211 100644 --- a/Utilities/cmcurl/lib/md4.c +++ b/Utilities/cmcurl/lib/md4.c @@ -194,11 +194,9 @@ static int MD4_Init(MD4_CTX *ctx) static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) { if(!ctx->data) { - ctx->data = malloc(size); - if(ctx->data) { - memcpy(ctx->data, data, size); + ctx->data = Curl_memdup(data, size); + if(ctx->data) ctx->size = size; - } } } diff --git a/Utilities/cmcurl/lib/memdebug.c b/Utilities/cmcurl/lib/memdebug.c index f6ced85..fce933a 100644 --- a/Utilities/cmcurl/lib/memdebug.c +++ b/Utilities/cmcurl/lib/memdebug.c @@ -304,12 +304,6 @@ void curl_dbg_free(void *ptr, int line, const char *source) curl_socket_t curl_dbg_socket(int domain, int type, int protocol, int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d socket() = %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d socket() = %ld\n" : - "FD %s:%d socket() = %zd\n"; - curl_socket_t sockfd; if(countcheck("socket", line, source)) @@ -318,7 +312,8 @@ curl_socket_t curl_dbg_socket(int domain, int type, int protocol, sockfd = socket(domain, type, protocol); if(source && (sockfd != CURL_SOCKET_BAD)) - curl_dbg_log(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d socket() = %" CURL_FORMAT_SOCKET_T "\n", + source, line, sockfd); return sockfd; } @@ -357,16 +352,12 @@ int curl_dbg_socketpair(int domain, int type, int protocol, curl_socket_t socket_vector[2], int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d socketpair() = %d %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d socketpair() = %ld %ld\n" : - "FD %s:%d socketpair() = %zd %zd\n"; - int res = socketpair(domain, type, protocol, socket_vector); if(source && (0 == res)) - curl_dbg_log(fmt, source, line, socket_vector[0], socket_vector[1]); + curl_dbg_log("FD %s:%d socketpair() = " + "%" CURL_FORMAT_SOCKET_T " %" CURL_FORMAT_SOCKET_T "\n", + source, line, socket_vector[0], socket_vector[1]); return res; } @@ -375,19 +366,14 @@ int curl_dbg_socketpair(int domain, int type, int protocol, curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen, int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d accept() = %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d accept() = %ld\n" : - "FD %s:%d accept() = %zd\n"; - struct sockaddr *addr = (struct sockaddr *)saddr; curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen; curl_socket_t sockfd = accept(s, addr, addrlen); if(source && (sockfd != CURL_SOCKET_BAD)) - curl_dbg_log(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d accept() = %" CURL_FORMAT_SOCKET_T "\n", + source, line, sockfd); return sockfd; } @@ -395,14 +381,9 @@ curl_socket_t curl_dbg_accept(curl_socket_t s, void *saddr, void *saddrlen, /* separate function to allow libcurl to mark a "faked" close */ void curl_dbg_mark_sclose(curl_socket_t sockfd, int line, const char *source) { - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d sclose(%d)\n": - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d sclose(%ld)\n": - "FD %s:%d sclose(%zd)\n"; - if(source) - curl_dbg_log(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d sclose(%" CURL_FORMAT_SOCKET_T ")\n", + source, line, sockfd); } /* this is our own defined way to close sockets on *ALL* platforms */ diff --git a/Utilities/cmcurl/lib/memdebug.h b/Utilities/cmcurl/lib/memdebug.h index 78a0125..51147cd 100644 --- a/Utilities/cmcurl/lib/memdebug.h +++ b/Utilities/cmcurl/lib/memdebug.h @@ -72,7 +72,7 @@ CURL_EXTERN ALLOC_FUNC wchar_t *curl_dbg_wcsdup(const wchar_t *str, CURL_EXTERN void curl_dbg_memdebug(const char *logname); CURL_EXTERN void curl_dbg_memlimit(long limit); -CURL_EXTERN void curl_dbg_log(const char *format, ...); +CURL_EXTERN void curl_dbg_log(const char *format, ...) CURL_PRINTF(1, 2); /* file descriptor manipulators */ CURL_EXTERN curl_socket_t curl_dbg_socket(int domain, int type, int protocol, diff --git a/Utilities/cmcurl/lib/mime.c b/Utilities/cmcurl/lib/mime.c index bb66130..d712331 100644 --- a/Utilities/cmcurl/lib/mime.c +++ b/Utilities/cmcurl/lib/mime.c @@ -30,6 +30,7 @@ #include "warnless.h" #include "urldata.h" #include "sendf.h" +#include "strdup.h" #if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ !defined(CURL_DISABLE_SMTP) || \ @@ -817,7 +818,7 @@ static size_t read_part_content(curl_mimepart *part, case MIMEKIND_FILE: if(part->fp && feof(part->fp)) break; /* At EOF. */ - /* FALLTHROUGH */ + FALLTHROUGH(); default: if(part->readfunc) { if(!(part->flags & MIME_FAST_READ)) { @@ -936,7 +937,7 @@ static size_t readback_part(curl_mimepart *part, mimesetstate(&part->state, MIMESTATE_USERHEADERS, hdr->next); break; } - /* FALLTHROUGH */ + FALLTHROUGH(); case MIMESTATE_CURLHEADERS: if(!hdr) mimesetstate(&part->state, MIMESTATE_USERHEADERS, part->userheaders); @@ -970,7 +971,7 @@ static size_t readback_part(curl_mimepart *part, fclose(part->fp); part->fp = NULL; } - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_READFUNC_ABORT: case CURL_READFUNC_PAUSE: case READ_ERROR: @@ -1235,6 +1236,7 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, } break; default: /* Invalid kind: should not occur. */ + DEBUGF(infof(data, "invalid MIMEKIND* attempt")); res = CURLE_BAD_FUNCTION_ARGUMENT; /* Internal error? */ break; } @@ -1370,27 +1372,22 @@ CURLcode curl_mime_filename(curl_mimepart *part, const char *filename) /* Set mime part content from memory data. */ CURLcode curl_mime_data(curl_mimepart *part, - const char *data, size_t datasize) + const char *ptr, size_t datasize) { if(!part) return CURLE_BAD_FUNCTION_ARGUMENT; cleanup_part_content(part); - if(data) { + if(ptr) { if(datasize == CURL_ZERO_TERMINATED) - datasize = strlen(data); + datasize = strlen(ptr); - part->data = malloc(datasize + 1); + part->data = Curl_memdup0(ptr, datasize); if(!part->data) return CURLE_OUT_OF_MEMORY; part->datasize = datasize; - - if(datasize) - memcpy(part->data, data, datasize); - part->data[datasize] = '\0'; /* Set a null terminator as sentinel. */ - part->readfunc = mime_mem_read; part->seekfunc = mime_mem_seek; part->freefunc = mime_mem_free; diff --git a/Utilities/cmcurl/lib/mime.h b/Utilities/cmcurl/lib/mime.h index 0a05c2a..a64f41d 100644 --- a/Utilities/cmcurl/lib/mime.h +++ b/Utilities/cmcurl/lib/mime.h @@ -130,7 +130,8 @@ struct curl_mimepart { size_t lastreadstatus; /* Last read callback returned status. */ }; -CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...); +CURLcode Curl_mime_add_header(struct curl_slist **slp, const char *fmt, ...) + CURL_PRINTF(2, 3); #if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ !defined(CURL_DISABLE_SMTP) || \ diff --git a/Utilities/cmcurl/lib/mprintf.c b/Utilities/cmcurl/lib/mprintf.c index 6b5df5b..63f7f24 100644 --- a/Utilities/cmcurl/lib/mprintf.c +++ b/Utilities/cmcurl/lib/mprintf.c @@ -20,25 +20,11 @@ * * SPDX-License-Identifier: curl * - * - * Purpose: - * A merge of Bjorn Reese's format() function and Daniel's dsprintf() - * 1.0. A full blooded printf() clone with full support for <num>$ - * everywhere (parameters, widths and precisions) including variabled - * sized parameters (like doubles, long longs, long doubles and even - * void * in 64-bit architectures). - * - * Current restrictions: - * - Max 128 parameters - * - No 'long double' support. - * - * If you ever want truly portable and good *printf() clones, the project that - * took on from here is named 'Trio' and you find more details on the trio web - * page at https://daniel.haxx.se/projects/trio/ */ #include "curl_setup.h" #include "dynbuf.h" +#include "curl_printf.h" #include <curl/mprintf.h> #include "curl_memory.h" @@ -86,7 +72,8 @@ #define BUFFSIZE 326 /* buffer for long-to-str and float-to-str calcs, should fit negative DBL_MAX (317 letters) */ -#define MAX_PARAMETERS 128 /* lame static limit */ +#define MAX_PARAMETERS 128 /* number of input arguments */ +#define MAX_SEGMENTS 128 /* number of output segments */ #ifdef __AMIGA__ # undef FORMAT_INT @@ -98,31 +85,33 @@ static const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; /* Upper-case digits. */ static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; -#define OUTCHAR(x) \ - do { \ - if(stream((unsigned char)(x), (FILE *)data) != -1) \ - done++; \ - else \ - return done; /* return immediately on failure */ \ +#define OUTCHAR(x) \ + do { \ + if(!stream(x, userp)) \ + done++; \ + else \ + return done; /* return on failure */ \ } while(0) /* Data type to read from the arglist */ typedef enum { - FORMAT_UNKNOWN = 0, FORMAT_STRING, FORMAT_PTR, - FORMAT_INT, FORMAT_INTPTR, + FORMAT_INT, FORMAT_LONG, FORMAT_LONGLONG, + FORMAT_INTU, + FORMAT_LONGU, + FORMAT_LONGLONGU, FORMAT_DOUBLE, FORMAT_LONGDOUBLE, - FORMAT_WIDTH /* For internal use */ + FORMAT_WIDTH, + FORMAT_PRECISION } FormatType; /* conversion and display flags */ enum { - FLAGS_NEW = 0, FLAGS_SPACE = 1<<0, FLAGS_SHOWSIGN = 1<<1, FLAGS_LEFT = 1<<2, @@ -142,23 +131,40 @@ enum { FLAGS_PRECPARAM = 1<<16, /* precision PARAMETER was specified */ FLAGS_CHAR = 1<<17, /* %c story */ FLAGS_FLOATE = 1<<18, /* %e or %E */ - FLAGS_FLOATG = 1<<19 /* %g or %G */ + FLAGS_FLOATG = 1<<19, /* %g or %G */ + FLAGS_SUBSTR = 1<<20 /* no input, only substring */ }; -struct va_stack { - FormatType type; - int flags; - long width; /* width OR width parameter number */ - long precision; /* precision OR precision parameter number */ +enum { + DOLLAR_UNKNOWN, + DOLLAR_NOPE, + DOLLAR_USE +}; + +/* + * Describes an input va_arg type and hold its value. + */ +struct va_input { + FormatType type; /* FormatType */ union { char *str; void *ptr; - union { - mp_intmax_t as_signed; - mp_uintmax_t as_unsigned; - } num; + mp_intmax_t nums; /* signed */ + mp_uintmax_t numu; /* unsigned */ double dnum; - } data; + } val; +}; + +/* + * Describes an output segment. + */ +struct outsegment { + int width; /* width OR width parameter number */ + int precision; /* precision OR precision parameter number */ + unsigned int flags; + unsigned int input; /* input argument array index */ + char *start; /* format string start to output */ + size_t outlen; /* number of bytes from the format string to output */ }; struct nsprintf { @@ -169,118 +175,123 @@ struct nsprintf { struct asprintf { struct dynbuf *b; - bool fail; /* if an alloc has failed and thus the output is not the complete - data */ + char merr; }; -static long dprintf_DollarString(char *input, char **end) -{ - int number = 0; - while(ISDIGIT(*input)) { - if(number < MAX_PARAMETERS) { - number *= 10; - number += *input - '0'; - } - input++; - } - if(number <= MAX_PARAMETERS && ('$' == *input)) { - *end = ++input; - return number; - } - return 0; -} +/* the provided input number is 1-based but this returns the number 0-based. -static bool dprintf_IsQualifierNoDollar(const char *fmt) + returns -1 if no valid number was provided. +*/ +static int dollarstring(char *input, char **end) { -#if defined(MP_HAVE_INT_EXTENSIONS) - if(!strncmp(fmt, "I32", 3) || !strncmp(fmt, "I64", 3)) { - return TRUE; - } -#endif - - switch(*fmt) { - case '-': case '+': case ' ': case '#': case '.': - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case 'h': case 'l': case 'L': case 'z': case 'q': - case '*': case 'O': -#if defined(MP_HAVE_INT_EXTENSIONS) - case 'I': -#endif - return TRUE; + if(ISDIGIT(*input)) { + int number = 0; + do { + if(number < MAX_PARAMETERS) { + number *= 10; + number += *input - '0'; + } + input++; + } while(ISDIGIT(*input)); - default: - return FALSE; + if(number && (number <= MAX_PARAMETERS) && ('$' == *input)) { + *end = ++input; + return number - 1; + } } + return -1; } -/****************************************************************** +/* + * Parse the format string. * - * Pass 1: - * Create an index with the type of each parameter entry and its - * value (may vary in size) + * Create two arrays. One describes the inputs, one describes the outputs. * * Returns zero on success. - * - ******************************************************************/ + */ -static int dprintf_Pass1(const char *format, struct va_stack *vto, - char **endpos, va_list arglist) +#define PFMT_OK 0 +#define PFMT_DOLLAR 1 /* bad dollar for main param */ +#define PFMT_DOLLARWIDTH 2 /* bad dollar use for width */ +#define PFMT_DOLLARPREC 3 /* bad dollar use for precision */ +#define PFMT_MANYARGS 4 /* too many input arguments used */ +#define PFMT_PREC 5 /* precision overflow */ +#define PFMT_PRECMIX 6 /* bad mix of precision specifiers */ +#define PFMT_WIDTH 7 /* width overflow */ +#define PFMT_INPUTGAP 8 /* gap in arguments */ +#define PFMT_WIDTHARG 9 /* attempted to use same arg twice, for width */ +#define PFMT_PRECARG 10 /* attempted to use same arg twice, for prec */ +#define PFMT_MANYSEGS 11 /* maxed out output segments */ + +static int parsefmt(const char *format, + struct outsegment *out, + struct va_input *in, + int *opieces, + int *ipieces, va_list arglist) { char *fmt = (char *)format; int param_num = 0; - long this_param; - long width; - long precision; - int flags; - long max_param = 0; - long i; + int param; + int width; + int precision; + unsigned int flags; + FormatType type; + int max_param = -1; + int i; + int ocount = 0; + unsigned char usedinput[MAX_PARAMETERS/8]; + size_t outlen = 0; + struct outsegment *optr; + int use_dollar = DOLLAR_UNKNOWN; + char *start = fmt; + + /* clear, set a bit for each used input */ + memset(usedinput, 0, sizeof(usedinput)); while(*fmt) { - if(*fmt++ == '%') { + if(*fmt == '%') { + struct va_input *iptr; + bool loopit = TRUE; + fmt++; + outlen = fmt - start - 1; if(*fmt == '%') { + /* this means a %% that should be output only as %. Create an output + segment. */ + if(outlen) { + optr = &out[ocount++]; + if(ocount > MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = 0; + optr->flags = FLAGS_SUBSTR; + optr->start = start; + optr->outlen = outlen; + } + start = fmt; fmt++; continue; /* while */ } - flags = FLAGS_NEW; - - /* Handle the positional case (N$) */ - - param_num++; - - this_param = dprintf_DollarString(fmt, &fmt); - if(0 == this_param) - /* we got no positional, get the next counter */ - this_param = param_num; - - if(this_param > max_param) - max_param = this_param; + flags = width = precision = 0; - /* - * The parameter with number 'i' should be used. Next, we need - * to get SIZE and TYPE of the parameter. Add the information - * to our array. - */ + if(use_dollar != DOLLAR_NOPE) { + param = dollarstring(fmt, &fmt); + if(param < 0) { + if(use_dollar == DOLLAR_USE) + /* illegal combo */ + return PFMT_DOLLAR; - width = 0; - precision = 0; - - /* Handle the flags */ - - while(dprintf_IsQualifierNoDollar(fmt)) { -#if defined(MP_HAVE_INT_EXTENSIONS) - if(!strncmp(fmt, "I32", 3)) { - flags |= FLAGS_LONG; - fmt += 3; - } - else if(!strncmp(fmt, "I64", 3)) { - flags |= FLAGS_LONGLONG; - fmt += 3; + /* we got no positional, just get the next arg */ + param = -1; + use_dollar = DOLLAR_NOPE; } else -#endif + use_dollar = DOLLAR_USE; + } + else + param = -1; + /* Handle the flags */ + while(loopit) { switch(*fmt++) { case ' ': flags |= FLAGS_SPACE; @@ -298,40 +309,63 @@ static int dprintf_Pass1(const char *format, struct va_stack *vto, case '.': if('*' == *fmt) { /* The precision is picked from a specified parameter */ - flags |= FLAGS_PRECPARAM; fmt++; - param_num++; - i = dprintf_DollarString(fmt, &fmt); - if(i) - precision = i; + if(use_dollar == DOLLAR_USE) { + precision = dollarstring(fmt, &fmt); + if(precision < 0) + /* illegal combo */ + return PFMT_DOLLARPREC; + } else - precision = param_num; - - if(precision > max_param) - max_param = precision; + /* get it from the next argument */ + precision = -1; } else { + bool is_neg = FALSE; flags |= FLAGS_PREC; - precision = strtol(fmt, &fmt, 10); + precision = 0; + if('-' == *fmt) { + is_neg = TRUE; + fmt++; + } + while(ISDIGIT(*fmt)) { + if(precision > INT_MAX/10) + return PFMT_PREC; + precision *= 10; + precision += *fmt - '0'; + fmt++; + } + if(is_neg) + precision = -precision; } if((flags & (FLAGS_PREC | FLAGS_PRECPARAM)) == (FLAGS_PREC | FLAGS_PRECPARAM)) /* it is not permitted to use both kinds of precision for the same argument */ - return 1; + return PFMT_PRECMIX; break; case 'h': flags |= FLAGS_SHORT; break; #if defined(MP_HAVE_INT_EXTENSIONS) case 'I': + if((fmt[0] == '3') && (fmt[1] == '2')) { + flags |= FLAGS_LONG; + fmt += 2; + } + else if((fmt[0] == '6') && (fmt[1] == '4')) { + flags |= FLAGS_LONGLONG; + fmt += 2; + } + else { #if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) - flags |= FLAGS_LONGLONG; + flags |= FLAGS_LONGLONG; #else - flags |= FLAGS_LONG; + flags |= FLAGS_LONG; #endif + } break; #endif case 'l': @@ -365,401 +399,421 @@ static int dprintf_Pass1(const char *format, struct va_stack *vto, case '0': if(!(flags & FLAGS_LEFT)) flags |= FLAGS_PAD_NIL; - /* FALLTHROUGH */ + FALLTHROUGH(); case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': flags |= FLAGS_WIDTH; - width = strtol(fmt-1, &fmt, 10); + width = 0; + fmt--; + do { + if(width > INT_MAX/10) + return PFMT_WIDTH; + width *= 10; + width += *fmt - '0'; + fmt++; + } while(ISDIGIT(*fmt)); break; - case '*': /* Special case */ + case '*': /* read width from argument list */ flags |= FLAGS_WIDTHPARAM; - param_num++; - - i = dprintf_DollarString(fmt, &fmt); - if(i) - width = i; + if(use_dollar == DOLLAR_USE) { + width = dollarstring(fmt, &fmt); + if(width < 0) + /* illegal combo */ + return PFMT_DOLLARWIDTH; + } else - width = param_num; - if(width > max_param) - max_param = width; + /* pick from the next argument */ + width = -1; break; - case '\0': - fmt--; default: + loopit = FALSE; + fmt--; break; - } - } /* switch */ - - /* Handle the specifier */ - - i = this_param - 1; - - if((i < 0) || (i >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; + } /* switch */ + } /* while */ switch(*fmt) { case 'S': flags |= FLAGS_ALT; - /* FALLTHROUGH */ + FALLTHROUGH(); case 's': - vto[i].type = FORMAT_STRING; + type = FORMAT_STRING; break; case 'n': - vto[i].type = FORMAT_INTPTR; + type = FORMAT_INTPTR; break; case 'p': - vto[i].type = FORMAT_PTR; + type = FORMAT_PTR; break; - case 'd': case 'i': - vto[i].type = FORMAT_INT; + case 'd': + case 'i': + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONG; + else if(flags & FLAGS_LONG) + type = FORMAT_LONG; + else + type = FORMAT_INT; break; case 'u': - vto[i].type = FORMAT_INT; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; flags |= FLAGS_UNSIGNED; break; case 'o': - vto[i].type = FORMAT_INT; + type = FORMAT_INT; flags |= FLAGS_OCTAL; break; case 'x': - vto[i].type = FORMAT_INT; + type = FORMAT_INTU; flags |= FLAGS_HEX|FLAGS_UNSIGNED; break; case 'X': - vto[i].type = FORMAT_INT; + type = FORMAT_INTU; flags |= FLAGS_HEX|FLAGS_UPPER|FLAGS_UNSIGNED; break; case 'c': - vto[i].type = FORMAT_INT; + type = FORMAT_INT; flags |= FLAGS_CHAR; break; case 'f': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; break; case 'e': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATE; break; case 'E': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATE|FLAGS_UPPER; break; case 'g': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATG; break; case 'G': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATG|FLAGS_UPPER; break; default: - vto[i].type = FORMAT_UNKNOWN; - break; + /* invalid instruction, disregard and continue */ + continue; } /* switch */ - vto[i].flags = flags; - vto[i].width = width; - vto[i].precision = precision; - if(flags & FLAGS_WIDTHPARAM) { - /* we have the width specified from a parameter, so we make that - parameter's info setup properly */ - long k = width - 1; - if((k < 0) || (k >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; - vto[i].width = k; - vto[k].type = FORMAT_WIDTH; - vto[k].flags = FLAGS_NEW; - /* can't use width or precision of width! */ - vto[k].width = 0; - vto[k].precision = 0; + if(width < 0) + width = param_num++; + else { + /* if this identifies a parameter already used, this + is illegal */ + if(usedinput[width/8] & (1 << (width&7))) + return PFMT_WIDTHARG; + } + if(width >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(width >= max_param) + max_param = width; + + in[width].type = FORMAT_WIDTH; + /* mark as used */ + usedinput[width/8] |= (unsigned char)(1 << (width&7)); } + if(flags & FLAGS_PRECPARAM) { - /* we have the precision specified from a parameter, so we make that - parameter's info setup properly */ - long k = precision - 1; - if((k < 0) || (k >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; - vto[i].precision = k; - vto[k].type = FORMAT_WIDTH; - vto[k].flags = FLAGS_NEW; - /* can't use width or precision of width! */ - vto[k].width = 0; - vto[k].precision = 0; + if(precision < 0) + precision = param_num++; + else { + /* if this identifies a parameter already used, this + is illegal */ + if(usedinput[precision/8] & (1 << (precision&7))) + return PFMT_PRECARG; + } + if(precision >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(precision >= max_param) + max_param = precision; + + in[precision].type = FORMAT_PRECISION; + usedinput[precision/8] |= (unsigned char)(1 << (precision&7)); } - *endpos++ = fmt + ((*fmt == '\0') ? 0 : 1); /* end of this sequence */ + + /* Handle the specifier */ + if(param < 0) + param = param_num++; + if(param >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(param >= max_param) + max_param = param; + + iptr = &in[param]; + iptr->type = type; + + /* mark this input as used */ + usedinput[param/8] |= (unsigned char)(1 << (param&7)); + + fmt++; + optr = &out[ocount++]; + if(ocount > MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = param; + optr->flags = flags; + optr->width = width; + optr->precision = precision; + optr->start = start; + optr->outlen = outlen; + start = fmt; } + else + fmt++; } - /* Read the arg list parameters into our data list */ - for(i = 0; i<max_param; i++) { - /* Width/precision arguments must be read before the main argument - they are attached to */ - if(vto[i].flags & FLAGS_WIDTHPARAM) { - vto[vto[i].width].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, int); - } - if(vto[i].flags & FLAGS_PRECPARAM) { - vto[vto[i].precision].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, int); - } + /* is there a trailing piece */ + outlen = fmt - start; + if(outlen) { + optr = &out[ocount++]; + if(ocount > MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = 0; + optr->flags = FLAGS_SUBSTR; + optr->start = start; + optr->outlen = outlen; + } - switch(vto[i].type) { + /* Read the arg list parameters into our data list */ + for(i = 0; i < max_param + 1; i++) { + struct va_input *iptr = &in[i]; + if(!(usedinput[i/8] & (1 << (i&7)))) + /* bad input */ + return PFMT_INPUTGAP; + + /* based on the type, read the correct argument */ + switch(iptr->type) { case FORMAT_STRING: - vto[i].data.str = va_arg(arglist, char *); + iptr->val.str = va_arg(arglist, char *); break; case FORMAT_INTPTR: - case FORMAT_UNKNOWN: case FORMAT_PTR: - vto[i].data.ptr = va_arg(arglist, void *); + iptr->val.ptr = va_arg(arglist, void *); break; - case FORMAT_INT: -#ifdef HAVE_LONG_LONG_TYPE - if((vto[i].flags & FLAGS_LONGLONG) && (vto[i].flags & FLAGS_UNSIGNED)) - vto[i].data.num.as_unsigned = - (mp_uintmax_t)va_arg(arglist, mp_uintmax_t); - else if(vto[i].flags & FLAGS_LONGLONG) - vto[i].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, mp_intmax_t); - else -#endif - { - if((vto[i].flags & FLAGS_LONG) && (vto[i].flags & FLAGS_UNSIGNED)) - vto[i].data.num.as_unsigned = - (mp_uintmax_t)va_arg(arglist, unsigned long); - else if(vto[i].flags & FLAGS_LONG) - vto[i].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, long); - else if(vto[i].flags & FLAGS_UNSIGNED) - vto[i].data.num.as_unsigned = - (mp_uintmax_t)va_arg(arglist, unsigned int); - else - vto[i].data.num.as_signed = - (mp_intmax_t)va_arg(arglist, int); - } + case FORMAT_LONGLONGU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, mp_uintmax_t); break; - case FORMAT_DOUBLE: - vto[i].data.dnum = va_arg(arglist, double); + case FORMAT_LONGLONG: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, mp_intmax_t); + break; + + case FORMAT_LONGU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned long); + break; + + case FORMAT_LONG: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, long); + break; + + case FORMAT_INTU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned int); break; + case FORMAT_INT: case FORMAT_WIDTH: - /* Argument has been read. Silently convert it into an integer - * for later use - */ - vto[i].type = FORMAT_INT; + case FORMAT_PRECISION: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, int); + break; + + case FORMAT_DOUBLE: + iptr->val.dnum = va_arg(arglist, double); break; default: + DEBUGASSERT(NULL); /* unexpected */ break; } } + *ipieces = max_param + 1; + *opieces = ocount; - return 0; - + return PFMT_OK; } -static int dprintf_formatf( - void *data, /* untouched by format(), just sent to the stream() function in - the second argument */ +/* + * formatf() - the general printf function. + * + * It calls parsefmt() to parse the format string. It populates two arrays; + * one that describes the input arguments and one that describes a number of + * output segments. + * + * On success, the input array describes the type of all arguments and their + * values. + * + * The function then iterates over the output sengments and outputs them one + * by one until done. Using the appropriate input arguments (if any). + * + * All output is sent to the 'stream()' callback, one byte at a time. + */ + +static int formatf( + void *userp, /* untouched by format(), just sent to the stream() function in + the second argument */ /* function pointer called for each output character */ - int (*stream)(int, FILE *), + int (*stream)(unsigned char, void *), const char *format, /* %-formatted string */ va_list ap_save) /* list of parameters */ { - /* Base-36 digits for numbers. */ - const char *digits = lower_digits; - - /* Pointer into the format string. */ - char *f; - - /* Number of characters written. */ - int done = 0; - - long param; /* current parameter to read */ - long param_num = 0; /* parameter counter */ - - struct va_stack vto[MAX_PARAMETERS]; - char *endpos[MAX_PARAMETERS]; - char **end; + static const char nilstr[] = "(nil)"; + const char *digits = lower_digits; /* Base-36 digits for numbers. */ + int done = 0; /* number of characters written */ + int i; + int ocount = 0; /* number of output segments */ + int icount = 0; /* number of input arguments */ + + struct outsegment output[MAX_SEGMENTS]; + struct va_input input[MAX_PARAMETERS]; char work[BUFFSIZE]; - struct va_stack *p; /* 'workend' points to the final buffer byte position, but with an extra byte as margin to avoid the (false?) warning Coverity gives us otherwise */ char *workend = &work[sizeof(work) - 2]; - /* Do the actual %-code parsing */ - if(dprintf_Pass1(format, vto, endpos, ap_save)) + /* Parse the format string */ + if(parsefmt(format, output, input, &ocount, &icount, ap_save)) return 0; - end = &endpos[0]; /* the initial end-position from the list dprintf_Pass1() - created for us */ - - f = (char *)format; - while(*f != '\0') { - /* Format spec modifiers. */ - int is_alt; - - /* Width of a field. */ - long width; - - /* Precision of a field. */ - long prec; - - /* Decimal integer is negative. */ - int is_neg; - - /* Base of a number to be written. */ - unsigned long base; - - /* Integral values to be written. */ - mp_uintmax_t num; - - /* Used to convert negative in positive. */ - mp_intmax_t signed_num; - + for(i = 0; i < ocount; i++) { + struct outsegment *optr = &output[i]; + struct va_input *iptr; + bool is_alt; /* Format spec modifiers. */ + int width; /* Width of a field. */ + int prec; /* Precision of a field. */ + bool is_neg; /* Decimal integer is negative. */ + unsigned long base; /* Base of a number to be written. */ + mp_uintmax_t num; /* Integral values to be written. */ + mp_intmax_t signed_num; /* Used to convert negative in positive. */ char *w; - - if(*f != '%') { - /* This isn't a format spec, so write everything out until the next one - OR end of string is reached. */ - do { - OUTCHAR(*f); - } while(*++f && ('%' != *f)); - continue; + size_t outlen = optr->outlen; + int flags = optr->flags; + + if(outlen) { + char *str = optr->start; + for(; outlen && *str; outlen--) + OUTCHAR(*str++); + if(optr->flags & FLAGS_SUBSTR) + /* this is just a substring */ + continue; } - ++f; - - /* Check for "%%". Note that although the ANSI standard lists - '%' as a conversion specifier, it says "The complete format - specification shall be `%%'," so we can avoid all the width - and precision processing. */ - if(*f == '%') { - ++f; - OUTCHAR('%'); - continue; - } - - /* If this is a positional parameter, the position must follow immediately - after the %, thus create a %<num>$ sequence */ - param = dprintf_DollarString(f, &f); - - if(!param) - param = param_num; - else - --param; - - param_num++; /* increase this always to allow "%2$s %1$s %s" and then the - third %s will pick the 3rd argument */ - - p = &vto[param]; - /* pick up the specified width */ - if(p->flags & FLAGS_WIDTHPARAM) { - width = (long)vto[p->width].data.num.as_signed; - param_num++; /* since the width is extracted from a parameter, we - must skip that to get to the next one properly */ + if(flags & FLAGS_WIDTHPARAM) { + width = (int)input[optr->width].val.nums; if(width < 0) { /* "A negative field width is taken as a '-' flag followed by a positive field width." */ - width = -width; - p->flags |= FLAGS_LEFT; - p->flags &= ~FLAGS_PAD_NIL; + if(width == INT_MIN) + width = INT_MAX; + else + width = -width; + flags |= FLAGS_LEFT; + flags &= ~FLAGS_PAD_NIL; } } else - width = p->width; + width = optr->width; /* pick up the specified precision */ - if(p->flags & FLAGS_PRECPARAM) { - prec = (long)vto[p->precision].data.num.as_signed; - param_num++; /* since the precision is extracted from a parameter, we - must skip that to get to the next one properly */ + if(flags & FLAGS_PRECPARAM) { + prec = (int)input[optr->precision].val.nums; if(prec < 0) /* "A negative precision is taken as if the precision were omitted." */ prec = -1; } - else if(p->flags & FLAGS_PREC) - prec = p->precision; + else if(flags & FLAGS_PREC) + prec = optr->precision; else prec = -1; - is_alt = (p->flags & FLAGS_ALT) ? 1 : 0; + is_alt = (flags & FLAGS_ALT) ? 1 : 0; + iptr = &input[optr->input]; - switch(p->type) { + switch(iptr->type) { + case FORMAT_INTU: + case FORMAT_LONGU: + case FORMAT_LONGLONGU: + flags |= FLAGS_UNSIGNED; + FALLTHROUGH(); case FORMAT_INT: - num = p->data.num.as_unsigned; - if(p->flags & FLAGS_CHAR) { + case FORMAT_LONG: + case FORMAT_LONGLONG: + num = iptr->val.numu; + if(flags & FLAGS_CHAR) { /* Character. */ - if(!(p->flags & FLAGS_LEFT)) + if(!(flags & FLAGS_LEFT)) while(--width > 0) OUTCHAR(' '); OUTCHAR((char) num); - if(p->flags & FLAGS_LEFT) + if(flags & FLAGS_LEFT) while(--width > 0) OUTCHAR(' '); break; } - if(p->flags & FLAGS_OCTAL) { - /* Octal unsigned integer. */ + if(flags & FLAGS_OCTAL) { + /* Octal unsigned integer */ base = 8; - goto unsigned_number; + is_neg = FALSE; } - else if(p->flags & FLAGS_HEX) { - /* Hexadecimal unsigned integer. */ - - digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; + else if(flags & FLAGS_HEX) { + /* Hexadecimal unsigned integer */ + digits = (flags & FLAGS_UPPER)? upper_digits : lower_digits; base = 16; - goto unsigned_number; + is_neg = FALSE; } - else if(p->flags & FLAGS_UNSIGNED) { - /* Decimal unsigned integer. */ + else if(flags & FLAGS_UNSIGNED) { + /* Decimal unsigned integer */ base = 10; - goto unsigned_number; + is_neg = FALSE; } + else { + /* Decimal integer. */ + base = 10; - /* Decimal integer. */ - base = 10; - - is_neg = (p->data.num.as_signed < (mp_intmax_t)0) ? 1 : 0; - if(is_neg) { - /* signed_num might fail to hold absolute negative minimum by 1 */ - signed_num = p->data.num.as_signed + (mp_intmax_t)1; - signed_num = -signed_num; - num = (mp_uintmax_t)signed_num; - num += (mp_uintmax_t)1; + is_neg = (iptr->val.nums < (mp_intmax_t)0); + if(is_neg) { + /* signed_num might fail to hold absolute negative minimum by 1 */ + signed_num = iptr->val.nums + (mp_intmax_t)1; + signed_num = -signed_num; + num = (mp_uintmax_t)signed_num; + num += (mp_uintmax_t)1; + } } - - goto number; - -unsigned_number: - /* Unsigned number of base BASE. */ - is_neg = 0; - number: - /* Number of base BASE. */ - /* Supply a default precision if none was given. */ if(prec == -1) prec = 1; /* Put the number in WORK. */ w = workend; - while(num > 0) { - *w-- = digits[num % base]; - num /= base; + switch(base) { + case 10: + while(num > 0) { + *w-- = (char)('0' + (num % 10)); + num /= 10; + } + break; + default: + while(num > 0) { + *w-- = digits[num % base]; + num /= base; + } + break; } - width -= (long)(workend - w); - prec -= (long)(workend - w); + width -= (int)(workend - w); + prec -= (int)(workend - w); if(is_alt && base == 8 && prec <= 0) { *w-- = '0'; @@ -775,29 +829,29 @@ number: if(is_alt && base == 16) width -= 2; - if(is_neg || (p->flags & FLAGS_SHOWSIGN) || (p->flags & FLAGS_SPACE)) + if(is_neg || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) --width; - if(!(p->flags & FLAGS_LEFT) && !(p->flags & FLAGS_PAD_NIL)) + if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) while(width-- > 0) OUTCHAR(' '); if(is_neg) OUTCHAR('-'); - else if(p->flags & FLAGS_SHOWSIGN) + else if(flags & FLAGS_SHOWSIGN) OUTCHAR('+'); - else if(p->flags & FLAGS_SPACE) + else if(flags & FLAGS_SPACE) OUTCHAR(' '); if(is_alt && base == 16) { OUTCHAR('0'); - if(p->flags & FLAGS_UPPER) + if(flags & FLAGS_UPPER) OUTCHAR('X'); else OUTCHAR('x'); } - if(!(p->flags & FLAGS_LEFT) && (p->flags & FLAGS_PAD_NIL)) + if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) while(width-- > 0) OUTCHAR('0'); @@ -806,219 +860,199 @@ number: OUTCHAR(*w); } - if(p->flags & FLAGS_LEFT) + if(flags & FLAGS_LEFT) while(width-- > 0) OUTCHAR(' '); break; - case FORMAT_STRING: - /* String. */ - { - static const char null[] = "(nil)"; - const char *str; - size_t len; - - str = (char *) p->data.str; - if(!str) { - /* Write null[] if there's space. */ - if(prec == -1 || prec >= (long) sizeof(null) - 1) { - str = null; - len = sizeof(null) - 1; - /* Disable quotes around (nil) */ - p->flags &= (~FLAGS_ALT); - } - else { - str = ""; - len = 0; - } + case FORMAT_STRING: { + const char *str; + size_t len; + + str = (char *)iptr->val.str; + if(!str) { + /* Write null string if there's space. */ + if(prec == -1 || prec >= (int) sizeof(nilstr) - 1) { + str = nilstr; + len = sizeof(nilstr) - 1; + /* Disable quotes around (nil) */ + flags &= (~FLAGS_ALT); } - else if(prec != -1) - len = (size_t)prec; - else if(*str == '\0') + else { + str = ""; len = 0; - else - len = strlen(str); + } + } + else if(prec != -1) + len = (size_t)prec; + else if(*str == '\0') + len = 0; + else + len = strlen(str); - width -= (len > LONG_MAX) ? LONG_MAX : (long)len; + width -= (len > INT_MAX) ? INT_MAX : (int)len; - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); + if(flags & FLAGS_ALT) + OUTCHAR('"'); - if(!(p->flags&FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); + if(!(flags&FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); - for(; len && *str; len--) - OUTCHAR(*str++); - if(p->flags&FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); + for(; len && *str; len--) + OUTCHAR(*str++); + if(flags&FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); - } + if(flags & FLAGS_ALT) + OUTCHAR('"'); break; + } case FORMAT_PTR: /* Generic pointer. */ - { - void *ptr; - ptr = (void *) p->data.ptr; - if(ptr) { - /* If the pointer is not NULL, write it as a %#x spec. */ - base = 16; - digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; - is_alt = 1; - num = (size_t) ptr; - is_neg = 0; - goto number; - } - else { - /* Write "(nil)" for a nil pointer. */ - static const char strnil[] = "(nil)"; - const char *point; - - width -= (long)(sizeof(strnil) - 1); - if(p->flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - for(point = strnil; *point != '\0'; ++point) - OUTCHAR(*point); - if(!(p->flags & FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); - } + if(iptr->val.ptr) { + /* If the pointer is not NULL, write it as a %#x spec. */ + base = 16; + digits = (flags & FLAGS_UPPER)? upper_digits : lower_digits; + is_alt = TRUE; + num = (size_t) iptr->val.ptr; + is_neg = FALSE; + goto number; } - break; + else { + /* Write "(nil)" for a nil pointer. */ + const char *point; - case FORMAT_DOUBLE: - { - char formatbuf[32]="%"; - char *fptr = &formatbuf[1]; - size_t left = sizeof(formatbuf)-strlen(formatbuf); - int len; - - width = -1; - if(p->flags & FLAGS_WIDTH) - width = p->width; - else if(p->flags & FLAGS_WIDTHPARAM) - width = (long)vto[p->width].data.num.as_signed; + width -= (int)(sizeof(nilstr) - 1); + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + for(point = nilstr; *point != '\0'; ++point) + OUTCHAR(*point); + if(!(flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + } + break; - prec = -1; - if(p->flags & FLAGS_PREC) - prec = p->precision; - else if(p->flags & FLAGS_PRECPARAM) - prec = (long)vto[p->precision].data.num.as_signed; - - if(p->flags & FLAGS_LEFT) - *fptr++ = '-'; - if(p->flags & FLAGS_SHOWSIGN) - *fptr++ = '+'; - if(p->flags & FLAGS_SPACE) - *fptr++ = ' '; - if(p->flags & FLAGS_ALT) - *fptr++ = '#'; - - *fptr = 0; - - if(width >= 0) { - if(width >= (long)sizeof(work)) - width = sizeof(work)-1; - /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, "%ld", width); - fptr += len; - left -= len; + case FORMAT_DOUBLE: { + char formatbuf[32]="%"; + char *fptr = &formatbuf[1]; + size_t left = sizeof(formatbuf)-strlen(formatbuf); + int len; + + if(flags & FLAGS_WIDTH) + width = optr->width; + + if(flags & FLAGS_PREC) + prec = optr->precision; + + if(flags & FLAGS_LEFT) + *fptr++ = '-'; + if(flags & FLAGS_SHOWSIGN) + *fptr++ = '+'; + if(flags & FLAGS_SPACE) + *fptr++ = ' '; + if(flags & FLAGS_ALT) + *fptr++ = '#'; + + *fptr = 0; + + if(width >= 0) { + if(width >= (int)sizeof(work)) + width = sizeof(work)-1; + /* RECURSIVE USAGE */ + len = curl_msnprintf(fptr, left, "%d", width); + fptr += len; + left -= len; + } + if(prec >= 0) { + /* for each digit in the integer part, we can have one less + precision */ + size_t maxprec = sizeof(work) - 2; + double val = iptr->val.dnum; + if(width > 0 && prec <= width) + maxprec -= width; + while(val >= 10.0) { + val /= 10; + maxprec--; } - if(prec >= 0) { - /* for each digit in the integer part, we can have one less - precision */ - size_t maxprec = sizeof(work) - 2; - double val = p->data.dnum; - if(width > 0 && prec <= width) - maxprec -= width; - while(val >= 10.0) { - val /= 10; - maxprec--; - } - if(prec > (long)maxprec) - prec = (long)maxprec-1; - if(prec < 0) - prec = 0; - /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, ".%ld", prec); - fptr += len; - } - if(p->flags & FLAGS_LONG) - *fptr++ = 'l'; + if(prec > (int)maxprec) + prec = (int)maxprec-1; + if(prec < 0) + prec = 0; + /* RECURSIVE USAGE */ + len = curl_msnprintf(fptr, left, ".%d", prec); + fptr += len; + } + if(flags & FLAGS_LONG) + *fptr++ = 'l'; - if(p->flags & FLAGS_FLOATE) - *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'E':'e'); - else if(p->flags & FLAGS_FLOATG) - *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'G' : 'g'); - else - *fptr++ = 'f'; + if(flags & FLAGS_FLOATE) + *fptr++ = (char)((flags & FLAGS_UPPER) ? 'E':'e'); + else if(flags & FLAGS_FLOATG) + *fptr++ = (char)((flags & FLAGS_UPPER) ? 'G' : 'g'); + else + *fptr++ = 'f'; - *fptr = 0; /* and a final null-termination */ + *fptr = 0; /* and a final null-termination */ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" #endif - /* NOTE NOTE NOTE!! Not all sprintf implementations return number of - output characters */ + /* NOTE NOTE NOTE!! Not all sprintf implementations return number of + output characters */ #ifdef HAVE_SNPRINTF - (snprintf)(work, sizeof(work), formatbuf, p->data.dnum); + (snprintf)(work, sizeof(work), formatbuf, iptr->val.dnum); #else - (sprintf)(work, formatbuf, p->data.dnum); + (sprintf)(work, formatbuf, iptr->val.dnum); #endif #ifdef __clang__ #pragma clang diagnostic pop #endif - DEBUGASSERT(strlen(work) <= sizeof(work)); - for(fptr = work; *fptr; fptr++) - OUTCHAR(*fptr); - } + DEBUGASSERT(strlen(work) <= sizeof(work)); + for(fptr = work; *fptr; fptr++) + OUTCHAR(*fptr); break; + } case FORMAT_INTPTR: /* Answer the count of characters written. */ #ifdef HAVE_LONG_LONG_TYPE - if(p->flags & FLAGS_LONGLONG) - *(LONG_LONG_TYPE *) p->data.ptr = (LONG_LONG_TYPE)done; + if(flags & FLAGS_LONGLONG) + *(LONG_LONG_TYPE *) iptr->val.ptr = (LONG_LONG_TYPE)done; else #endif - if(p->flags & FLAGS_LONG) - *(long *) p->data.ptr = (long)done; - else if(!(p->flags & FLAGS_SHORT)) - *(int *) p->data.ptr = (int)done; + if(flags & FLAGS_LONG) + *(long *) iptr->val.ptr = (long)done; + else if(!(flags & FLAGS_SHORT)) + *(int *) iptr->val.ptr = (int)done; else - *(short *) p->data.ptr = (short)done; + *(short *) iptr->val.ptr = (short)done; break; default: break; } - f = *end++; /* goto end of %-code */ - } return done; } /* fputc() look-alike */ -static int addbyter(int output, FILE *data) +static int addbyter(unsigned char outc, void *f) { - struct nsprintf *infop = (struct nsprintf *)data; - unsigned char outc = (unsigned char)output; - + struct nsprintf *infop = f; if(infop->length < infop->max) { /* only do this if we haven't reached max length yet */ - infop->buffer[0] = outc; /* store */ - infop->buffer++; /* increase pointer */ + *infop->buffer++ = outc; /* store */ infop->length++; /* we are now one byte larger */ - return outc; /* fputc() returns like this on success */ + return 0; /* fputc() returns like this on success */ } - return -1; + return 1; } int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, @@ -1031,7 +1065,7 @@ int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, info.length = 0; info.max = maxlength; - retcode = dprintf_formatf(&info, addbyter, format, ap_save); + retcode = formatf(&info, addbyter, format, ap_save); if(info.max) { /* we terminate this with a zero byte */ if(info.max == info.length) { @@ -1057,29 +1091,28 @@ int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) } /* fputc() look-alike */ -static int alloc_addbyter(int output, FILE *data) +static int alloc_addbyter(unsigned char outc, void *f) { - struct asprintf *infop = (struct asprintf *)data; - unsigned char outc = (unsigned char)output; - - if(Curl_dyn_addn(infop->b, &outc, 1)) { - infop->fail = 1; - return -1; /* fail */ + struct asprintf *infop = f; + CURLcode result = Curl_dyn_addn(infop->b, &outc, 1); + if(result) { + infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM; + return 1 ; /* fail */ } - return outc; /* fputc() returns like this on success */ + return 0; } -/* appends the formatted string, returns 0 on success, 1 on error */ +/* appends the formatted string, returns MERR error code */ int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) { struct asprintf info; info.b = dyn; - info.fail = 0; + info.merr = MERR_OK; - (void)dprintf_formatf(&info, alloc_addbyter, format, ap_save); - if(info.fail) { + (void)formatf(&info, alloc_addbyter, format, ap_save); + if(info.merr) { Curl_dyn_free(info.b); - return 1; + return info.merr; } return 0; } @@ -1090,10 +1123,10 @@ char *curl_mvaprintf(const char *format, va_list ap_save) struct dynbuf dyn; info.b = &dyn; Curl_dyn_init(info.b, DYN_APRINTF); - info.fail = 0; + info.merr = MERR_OK; - (void)dprintf_formatf(&info, alloc_addbyter, format, ap_save); - if(info.fail) { + (void)formatf(&info, alloc_addbyter, format, ap_save); + if(info.merr) { Curl_dyn_free(info.b); return NULL; } @@ -1112,13 +1145,12 @@ char *curl_maprintf(const char *format, ...) return s; } -static int storebuffer(int output, FILE *data) +static int storebuffer(unsigned char outc, void *f) { - char **buffer = (char **)data; - unsigned char outc = (unsigned char)output; + char **buffer = f; **buffer = outc; (*buffer)++; - return outc; /* act like fputc() ! */ + return 0; } int curl_msprintf(char *buffer, const char *format, ...) @@ -1126,19 +1158,29 @@ int curl_msprintf(char *buffer, const char *format, ...) va_list ap_save; /* argument pointer */ int retcode; va_start(ap_save, format); - retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + retcode = formatf(&buffer, storebuffer, format, ap_save); va_end(ap_save); *buffer = 0; /* we terminate this with a zero byte */ return retcode; } +static int fputc_wrapper(unsigned char outc, void *f) +{ + int out = outc; + FILE *s = f; + int rc = fputc(out, s); + if(rc == out) + return 0; + return 1; +} + int curl_mprintf(const char *format, ...) { int retcode; va_list ap_save; /* argument pointer */ va_start(ap_save, format); - retcode = dprintf_formatf(stdout, fputc, format, ap_save); + retcode = formatf(stdout, fputc_wrapper, format, ap_save); va_end(ap_save); return retcode; } @@ -1148,25 +1190,24 @@ int curl_mfprintf(FILE *whereto, const char *format, ...) int retcode; va_list ap_save; /* argument pointer */ va_start(ap_save, format); - retcode = dprintf_formatf(whereto, fputc, format, ap_save); + retcode = formatf(whereto, fputc_wrapper, format, ap_save); va_end(ap_save); return retcode; } int curl_mvsprintf(char *buffer, const char *format, va_list ap_save) { - int retcode; - retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + int retcode = formatf(&buffer, storebuffer, format, ap_save); *buffer = 0; /* we terminate this with a zero byte */ return retcode; } int curl_mvprintf(const char *format, va_list ap_save) { - return dprintf_formatf(stdout, fputc, format, ap_save); + return formatf(stdout, fputc_wrapper, format, ap_save); } int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save) { - return dprintf_formatf(whereto, fputc, format, ap_save); + return formatf(whereto, fputc_wrapper, format, ap_save); } diff --git a/Utilities/cmcurl/lib/mqtt.c b/Utilities/cmcurl/lib/mqtt.c index 366235c..5a9d6d0 100644 --- a/Utilities/cmcurl/lib/mqtt.c +++ b/Utilities/cmcurl/lib/mqtt.c @@ -88,7 +88,7 @@ const struct Curl_handler Curl_handler_mqtt = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_MQTT, /* defport */ @@ -524,8 +524,10 @@ static CURLcode mqtt_publish(struct Curl_easy *data) char encodedbytes[4]; curl_off_t postfieldsize = data->set.postfieldsize; - if(!payload) + if(!payload) { + DEBUGF(infof(data, "mqtt_publish without payload, return bad arg")); return CURLE_BAD_FUNCTION_ARGUMENT; + } if(postfieldsize < 0) payloadlen = strlen(payload); else @@ -622,7 +624,6 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done) struct connectdata *conn = data->conn; curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; ssize_t nread; - unsigned char *pkt = (unsigned char *)data->state.buffer; size_t remlen; struct mqtt_conn *mqtt = &conn->proto.mqtt; struct MQTT *mq = data->req.p.mqtt; @@ -671,13 +672,14 @@ MQTT_SUBACK_COMING: data->req.bytecount = 0; data->req.size = remlen; mq->npacket = remlen; /* get this many bytes */ - /* FALLTHROUGH */ + FALLTHROUGH(); case MQTT_PUB_REMAIN: { /* read rest of packet, but no more. Cap to buffer size */ + char buffer[4*1024]; size_t rest = mq->npacket; - if(rest > (size_t)data->set.buffer_size) - rest = (size_t)data->set.buffer_size; - result = Curl_read(data, sockfd, (char *)pkt, rest, &nread); + if(rest > sizeof(buffer)) + rest = sizeof(buffer); + result = Curl_read(data, sockfd, buffer, rest, &nread); if(result) { if(CURLE_AGAIN == result) { infof(data, "EEEE AAAAGAIN"); @@ -690,14 +692,12 @@ MQTT_SUBACK_COMING: goto end; } - mq->npacket -= nread; - /* if QoS is set, message contains packet id */ - - result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)pkt, nread); + result = Curl_client_write(data, CLIENTWRITE_BODY, buffer, nread); if(result) goto end; + mq->npacket -= nread; if(!mq->npacket) /* no more PUBLISH payload, back to subscribe wait state */ mqstate(data, MQTT_FIRST, MQTT_PUBWAIT); @@ -745,7 +745,6 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) struct MQTT *mq = data->req.p.mqtt; ssize_t nread; curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - unsigned char *pkt = (unsigned char *)data->state.buffer; unsigned char byte; *done = FALSE; @@ -776,14 +775,14 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) /* remember the first byte */ mq->npacket = 0; mqstate(data, MQTT_REMAINING_LENGTH, MQTT_NOSTATE); - /* FALLTHROUGH */ + FALLTHROUGH(); case MQTT_REMAINING_LENGTH: do { result = Curl_read(data, sockfd, (char *)&byte, 1, &nread); if(!nread) break; Curl_debug(data, CURLINFO_HEADER_IN, (char *)&byte, 1); - pkt[mq->npacket++] = byte; + mq->pkt_hd[mq->npacket++] = byte; } while((byte & 0x80) && (mq->npacket < 4)); if(nread && (byte & 0x80)) /* MQTT supports up to 127 * 128^0 + 127 * 128^1 + 127 * 128^2 + @@ -791,7 +790,7 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) result = CURLE_WEIRD_SERVER_REPLY; if(result) break; - mq->remaining_length = mqtt_decode_len(&pkt[0], mq->npacket, NULL); + mq->remaining_length = mqtt_decode_len(mq->pkt_hd, mq->npacket, NULL); mq->npacket = 0; if(mq->remaining_length) { mqstate(data, mqtt->nextstate, MQTT_NOSTATE); diff --git a/Utilities/cmcurl/lib/mqtt.h b/Utilities/cmcurl/lib/mqtt.h index 84f1770..99ab12a 100644 --- a/Utilities/cmcurl/lib/mqtt.h +++ b/Utilities/cmcurl/lib/mqtt.h @@ -57,6 +57,7 @@ struct MQTT { 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 5456113..0926b0d 100644 --- a/Utilities/cmcurl/lib/multi.c +++ b/Utilities/cmcurl/lib/multi.c @@ -672,6 +672,7 @@ static CURLcode multi_done(struct Curl_easy *data, many callbacks and protocols work differently, we could potentially do this more fine-grained in the future. */ premature = TRUE; + FALLTHROUGH(); default: break; } @@ -993,31 +994,92 @@ void Curl_attach_connection(struct Curl_easy *data, Curl_conn_ev_data_attach(conn, data); } -static int domore_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +static int connecting_getsock(struct Curl_easy *data, curl_socket_t *socks) { + struct connectdata *conn = data->conn; + (void)socks; + /* Not using `conn->sockfd` as `Curl_setup_transfer()` initializes + * that *after* the connect. */ + if(conn && conn->sock[FIRSTSOCKET] != CURL_SOCKET_BAD) { + /* Default is to wait to something from the server */ + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(0); + } + return GETSOCK_BLANK; +} + +static int protocol_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + struct connectdata *conn = data->conn; + if(conn && conn->handler->proto_getsock) + return conn->handler->proto_getsock(data, conn, socks); + else if(conn && conn->sockfd != CURL_SOCKET_BAD) { + /* Default is to wait to something from the server */ + socks[0] = conn->sockfd; + return GETSOCK_READSOCK(0); + } + return GETSOCK_BLANK; +} + +static int domore_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + struct connectdata *conn = data->conn; if(conn && conn->handler->domore_getsock) return conn->handler->domore_getsock(data, conn, socks); + else if(conn && conn->sockfd != CURL_SOCKET_BAD) { + /* Default is that we want to send something to the server */ + socks[0] = conn->sockfd; + return GETSOCK_WRITESOCK(0); + } return GETSOCK_BLANK; } -static int doing_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +static int doing_getsock(struct Curl_easy *data, curl_socket_t *socks) { + struct connectdata *conn = data->conn; if(conn && conn->handler->doing_getsock) return conn->handler->doing_getsock(data, conn, socks); + else if(conn && conn->sockfd != CURL_SOCKET_BAD) { + /* Default is that we want to send something to the server */ + socks[0] = conn->sockfd; + return GETSOCK_WRITESOCK(0); + } return GETSOCK_BLANK; } -static int protocol_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) +static int perform_getsock(struct Curl_easy *data, curl_socket_t *sock) { - if(conn->handler->proto_getsock) - return conn->handler->proto_getsock(data, conn, socks); - return GETSOCK_BLANK; + struct connectdata *conn = data->conn; + + if(!conn) + return GETSOCK_BLANK; + else if(conn->handler->perform_getsock) + return conn->handler->perform_getsock(data, conn, sock); + else { + /* Default is to obey the data->req.keepon flags for send/recv */ + int bitmap = GETSOCK_BLANK; + unsigned sockindex = 0; + if(CURL_WANT_RECV(data)) { + DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); + bitmap |= GETSOCK_READSOCK(sockindex); + sock[sockindex] = conn->sockfd; + } + + if(CURL_WANT_SEND(data)) { + if((conn->sockfd != conn->writesockfd) || + bitmap == GETSOCK_BLANK) { + /* only if they are not the same socket and we have a readable + one, we increase index */ + if(bitmap != GETSOCK_BLANK) + sockindex++; /* increase index if we need two entries */ + + DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); + sock[sockindex] = conn->writesockfd; + } + bitmap |= GETSOCK_WRITESOCK(sockindex); + } + return bitmap; + } } /* Initializes `poll_set` with the current socket poll actions needed @@ -1033,45 +1095,61 @@ static void multi_getsock(struct Curl_easy *data, return; switch(data->mstate) { - default: + case MSTATE_INIT: + case MSTATE_PENDING: + case MSTATE_CONNECT: + /* nothing to poll for yet */ break; case MSTATE_RESOLVING: - Curl_pollset_add_socks2(data, ps, Curl_resolv_getsock); + Curl_pollset_add_socks(data, ps, Curl_resolv_getsock); /* connection filters are not involved in this phase */ - return; + break; + + case MSTATE_CONNECTING: + case MSTATE_TUNNELING: + Curl_pollset_add_socks(data, ps, connecting_getsock); + Curl_conn_adjust_pollset(data, ps); + break; - case MSTATE_PROTOCONNECTING: case MSTATE_PROTOCONNECT: + case MSTATE_PROTOCONNECTING: Curl_pollset_add_socks(data, ps, protocol_getsock); + Curl_conn_adjust_pollset(data, ps); break; case MSTATE_DO: case MSTATE_DOING: Curl_pollset_add_socks(data, ps, doing_getsock); - break; - - case MSTATE_TUNNELING: - case MSTATE_CONNECTING: + Curl_conn_adjust_pollset(data, ps); break; case MSTATE_DOING_MORE: Curl_pollset_add_socks(data, ps, domore_getsock); + Curl_conn_adjust_pollset(data, ps); break; - case MSTATE_DID: /* since is set after DO is completed, we switch to - waiting for the same as the PERFORMING state */ + case MSTATE_DID: /* same as PERFORMING in regard to polling */ case MSTATE_PERFORMING: - Curl_pollset_add_socks(data, ps, Curl_single_getsock); + Curl_pollset_add_socks(data, ps, perform_getsock); + Curl_conn_adjust_pollset(data, ps); break; case MSTATE_RATELIMITING: - /* nothing to wait for */ - return; - } + /* we need to let time pass, ignore socket(s) */ + break; + + case MSTATE_DONE: + case MSTATE_COMPLETED: + case MSTATE_MSGSENT: + /* nothing more to poll for */ + break; - /* Let connection filters add/remove as needed */ - Curl_conn_adjust_pollset(data, ps); + default: + failf(data, "multi_getsock: unexpected multi state %d", data->mstate); + DEBUGASSERT(0); + break; + } } CURLMcode curl_multi_fdset(struct Curl_multi *multi, @@ -1942,6 +2020,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } if(!result) { + *nowp = Curl_pgrsTime(data, TIMER_POSTQUEUE); if(async) /* We're now waiting for an asynchronous name lookup */ multistate(data, MSTATE_RESOLVING); @@ -1983,8 +2062,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(dns) { #ifdef CURLRES_ASYNCH - conn->resolve_async.dns = dns; - conn->resolve_async.done = TRUE; + data->state.async.dns = dns; + data->state.async.done = TRUE; #endif result = CURLE_OK; infof(data, "Hostname '%s' was found in DNS cache", hostname); @@ -2371,7 +2450,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, { char *newurl = NULL; bool retry = FALSE; - bool comeback = FALSE; DEBUGASSERT(data->state.buffer); /* check if over send speed */ send_timeout_ms = 0; @@ -2402,7 +2480,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } /* read/write data if it is ready to do so */ - result = Curl_readwrite(data->conn, data, &done, &comeback); + result = Curl_readwrite(data, &done); if(done || (result == CURLE_RECV_ERROR)) { /* If CURLE_RECV_ERROR happens early enough, we assume it was a race @@ -2512,7 +2590,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } } } - else if(comeback) { + else if(data->state.select_bits) { /* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer won't get stuck on this transfer at the expense of other concurrent transfers */ @@ -3164,7 +3242,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, if(data->conn && !(data->conn->handler->flags & PROTOPT_DIRLOCK)) /* set socket event bitmask if they're not locked */ - data->conn->cselect_bits = (unsigned char)ev_bitmask; + data->state.select_bits = (unsigned char)ev_bitmask; Curl_expire(data, 0, EXPIRE_RUN_NOW); } diff --git a/Utilities/cmcurl/lib/noproxy.c b/Utilities/cmcurl/lib/noproxy.c index 2b9908d..5241640 100644 --- a/Utilities/cmcurl/lib/noproxy.c +++ b/Utilities/cmcurl/lib/noproxy.c @@ -216,7 +216,6 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy, /* case C passes through, not a match */ break; case TYPE_IPV4: - /* FALLTHROUGH */ case TYPE_IPV6: { const char *check = token; char *slash; diff --git a/Utilities/cmcurl/lib/openldap.c b/Utilities/cmcurl/lib/openldap.c index 131f474..1e60ff7 100644 --- a/Utilities/cmcurl/lib/openldap.c +++ b/Utilities/cmcurl/lib/openldap.c @@ -130,7 +130,7 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ @@ -158,7 +158,7 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ oldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_LDAPS, /* defport */ @@ -645,7 +645,7 @@ static CURLcode oldap_state_mechs_resp(struct Curl_easy *data, switch(code) { case LDAP_SIZELIMIT_EXCEEDED: infof(data, "Too many authentication mechanisms\n"); - /* FALLTHROUGH */ + FALLTHROUGH(); case LDAP_SUCCESS: case LDAP_NO_RESULTS_RETURNED: if(Curl_sasl_can_authenticate(&li->sasl, data)) @@ -793,10 +793,13 @@ static CURLcode oldap_connecting(struct Curl_easy *data, bool *done) result = oldap_perform_bind(data, OLDAP_BIND); break; } - /* FALLTHROUGH */ + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) + break; + FALLTHROUGH(); case OLDAP_TLS: result = oldap_ssl_connect(data, OLDAP_TLS); - if(result && data->set.use_ssl != CURLUSESSL_TRY) + if(result) result = oldap_map_error(code, CURLE_USE_SSL_FAILED); else if(ssl_installed(conn)) { conn->bits.tls_upgraded = TRUE; @@ -887,10 +890,14 @@ static CURLcode oldap_do(struct Curl_easy *data, bool *done) result = oldap_url_parse(data, &lud); if(!result) { - 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); +#ifdef USE_SSL + if(ssl_installed(conn)) { + Sockbuf *sb; + /* re-install the libcurl SSL handlers into the sockbuf. */ + ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb); + ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, data); + } +#endif rc = ldap_search_ext(li->ld, lud->lud_dn, lud->lud_scope, lud->lud_filter, lud->lud_attrs, 0, @@ -1014,7 +1021,7 @@ static ssize_t oldap_recv(struct Curl_easy *data, int sockindex, char *buf, switch(code) { case LDAP_SIZELIMIT_EXCEEDED: infof(data, "There are more than %d entries", lr->nument); - /* FALLTHROUGH */ + FALLTHROUGH(); case LDAP_SUCCESS: data->req.size = data->req.bytecount; break; diff --git a/Utilities/cmcurl/lib/pingpong.c b/Utilities/cmcurl/lib/pingpong.c index 0081c9c..b976ffb 100644 --- a/Utilities/cmcurl/lib/pingpong.c +++ b/Utilities/cmcurl/lib/pingpong.c @@ -36,6 +36,7 @@ #include "pingpong.h" #include "multiif.h" #include "vtls/vtls.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -105,7 +106,7 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, if(Curl_conn_data_pending(data, FIRSTSOCKET)) rc = 1; - else if(Curl_pp_moredata(pp)) + else if(pp->overflow) /* We are receiving and there is data in the cache so just read it */ rc = 1; else if(!pp->sendleft && Curl_conn_data_pending(data, FIRSTSOCKET)) @@ -139,19 +140,13 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, } /* initialize stuff to prepare for reading a fresh new response */ -void Curl_pp_init(struct Curl_easy *data, struct pingpong *pp) +void Curl_pp_init(struct pingpong *pp) { - DEBUGASSERT(data); pp->nread_resp = 0; - pp->linestart_resp = data->state.buffer; - pp->pending_resp = TRUE; pp->response = Curl_now(); /* start response time-out now! */ -} - -/* setup for the coming transfer */ -void Curl_pp_setup(struct pingpong *pp) -{ + pp->pending_resp = TRUE; Curl_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); + Curl_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD); } /*********************************************************************** @@ -197,9 +192,9 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, if(result) return result; + pp->pending_resp = TRUE; write_len = Curl_dyn_len(&pp->sendbuf); s = Curl_dyn_ptr(&pp->sendbuf); - Curl_pp_init(data, pp); #ifdef HAVE_GSSAPI conn->data_prot = PROT_CMD; @@ -255,6 +250,25 @@ CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, return result; } +static CURLcode pingpong_read(struct Curl_easy *data, + curl_socket_t sockfd, + char *buffer, + size_t buflen, + ssize_t *nread) +{ + CURLcode result; +#ifdef HAVE_GSSAPI + enum protection_level prot = data->conn->data_prot; + data->conn->data_prot = PROT_CLEAR; +#endif + result = Curl_read(data, sockfd, buffer, buflen, nread); +#ifdef HAVE_GSSAPI + DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); + data->conn->data_prot = (unsigned char)prot; +#endif + return result; +} + /* * Curl_pp_readresp() * @@ -266,181 +280,96 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, int *code, /* return the server code if done */ size_t *size) /* size of the response */ { - ssize_t perline; /* count bytes per line */ - bool keepon = TRUE; - ssize_t gotbytes; - char *ptr; struct connectdata *conn = data->conn; - char * const buf = data->state.buffer; CURLcode result = CURLE_OK; *code = 0; /* 0 for errors or not done */ *size = 0; - ptr = buf + pp->nread_resp; + 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); - /* number of bytes in the current line, so far */ - perline = (ssize_t)(ptr-pp->linestart_resp); + /* trim off the "final" leading part */ + Curl_dyn_tail(&pp->recvbuf, full - pp->nfinal); - while((pp->nread_resp < (size_t)data->set.buffer_size) && - (keepon && !result)) { + pp->nfinal = 0; /* now gone */ + } + if(!pp->overflow) { + ssize_t gotbytes = 0; + char buffer[900]; - if(pp->cache) { - /* we had data in the "cache", copy that instead of doing an actual - * read - * - * pp->cache_size is cast to ssize_t here. This should be safe, because - * it would have been populated with something of size int to begin - * with, even though its datatype may be larger than an int. - */ - if((ptr + pp->cache_size) > (buf + data->set.buffer_size + 1)) { - failf(data, "cached response data too big to handle"); - return CURLE_WEIRD_SERVER_REPLY; - } - memcpy(ptr, pp->cache, pp->cache_size); - gotbytes = (ssize_t)pp->cache_size; - free(pp->cache); /* free the cache */ - pp->cache = NULL; /* clear the pointer */ - pp->cache_size = 0; /* zero the size just in case */ - } - else { -#ifdef HAVE_GSSAPI - enum protection_level prot = conn->data_prot; - conn->data_prot = PROT_CLEAR; -#endif - DEBUGASSERT((ptr + data->set.buffer_size - pp->nread_resp) <= - (buf + data->set.buffer_size + 1)); - result = Curl_read(data, sockfd, ptr, - data->set.buffer_size - pp->nread_resp, - &gotbytes); -#ifdef HAVE_GSSAPI - DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); - conn->data_prot = (unsigned char)prot; -#endif - if(result == CURLE_AGAIN) - return CURLE_OK; /* return */ + result = pingpong_read(data, sockfd, buffer, sizeof(buffer), &gotbytes); + if(result == CURLE_AGAIN) + return CURLE_OK; - if(result) - /* Set outer result variable to this error. */ - keepon = FALSE; - } + if(result) + return result; - if(!keepon) - ; - else if(gotbytes <= 0) { - keepon = FALSE; - result = CURLE_RECV_ERROR; + if(gotbytes <= 0) { failf(data, "response reading failed (errno: %d)", SOCKERRNO); + return CURLE_RECV_ERROR; } - else { - /* we got a whole chunk of data, which can be anything from one - * byte to a set of lines and possible just a piece of the last - * line */ - ssize_t i; - ssize_t clipamount = 0; - bool restart = FALSE; - - data->req.headerbytecount += (unsigned int)gotbytes; - - pp->nread_resp += gotbytes; - for(i = 0; i < gotbytes; ptr++, i++) { - perline++; - if(*ptr == '\n') { - /* a newline is CRLF in pp-talk, so the CR is ignored as - the line isn't really terminated until the LF comes */ - - /* output debug output if that is requested */ + + result = Curl_dyn_addn(&pp->recvbuf, buffer, gotbytes); + if(result) + return result; + + data->req.headerbytecount += (unsigned int)gotbytes; + + pp->nread_resp += gotbytes; + } + + do { + char *line = Curl_dyn_ptr(&pp->recvbuf); + char *nl = memchr(line, '\n', Curl_dyn_len(&pp->recvbuf)); + if(nl) { + /* a newline is CRLF in pp-talk, so the CR is ignored as + the line isn't really terminated until the LF comes */ + size_t length = nl - line + 1; + + /* output debug output if that is requested */ #ifdef HAVE_GSSAPI - if(!conn->sec_complete) + if(!conn->sec_complete) #endif - Curl_debug(data, CURLINFO_HEADER_IN, - pp->linestart_resp, (size_t)perline); - - /* - * We pass all response-lines to the callback function registered - * for "headers". The response lines can be seen as a kind of - * headers. - */ - result = Curl_client_write(data, CLIENTWRITE_INFO, - pp->linestart_resp, perline); - if(result) - return result; - - if(pp->endofresp(data, conn, pp->linestart_resp, perline, code)) { - /* This is the end of the last line, copy the last line to the - start of the buffer and null-terminate, for old times sake */ - size_t n = ptr - pp->linestart_resp; - memmove(buf, pp->linestart_resp, n); - buf[n] = 0; /* null-terminate */ - keepon = FALSE; - pp->linestart_resp = ptr + 1; /* advance pointer */ - i++; /* skip this before getting out */ - - *size = pp->nread_resp; /* size of the response */ - pp->nread_resp = 0; /* restart */ - break; - } - perline = 0; /* line starts over here */ - pp->linestart_resp = ptr + 1; - } - } + Curl_debug(data, CURLINFO_HEADER_IN, line, length); - if(!keepon && (i != gotbytes)) { - /* We found the end of the response lines, but we didn't parse the - full chunk of data we have read from the server. We therefore need - to store the rest of the data to be checked on the next invoke as - it may actually contain another end of response already! */ - clipamount = gotbytes - i; - restart = TRUE; - DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing " - "server response left", - (int)clipamount)); - } - else if(keepon) { - - if((perline == gotbytes) && - (gotbytes > (ssize_t)data->set.buffer_size/2)) { - /* We got an excessive line without newlines and we need to deal - with it. We keep the first bytes of the line then we throw - away the rest. */ - infof(data, "Excessive server response line length received, " - "%zd bytes. Stripping", gotbytes); - restart = TRUE; - - /* we keep 40 bytes since all our pingpong protocols are only - interested in the first piece */ - clipamount = 40; - } - else if(pp->nread_resp > (size_t)data->set.buffer_size/2) { - /* We got a large chunk of data and there's potentially still - trailing data to take care of, so we put any such part in the - "cache", clear the buffer to make space and restart. */ - clipamount = perline; - restart = TRUE; - } - } - else if(i == gotbytes) - restart = TRUE; - - if(clipamount) { - pp->cache_size = clipamount; - pp->cache = malloc(pp->cache_size); - if(pp->cache) - memcpy(pp->cache, pp->linestart_resp, pp->cache_size); + /* + * Pass all response-lines to the callback function registered for + * "headers". The response lines can be seen as a kind of headers. + */ + result = Curl_client_write(data, CLIENTWRITE_INFO, line, length); + if(result) + return result; + + if(pp->endofresp(data, conn, line, length, code)) { + /* When at "end of response", keep the endofresp line first in the + buffer since it will be accessed outside (by pingpong + parsers). Store the overflow counter to inform about additional + data in this buffer after the endofresp line. */ + pp->nfinal = length; + if(Curl_dyn_len(&pp->recvbuf) > length) + pp->overflow = Curl_dyn_len(&pp->recvbuf) - length; else - return CURLE_OUT_OF_MEMORY; + pp->overflow = 0; + *size = pp->nread_resp; /* size of the response */ + pp->nread_resp = 0; /* restart */ + break; } - if(restart) { - /* now reset a few variables to start over nicely from the start of - the big buffer */ - pp->nread_resp = 0; /* start over from scratch in the buffer */ - ptr = pp->linestart_resp = buf; - perline = 0; - } - - } /* there was data */ + if(Curl_dyn_len(&pp->recvbuf) > length) + /* keep the remaining piece */ + Curl_dyn_tail((&pp->recvbuf), Curl_dyn_len(&pp->recvbuf) - length); + else + Curl_dyn_reset(&pp->recvbuf); + } + else { + /* without a newline, there is no overflow */ + pp->overflow = 0; + break; + } - } /* while there's buffer left and loop is requested */ + } while(1); /* while there's buffer left to scan */ pp->pending_resp = FALSE; @@ -488,14 +417,13 @@ CURLcode Curl_pp_flushsend(struct Curl_easy *data, CURLcode Curl_pp_disconnect(struct pingpong *pp) { Curl_dyn_free(&pp->sendbuf); - Curl_safefree(pp->cache); + Curl_dyn_free(&pp->recvbuf); return CURLE_OK; } bool Curl_pp_moredata(struct pingpong *pp) { - return (!pp->sendleft && pp->cache && pp->nread_resp < pp->cache_size) ? - TRUE : FALSE; + return (!pp->sendleft && Curl_dyn_len(&pp->recvbuf)); } #endif diff --git a/Utilities/cmcurl/lib/pingpong.h b/Utilities/cmcurl/lib/pingpong.h index 80d3f77..006b9c5 100644 --- a/Utilities/cmcurl/lib/pingpong.h +++ b/Utilities/cmcurl/lib/pingpong.h @@ -47,16 +47,11 @@ typedef enum { * It holds response cache and non-blocking sending data. */ struct pingpong { - char *cache; /* data cache between getresponse()-calls */ - size_t cache_size; /* size of cache in bytes */ size_t nread_resp; /* number of bytes currently read of a server response */ - char *linestart_resp; /* line start pointer for the server response - reader function */ bool pending_resp; /* set TRUE when a server response is pending or in progress, and is cleared once the last response is read */ - char *sendthis; /* allocated pointer to a buffer that is to be sent to the - server */ + char *sendthis; /* pointer to a buffer that is to be sent to the server */ size_t sendleft; /* number of bytes left to send from the sendthis buffer */ size_t sendsize; /* total size of the sendthis buffer */ struct curltime response; /* set to Curl_now() when a command has been sent @@ -64,6 +59,10 @@ struct pingpong { timediff_t response_time; /* When no timeout is given, this is the amount of milliseconds we await for a server response. */ struct dynbuf sendbuf; + struct dynbuf recvbuf; + size_t overflow; /* number of bytes left after a final response line */ + size_t nfinal; /* number of bytes in the final response line, which + after a match is first in the receice buffer */ /* Function pointers the protocols MUST implement and provide for the pingpong layer to function */ @@ -90,10 +89,7 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, struct pingpong *pp, bool block, bool disconnecting); /* initialize stuff to prepare for reading a fresh new response */ -void Curl_pp_init(struct Curl_easy *data, struct pingpong *pp); - -/* setup for the transfer */ -void Curl_pp_setup(struct pingpong *pp); +void Curl_pp_init(struct pingpong *pp); /* Returns timeout in ms. 0 or negative number means the timeout has already triggered */ @@ -113,7 +109,7 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data, */ CURLcode Curl_pp_sendf(struct Curl_easy *data, struct pingpong *pp, - const char *fmt, ...); + const char *fmt, ...) CURL_PRINTF(3, 4); /*********************************************************************** * @@ -128,7 +124,7 @@ CURLcode Curl_pp_sendf(struct Curl_easy *data, CURLcode Curl_pp_vsendf(struct Curl_easy *data, struct pingpong *pp, const char *fmt, - va_list args); + va_list args) CURL_PRINTF(3, 0); /* * Curl_pp_readresp() diff --git a/Utilities/cmcurl/lib/pop3.c b/Utilities/cmcurl/lib/pop3.c index 3e0f20a..cf25192 100644 --- a/Utilities/cmcurl/lib/pop3.c +++ b/Utilities/cmcurl/lib/pop3.c @@ -77,6 +77,7 @@ #include "curl_sasl.h" #include "curl_md5.h" #include "warnless.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -124,7 +125,7 @@ const struct Curl_handler Curl_handler_pop3 = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_POP3, /* defport */ @@ -153,7 +154,7 @@ const struct Curl_handler Curl_handler_pop3s = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_POP3S, /* defport */ @@ -251,8 +252,8 @@ static bool pop3_endofresp(struct Curl_easy *data, struct connectdata *conn, */ static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out) { - char *message = data->state.buffer; - size_t len = strlen(message); + char *message = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); + size_t len = data->conn->proto.pop3c.pp.nfinal; if(len > 2) { /* Find the start of the message */ @@ -648,8 +649,8 @@ static CURLcode pop3_state_servergreet_resp(struct Curl_easy *data, CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *line = data->state.buffer; - size_t len = strlen(line); + const char *line = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); + size_t len = data->conn->proto.pop3c.pp.nfinal; (void)instate; /* no use for this yet */ @@ -657,44 +658,35 @@ static CURLcode pop3_state_servergreet_resp(struct Curl_easy *data, failf(data, "Got unexpected pop3-server response"); result = CURLE_WEIRD_SERVER_REPLY; } - else { + else if(len > 3) { /* Does the server support APOP authentication? */ - if(len >= 4 && line[len - 2] == '>') { - /* Look for the APOP timestamp */ - size_t i; - for(i = 3; i < len - 2; ++i) { - if(line[i] == '<') { - /* Calculate the length of the timestamp */ - size_t timestamplen = len - 1 - i; - char *at; - if(!timestamplen) - break; - - /* Allocate some memory for the timestamp */ - pop3c->apoptimestamp = (char *)calloc(1, timestamplen + 1); - - if(!pop3c->apoptimestamp) - break; - - /* Copy the timestamp */ - memcpy(pop3c->apoptimestamp, line + i, timestamplen); - pop3c->apoptimestamp[timestamplen] = '\0'; - - /* If the timestamp does not contain '@' it is not (as required by - RFC-1939) conformant to the RFC-822 message id syntax, and we - therefore do not use APOP authentication. */ - at = strchr(pop3c->apoptimestamp, '@'); - if(!at) - Curl_safefree(pop3c->apoptimestamp); - else - /* Store the APOP capability */ - pop3c->authtypes |= POP3_TYPE_APOP; - break; - } + char *lt; + char *gt = NULL; + + /* Look for the APOP timestamp */ + lt = memchr(line, '<', len); + if(lt) + /* search the remainder for '>' */ + gt = memchr(lt, '>', len - (lt - line)); + if(gt) { + /* the length of the timestamp, including the brackets */ + size_t timestamplen = gt - lt + 1; + char *at = memchr(lt, '@', timestamplen); + /* If the timestamp does not contain '@' it is not (as required by + RFC-1939) conformant to the RFC-822 message id syntax, and we + therefore do not use APOP authentication. */ + if(at) { + /* dupe the timestamp */ + pop3c->apoptimestamp = Curl_memdup0(lt, timestamplen); + if(!pop3c->apoptimestamp) + return CURLE_OUT_OF_MEMORY; + /* Store the APOP capability */ + pop3c->authtypes |= POP3_TYPE_APOP; } } - result = pop3_perform_capa(data, conn); + if(!result) + result = pop3_perform_capa(data, conn); } return result; @@ -707,8 +699,8 @@ static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code, CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *line = data->state.buffer; - size_t len = strlen(line); + const char *line = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); + size_t len = data->conn->proto.pop3c.pp.nfinal; (void)instate; /* no use for this yet */ @@ -795,7 +787,7 @@ static CURLcode pop3_state_starttls_resp(struct Curl_easy *data, (void)instate; /* no use for this yet */ /* Pipelining in response is forbidden. */ - if(data->conn->proto.pop3c.pp.cache_size) + if(data->conn->proto.pop3c.pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(pop3code != '+') { @@ -944,24 +936,29 @@ static CURLcode pop3_state_command_resp(struct Curl_easy *data, /* POP3 download */ Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); - if(pp->cache) { - /* The header "cache" contains a bunch of data that is actually body - content so send it as such. Note that there may even be additional - "headers" after the body */ + if(pp->overflow) { + /* The recv buffer contains data that is actually body content so send + it as such. Note that there may even be additional "headers" after + the body */ + + /* keep only the overflow */ + Curl_dyn_tail(&pp->recvbuf, pp->overflow); + pp->nfinal = 0; /* done */ if(!data->req.no_body) { - result = Curl_pop3_write(data, pp->cache, pp->cache_size); + result = Curl_pop3_write(data, Curl_dyn_ptr(&pp->recvbuf), + Curl_dyn_len(&pp->recvbuf)); if(result) return result; } - /* Free the cache */ - Curl_safefree(pp->cache); - - /* Reset the cache size */ - pp->cache_size = 0; + /* reset the buffer */ + Curl_dyn_reset(&pp->recvbuf); + pp->overflow = 0; } } + else + pp->overflow = 0; /* End of DO phase */ pop3_state(data, POP3_STOP); @@ -1131,8 +1128,7 @@ static CURLcode pop3_connect(struct Curl_easy *data, bool *done) Curl_sasl_init(&pop3c->sasl, data, &saslpop3); /* Initialise the pingpong layer */ - Curl_pp_setup(pp); - Curl_pp_init(data, pp); + Curl_pp_init(pp); /* Parse the URL options */ result = pop3_parse_url_options(conn); diff --git a/Utilities/cmcurl/lib/progress.c b/Utilities/cmcurl/lib/progress.c index e96cbf7..d05fcc3 100644 --- a/Utilities/cmcurl/lib/progress.c +++ b/Utilities/cmcurl/lib/progress.c @@ -174,10 +174,18 @@ void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, data->progress.t_startop = timestamp; break; case TIMER_STARTSINGLE: - /* This is set at the start of each single fetch */ + /* This is set at the start of each single transfer */ data->progress.t_startsingle = timestamp; data->progress.is_t_startransfer_set = false; break; + case TIMER_POSTQUEUE: + /* Set when the transfer starts (after potentially having been brought + back from the waiting queue). It needs to count from t_startop and not + t_startsingle since the latter is reset when a connection is brought + back from the pending queue. */ + data->progress.t_postqueue = + Curl_timediff_us(timestamp, data->progress.t_startop); + break; case TIMER_STARTACCEPT: data->progress.t_acceptdata = timestamp; break; diff --git a/Utilities/cmcurl/lib/progress.h b/Utilities/cmcurl/lib/progress.h index fc39e34..7374941 100644 --- a/Utilities/cmcurl/lib/progress.h +++ b/Utilities/cmcurl/lib/progress.h @@ -30,7 +30,8 @@ typedef enum { TIMER_NONE, TIMER_STARTOP, - TIMER_STARTSINGLE, + TIMER_STARTSINGLE, /* start of transfer, might get queued */ + TIMER_POSTQUEUE, /* start, immediately after dequeue */ TIMER_NAMELOOKUP, TIMER_CONNECT, TIMER_APPCONNECT, diff --git a/Utilities/cmcurl/lib/rand.c b/Utilities/cmcurl/lib/rand.c index 3383c49..c62b1a4 100644 --- a/Utilities/cmcurl/lib/rand.c +++ b/Utilities/cmcurl/lib/rand.c @@ -201,7 +201,7 @@ CURLcode Curl_rand(struct Curl_easy *data, unsigned char *rnd, size_t num) { CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; - DEBUGASSERT(num > 0); + DEBUGASSERT(num); while(num) { unsigned int r; @@ -241,9 +241,11 @@ CURLcode Curl_rand_hex(struct Curl_easy *data, unsigned char *rnd, memset(buffer, 0, sizeof(buffer)); #endif - if((num/2 >= sizeof(buffer)) || !(num&1)) + if((num/2 >= sizeof(buffer)) || !(num&1)) { /* make sure it fits in the local buffer and that it is an odd number! */ + DEBUGF(infof(data, "invalid buffer size with Curl_rand_hex")); return CURLE_BAD_FUNCTION_ARGUMENT; + } num--; /* save one for null-termination */ diff --git a/Utilities/cmcurl/lib/rtsp.c b/Utilities/cmcurl/lib/rtsp.c index e673bb8..26f4735 100644 --- a/Utilities/cmcurl/lib/rtsp.c +++ b/Utilities/cmcurl/lib/rtsp.c @@ -58,21 +58,20 @@ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); /* - * Parse and write out any available RTP data. + * Parse and write out an RTSP response. * @param data the transfer * @param conn the connection * @param buf data read from connection * @param blen amount of data in buf - * @param consumed out, number of blen consumed + * @param is_eos TRUE iff this is the last write * @param readmore out, TRUE iff complete buf was consumed and more data * is needed */ -static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, - struct connectdata *conn, - const char *buf, - size_t blen, - size_t *pconsumed, - bool *readmore); +static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, + const char *buf, + size_t blen, + bool is_eos, + bool *done); static CURLcode rtsp_setup_connection(struct Curl_easy *data, struct connectdata *conn); @@ -115,7 +114,7 @@ const struct Curl_handler Curl_handler_rtsp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtsp_disconnect, /* disconnect */ - rtsp_rtp_readwrite, /* readwrite */ + rtsp_rtp_write_resp, /* write_resp */ rtsp_conncheck, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_RTSP, /* defport */ @@ -590,26 +589,48 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) return result; } +/** + * write any BODY bytes missing to the client, ignore the rest. + */ +static CURLcode rtp_write_body_junk(struct Curl_easy *data, + const char *buf, + size_t blen) +{ + struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); + curl_off_t body_remain; + bool in_body; + + in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); + body_remain = in_body? (data->req.size - data->req.bytecount) : 0; + DEBUGASSERT(body_remain >= 0); + if(body_remain) { + if((curl_off_t)blen > body_remain) + blen = (size_t)body_remain; + return Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen); + } + return CURLE_OK; +} + static CURLcode rtsp_filter_rtp(struct Curl_easy *data, - struct connectdata *conn, const char *buf, size_t blen, - bool in_body, size_t *pconsumed) { - struct rtsp_conn *rtspc = &(conn->proto.rtspc); + struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); CURLcode result = CURLE_OK; + size_t skip_len = 0; *pconsumed = 0; while(blen) { + bool in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); switch(rtspc->state) { case RTP_PARSE_SKIP: { DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 0); - if(in_body && buf[0] != '$') { - /* in BODY and no valid start, do not consume and return */ - goto out; - } while(blen && buf[0] != '$') { if(!in_body && buf[0] == 'R' && data->set.rtspreq != RTSPREQ_RECEIVE) { @@ -624,13 +645,22 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, goto out; } } - /* junk, consume without buffering */ + /* junk/BODY, consume without buffering */ *pconsumed += 1; ++buf; --blen; + ++skip_len; } if(blen && buf[0] == '$') { /* possible start of an RTP message, buffer */ + if(skip_len) { + /* end of junk/BODY bytes, flush */ + result = rtp_write_body_junk(data, + (char *)(buf - skip_len), skip_len); + skip_len = 0; + if(result) + goto out; + } if(Curl_dyn_addn(&rtspc->buf, buf, 1)) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -650,35 +680,22 @@ static CURLcode rtsp_filter_rtp(struct Curl_easy *data, if(!(data->state.rtp_channel_mask[idx] & (1 << off))) { /* invalid channel number, junk or BODY data */ rtspc->state = RTP_PARSE_SKIP; - if(in_body) { - /* we do not consume this byte, it is BODY data */ - DEBUGF(infof(data, "RTSP: invalid RTP channel %d in BODY, " - "treating as BODY data", idx)); - if(*pconsumed == 0) { - /* We did not consume the initial '$' in our buffer, but had - * it from an earlier call. We cannot un-consume it and have - * to write it directly as BODY data */ - result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&rtspc->buf), 1); - Curl_dyn_free(&rtspc->buf); - if(result) - goto out; - } - else { - /* un-consume the '$' and leave */ - Curl_dyn_free(&rtspc->buf); - *pconsumed -= 1; - --buf; - ++blen; + DEBUGASSERT(skip_len == 0); + /* we do not consume this byte, it is BODY data */ + DEBUGF(infof(data, "RTSP: invalid RTP channel %d, skipping", idx)); + if(*pconsumed == 0) { + /* We did not consume the initial '$' in our buffer, but had + * it from an earlier call. We cannot un-consume it and have + * to write it directly as BODY data */ + result = rtp_write_body_junk(data, Curl_dyn_ptr(&rtspc->buf), 1); + if(result) goto out; - } } else { - /* not BODY, forget the junk '$'. Do not consume this byte, - * it might be a start */ - infof(data, "RTSP: invalid RTP channel %d, skipping", idx); - Curl_dyn_free(&rtspc->buf); + /* count the '$' as skip and continue */ + skip_len = 1; } + Curl_dyn_free(&rtspc->buf); break; } /* a valid channel, so we expect this to be a real RTP message */ @@ -754,52 +771,51 @@ 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); return result; } -static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, - struct connectdata *conn, - const char *buf, - size_t blen, - size_t *pconsumed, - bool *readmore) +static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, + const char *buf, + size_t blen, + bool is_eos, + bool *done) { - struct rtsp_conn *rtspc = &(conn->proto.rtspc); + struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); CURLcode result = CURLE_OK; size_t consumed = 0; - bool in_body; if(!data->req.header) rtspc->in_header = FALSE; - in_body = (data->req.headerline && !rtspc->in_header) && - (data->req.size >= 0) && - (data->req.bytecount < data->req.size); - - *readmore = FALSE; - *pconsumed = 0; + *done = FALSE; if(!blen) { goto out; } + DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, eos=%d)", + blen, rtspc->in_header, is_eos)); + /* If header parsing is not onging, extract RTP messages */ if(!rtspc->in_header) { - result = rtsp_filter_rtp(data, conn, buf, blen, in_body, &consumed); + result = rtsp_filter_rtp(data, buf, blen, &consumed); if(result) goto out; - *pconsumed += consumed; buf += consumed; blen -= consumed; + /* either we consumed all or are at the start of header parsing */ + if(blen && !data->req.header) + DEBUGF(infof(data, "RTSP: %zu bytes, possibly excess in response body", + blen)); } /* we want to parse headers, do so */ if(data->req.header && blen) { rtspc->in_header = TRUE; - result = Curl_http_readwrite_headers(data, conn, buf, blen, - &consumed); + result = Curl_http_write_resp_hds(data, buf, blen, &consumed, done); if(result) goto out; - *pconsumed += consumed; buf += consumed; blen -= consumed; @@ -807,26 +823,41 @@ static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, rtspc->in_header = FALSE; if(!rtspc->in_header) { - /* If header parsing is done and data left, extract RTP messages */ - in_body = (data->req.headerline && !rtspc->in_header) && - (data->req.size >= 0) && - (data->req.bytecount < data->req.size); - result = rtsp_filter_rtp(data, conn, buf, blen, in_body, &consumed); + /* If header parsing is done, extract interleaved RTP messages */ + if(data->req.size <= -1) { + /* Respect section 4.4 of rfc2326: If the Content-Length header is + absent, a length 0 must be assumed. */ + data->req.size = 0; + data->req.download_done = TRUE; + } + result = rtsp_filter_rtp(data, buf, blen, &consumed); if(result) goto out; - *pconsumed += consumed; + blen -= consumed; } } if(rtspc->state != RTP_PARSE_SKIP) - *readmore = TRUE; + *done = FALSE; + /* we SHOULD have consumed all bytes, unless the response is borked. + * In which case we write out the left over bytes, letting the client + * writer deal with it (it will report EXCESS and fail the transfer). */ + DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, done=%d " + " rtspc->state=%d, req.size=%" CURL_FORMAT_CURL_OFF_T ")", + blen, rtspc->in_header, *done, rtspc->state, data->req.size)); + if(!result && (is_eos || blen)) { + result = Curl_client_write(data, CLIENTWRITE_BODY| + (is_eos? CLIENTWRITE_EOS:0), + (char *)buf, blen); + } out: - if(!*readmore && data->set.rtspreq == RTSPREQ_RECEIVE) { + if((data->set.rtspreq == RTSPREQ_RECEIVE) && + (rtspc->state == RTP_PARSE_SKIP)) { /* In special mode RECEIVE, we just process one chunk of network * data, so we stop the transfer here, if we have no incomplete * RTP message pending. */ - data->req.keepon &= ~KEEP_RECV; + data->req.download_done = TRUE; } return result; } @@ -922,7 +953,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) /* If the Session ID is set, then compare */ if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen || - strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen) != 0) { + strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen)) { failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", start, data->set.str[STRING_RTSP_SESSION_ID]); return CURLE_RTSP_SESSION_ERROR; @@ -934,11 +965,9 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) */ /* Copy the id substring into a new buffer */ - data->set.str[STRING_RTSP_SESSION_ID] = malloc(idlen + 1); + data->set.str[STRING_RTSP_SESSION_ID] = Curl_memdup0(start, idlen); if(!data->set.str[STRING_RTSP_SESSION_ID]) return CURLE_OUT_OF_MEMORY; - memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, idlen); - (data->set.str[STRING_RTSP_SESSION_ID])[idlen] = '\0'; } } else if(checkprefix("Transport:", header)) { diff --git a/Utilities/cmcurl/lib/sendf.c b/Utilities/cmcurl/lib/sendf.c index a2fac0c..db3189a 100644 --- a/Utilities/cmcurl/lib/sendf.c +++ b/Utilities/cmcurl/lib/sendf.c @@ -296,13 +296,6 @@ static CURLcode chop_write(struct Curl_easy *data, if(!skip_body_write && ((type & CLIENTWRITE_BODY) || ((type & CLIENTWRITE_HEADER) && data->set.include_header))) { -#ifdef USE_WEBSOCKETS - if(conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) { - writebody = Curl_ws_writecb; - writebody_ptr = data; - } - else -#endif writebody = data->set.fwrite_func; } if((type & (CLIENTWRITE_HEADER|CLIENTWRITE_INFO)) && @@ -345,7 +338,7 @@ static CURLcode chop_write(struct Curl_easy *data, len -= chunklen; } -#ifndef CURL_DISABLE_HTTP +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API) /* HTTP header, but not status-line */ if((conn->handler->protocol & PROTO_FAMILY_HTTP) && (type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS) ) { @@ -404,10 +397,12 @@ CURLcode Curl_client_write(struct Curl_easy *data, #endif /* it is one of those, at least */ DEBUGASSERT(type & (CLIENTWRITE_BODY|CLIENTWRITE_HEADER|CLIENTWRITE_INFO)); - /* BODY is only BODY */ - DEBUGASSERT(!(type & CLIENTWRITE_BODY) || (type == CLIENTWRITE_BODY)); - /* INFO is only INFO */ - DEBUGASSERT(!(type & CLIENTWRITE_INFO) || (type == CLIENTWRITE_INFO)); + /* BODY is only BODY (with optional EOS) */ + DEBUGASSERT(!(type & CLIENTWRITE_BODY) || + ((type & ~(CLIENTWRITE_BODY|CLIENTWRITE_EOS)) == 0)); + /* INFO is only INFO (with optional EOS) */ + DEBUGASSERT(!(type & CLIENTWRITE_INFO) || + ((type & ~(CLIENTWRITE_INFO|CLIENTWRITE_EOS)) == 0)); if(!data->req.writer_stack) { result = do_init_stack(data); @@ -477,8 +472,6 @@ CURLcode Curl_cwriter_write(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { - if(!nbytes) - return CURLE_OK; if(!writer) return CURLE_WRITE_ERROR; return writer->cwt->do_write(data, writer, type, buf, nbytes); @@ -556,7 +549,6 @@ static CURLcode cw_download_write(struct Curl_easy *data, { CURLcode result; size_t nwrite, excess_len = 0; - const char *excess_data = NULL; if(!(type & CLIENTWRITE_BODY)) { if((type & CLIENTWRITE_CONNECT) && data->set.suppress_connect_headers) @@ -564,13 +556,38 @@ static CURLcode cw_download_write(struct Curl_easy *data, return Curl_cwriter_write(data, writer->next, type, buf, nbytes); } + if(!data->req.bytecount) { + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + if(data->req.exp100 > EXP100_SEND_DATA) + /* set time stamp to compare with when waiting for the 100 */ + data->req.start100 = Curl_now(); + } + + /* Here, we deal with REAL BODY bytes. All filtering and transfer + * encodings have been applied and only the true content, e.g. BODY, + * bytes are passed here. + * This allows us to check sizes, update stats, etc. independent + * from the protocol in play. */ + + if(data->req.no_body && nbytes > 0) { + /* BODY arrives although we want none, bail out */ + streamclose(data->conn, "ignoring body"); + DEBUGF(infof(data, "did not want a BODY, but seeing %zu bytes", + nbytes)); + data->req.download_done = TRUE; + return CURLE_WEIRD_SERVER_REPLY; + } + + /* Determine if we see any bytes in excess to what is allowed. + * We write the allowed bytes and handle excess further below. + * This gives deterministic BODY writes on varying buffer receive + * lengths. */ nwrite = nbytes; if(-1 != data->req.maxdownload) { size_t wmax = get_max_body_write_len(data, data->req.maxdownload); if(nwrite > wmax) { excess_len = nbytes - wmax; nwrite = wmax; - excess_data = buf + nwrite; } if(nwrite == wmax) { @@ -578,6 +595,8 @@ static CURLcode cw_download_write(struct Curl_easy *data, } } + /* Error on too large filesize is handled below, after writing + * the permitted bytes */ if(data->set.max_filesize) { size_t wmax = get_max_body_write_len(data, data->set.max_filesize); if(nwrite > wmax) { @@ -585,6 +604,7 @@ static CURLcode cw_download_write(struct Curl_easy *data, } } + /* Update stats, write and report progress */ data->req.bytecount += nwrite; ++data->req.bodywrites; if(!data->req.ignorebody && nwrite) { @@ -597,23 +617,7 @@ static CURLcode cw_download_write(struct Curl_easy *data, return result; if(excess_len) { - if(data->conn->handler->readwrite) { - /* RTSP hack moved from transfer loop to here */ - bool readmore = FALSE; /* indicates data is incomplete, need more */ - size_t consumed = 0; - result = data->conn->handler->readwrite(data, data->conn, - excess_data, excess_len, - &consumed, &readmore); - if(result) - return result; - DEBUGASSERT(consumed <= excess_len); - excess_len -= consumed; - if(readmore) { - data->req.download_done = FALSE; - data->req.keepon |= KEEP_RECV; /* we're not done reading */ - } - } - if(excess_len && !data->req.ignorebody) { + if(!data->req.ignorebody) { infof(data, "Excess found writing body:" " excess = %zu" @@ -762,6 +766,21 @@ CURLcode Curl_cwriter_add(struct Curl_easy *data, return CURLE_OK; } +void Curl_cwriter_remove_by_name(struct Curl_easy *data, + const char *name) +{ + struct Curl_cwriter **anchor = &data->req.writer_stack; + + while(*anchor) { + if(!strcmp(name, (*anchor)->cwt->name)) { + struct Curl_cwriter *w = (*anchor); + *anchor = w->next; + Curl_cwriter_free(data, w); + continue; + } + anchor = &((*anchor)->next); + } +} /* * Internal read-from-socket function. This is meant to deal with plain diff --git a/Utilities/cmcurl/lib/sendf.h b/Utilities/cmcurl/lib/sendf.h index a70189f..7deae2a 100644 --- a/Utilities/cmcurl/lib/sendf.h +++ b/Utilities/cmcurl/lib/sendf.h @@ -49,6 +49,7 @@ #define CLIENTWRITE_CONNECT (1<<4) /* a CONNECT related HEADER */ #define CLIENTWRITE_1XX (1<<5) /* a 1xx response related HEADER */ #define CLIENTWRITE_TRAILER (1<<6) /* a trailer HEADER */ +#define CLIENTWRITE_EOS (1<<7) /* End Of transfer download Stream */ /** * Write `len` bytes at `prt` to the client. `type` indicates what @@ -147,6 +148,9 @@ size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase); CURLcode Curl_cwriter_add(struct Curl_easy *data, struct Curl_cwriter *writer); +void Curl_cwriter_remove_by_name(struct Curl_easy *data, + const char *name); + /** * Convenience method for calling `writer->do_write()` that * checks for NULL writer. diff --git a/Utilities/cmcurl/lib/setopt.c b/Utilities/cmcurl/lib/setopt.c index a08140c..a527077 100644 --- a/Utilities/cmcurl/lib/setopt.c +++ b/Utilities/cmcurl/lib/setopt.c @@ -51,7 +51,7 @@ #include "altsvc.h" #include "hsts.h" #include "tftp.h" - +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -366,6 +366,17 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) else return CURLE_BAD_FUNCTION_ARGUMENT; break; + case CURLOPT_SERVER_RESPONSE_TIMEOUT_MS: + /* + * Option that specifies how quickly a server response must be obtained + * before it is considered failure. For pingpong protocols. + */ + arg = va_arg(param, long); + if((arg >= 0) && (arg <= INT_MAX)) + data->set.server_response_timeout = (unsigned int)arg; + else + return CURLE_BAD_FUNCTION_ARGUMENT; + break; #ifndef CURL_DISABLE_TFTP case CURLOPT_TFTP_NO_OPTIONS: /* @@ -497,26 +508,17 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) (data->set.postfieldsize > (curl_off_t)((size_t)-1)))) result = CURLE_OUT_OF_MEMORY; else { - char *p; - - (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - /* Allocate even when size == 0. This satisfies the need of possible - later address compare to detect the COPYPOSTFIELDS mode, and - to mark that postfields is used rather than read function or - form data. + later address compare to detect the COPYPOSTFIELDS mode, and to + mark that postfields is used rather than read function or form + data. */ - p = malloc((size_t)(data->set.postfieldsize? - data->set.postfieldsize:1)); - + char *p = Curl_memdup0(argptr, (size_t)data->set.postfieldsize); + (void) Curl_setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); if(!p) result = CURLE_OUT_OF_MEMORY; - else { - if(data->set.postfieldsize) - memcpy(p, argptr, (size_t)data->set.postfieldsize); - + else data->set.str[STRING_COPYPOSTFIELDS] = p; - } } } @@ -670,6 +672,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->set.opt_no_body = FALSE; /* this is implied */ Curl_mime_cleanpart(data->state.formp); Curl_safefree(data->state.formp); + data->state.mimepost = NULL; break; #endif @@ -977,6 +980,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) #ifndef CURL_DISABLE_FORM_API Curl_mime_cleanpart(data->state.formp); Curl_safefree(data->state.formp); + data->state.mimepost = NULL; #endif } break; @@ -3109,6 +3113,10 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) return CURLE_OUT_OF_MEMORY; } arg = va_arg(param, long); + if(!arg) { + DEBUGF(infof(data, "bad CURLOPT_ALTSVC_CTRL input")); + return CURLE_BAD_FUNCTION_ARGUMENT; + } result = Curl_altsvc_ctrl(data->asi, arg); if(result) return result; @@ -3163,5 +3171,9 @@ CURLcode curl_easy_setopt(struct Curl_easy *data, CURLoption tag, ...) result = Curl_vsetopt(data, tag, arg); va_end(arg); +#ifdef DEBUGBUILD + if(result == CURLE_BAD_FUNCTION_ARGUMENT) + infof(data, "setopt arg 0x%x returned CURLE_BAD_FUNCTION_ARGUMENT", tag); +#endif return result; } diff --git a/Utilities/cmcurl/lib/setup-win32.h b/Utilities/cmcurl/lib/setup-win32.h index 4e034d4..d7e2e6b 100644 --- a/Utilities/cmcurl/lib/setup-win32.h +++ b/Utilities/cmcurl/lib/setup-win32.h @@ -24,18 +24,53 @@ * ***************************************************************************/ +#undef USE_WINSOCK +/* ---------------------------------------------------------------- */ +/* Watt-32 TCP/IP SPECIFIC */ +/* ---------------------------------------------------------------- */ +#ifdef USE_WATT32 +# include <tcp.h> +# undef byte +# undef word +# define HAVE_SYS_IOCTL_H +# define HAVE_SYS_SOCKET_H +# define HAVE_NETINET_IN_H +# define HAVE_NETDB_H +# define HAVE_ARPA_INET_H +# define SOCKET int +/* ---------------------------------------------------------------- */ +/* BSD-style lwIP TCP/IP stack SPECIFIC */ +/* ---------------------------------------------------------------- */ +#elif defined(USE_LWIPSOCK) + /* Define to use BSD-style lwIP TCP/IP stack. */ + /* #define USE_LWIPSOCK 1 */ +# undef HAVE_GETHOSTNAME +# undef LWIP_POSIX_SOCKETS_IO_NAMES +# undef RECV_TYPE_ARG1 +# undef RECV_TYPE_ARG3 +# undef SEND_TYPE_ARG1 +# undef SEND_TYPE_ARG3 +# define HAVE_GETHOSTBYNAME_R +# define HAVE_GETHOSTBYNAME_R_6 +# define LWIP_POSIX_SOCKETS_IO_NAMES 0 +# define RECV_TYPE_ARG1 int +# define RECV_TYPE_ARG3 size_t +# define SEND_TYPE_ARG1 int +# define SEND_TYPE_ARG3 size_t +#elif defined(_WIN32) +# define USE_WINSOCK 2 +#endif + /* * Include header files for windows builds before redefining anything. * Use this preprocessor block only to include or exclude windows.h, * winsock2.h or ws2tcpip.h. Any other windows thing belongs * to any other further and independent block. Under Cygwin things work * just as under linux (e.g. <sys/socket.h>) and the winsock headers should - * never be included when __CYGWIN__ is defined. configure script takes - * care of this, not defining HAVE_WINDOWS_H, HAVE_WINSOCK2_H, - * neither HAVE_WS2TCPIP_H when __CYGWIN__ is defined. + * never be included when __CYGWIN__ is defined. */ -#ifdef HAVE_WINDOWS_H +#ifdef _WIN32 # if defined(UNICODE) && !defined(_UNICODE) # error "UNICODE is defined but _UNICODE is not defined" # endif @@ -53,12 +88,8 @@ # ifndef NOGDI # define NOGDI # endif -# ifdef HAVE_WINSOCK2_H -# include <winsock2.h> -# ifdef HAVE_WS2TCPIP_H -# include <ws2tcpip.h> -# endif -# endif +# include <winsock2.h> +# include <ws2tcpip.h> # include <windows.h> # include <winerror.h> # include <tchar.h> @@ -68,17 +99,6 @@ #endif /* - * Define USE_WINSOCK to 2 if we have and use WINSOCK2 API, else - * undefine USE_WINSOCK. - */ - -#undef USE_WINSOCK - -#ifdef HAVE_WINSOCK2_H -# define USE_WINSOCK 2 -#endif - -/* * Define _WIN32_WINNT_[OS] symbols because not all Windows build systems have * those symbols to compare against, and even those that do may be missing * newer symbols. diff --git a/Utilities/cmcurl/lib/share.c b/Utilities/cmcurl/lib/share.c index c0a8d80..8fa5cda 100644 --- a/Utilities/cmcurl/lib/share.c +++ b/Utilities/cmcurl/lib/share.c @@ -133,13 +133,13 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) res = CURLSHE_BAD_OPTION; } if(!res) - share->specifier |= (1<<type); + share->specifier |= (unsigned int)(1<<type); break; case CURLSHOPT_UNSHARE: /* this is a type this share will no longer share */ type = va_arg(param, int); - share->specifier &= ~(1<<type); + share->specifier &= ~(unsigned int)(1<<type); switch(type) { case CURL_LOCK_DATA_DNS: break; @@ -264,7 +264,7 @@ Curl_share_lock(struct Curl_easy *data, curl_lock_data type, if(!share) return CURLSHE_INVALID; - if(share->specifier & (1<<type)) { + if(share->specifier & (unsigned int)(1<<type)) { if(share->lockfunc) /* only call this if set! */ share->lockfunc(data, type, accesstype, share->clientdata); } @@ -281,7 +281,7 @@ Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) if(!share) return CURLSHE_INVALID; - if(share->specifier & (1<<type)) { + if(share->specifier & (unsigned int)(1<<type)) { if(share->unlockfunc) /* only call this if set! */ share->unlockfunc (data, type, share->clientdata); } diff --git a/Utilities/cmcurl/lib/smb.c b/Utilities/cmcurl/lib/smb.c index 6c8a47c..1d1867c 100644 --- a/Utilities/cmcurl/lib/smb.c +++ b/Utilities/cmcurl/lib/smb.c @@ -272,7 +272,7 @@ const struct Curl_handler Curl_handler_smb = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMB, /* defport */ @@ -299,7 +299,7 @@ const struct Curl_handler Curl_handler_smbs = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMBS, /* defport */ diff --git a/Utilities/cmcurl/lib/smtp.c b/Utilities/cmcurl/lib/smtp.c index 65fbc5b..bfe7b8f 100644 --- a/Utilities/cmcurl/lib/smtp.c +++ b/Utilities/cmcurl/lib/smtp.c @@ -130,7 +130,7 @@ const struct Curl_handler Curl_handler_smtp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMTP, /* defport */ @@ -159,7 +159,7 @@ const struct Curl_handler Curl_handler_smtps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SMTPS, /* defport */ @@ -250,8 +250,8 @@ static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, */ static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) { - char *message = data->state.buffer; - size_t len = strlen(message); + char *message = Curl_dyn_ptr(&data->conn->proto.smtpc.pp.recvbuf); + size_t len = data->conn->proto.smtpc.pp.nfinal; if(len > 4) { /* Find the start of the message */ @@ -859,7 +859,7 @@ static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, (void)instate; /* no use for this yet */ /* Pipelining in response is forbidden. */ - if(data->conn->proto.smtpc.pp.cache_size) + if(data->conn->proto.smtpc.pp.overflow) return CURLE_WEIRD_SERVER_REPLY; if(smtpcode != 220) { @@ -883,8 +883,8 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, { CURLcode result = CURLE_OK; struct smtp_conn *smtpc = &conn->proto.smtpc; - const char *line = data->state.buffer; - size_t len = strlen(line); + const char *line = Curl_dyn_ptr(&smtpc->pp.recvbuf); + size_t len = smtpc->pp.nfinal; (void)instate; /* no use for this yet */ @@ -1033,8 +1033,8 @@ static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, { CURLcode result = CURLE_OK; struct SMTP *smtp = data->req.p.smtp; - char *line = data->state.buffer; - size_t len = strlen(line); + char *line = Curl_dyn_ptr(&data->conn->proto.smtpc.pp.recvbuf); + size_t len = data->conn->proto.smtpc.pp.nfinal; (void)instate; /* no use for this yet */ @@ -1044,12 +1044,8 @@ static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, result = CURLE_WEIRD_SERVER_REPLY; } else { - /* Temporarily add the LF character back and send as body to the client */ - if(!data->req.no_body) { - line[len] = '\n'; - result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1); - line[len] = '\0'; - } + if(!data->req.no_body) + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); if(smtpcode != 1) { if(smtp->rcpt) { @@ -1268,7 +1264,6 @@ static CURLcode smtp_statemachine(struct Curl_easy *data, break; case SMTP_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ smtp_state(data, SMTP_STOP); @@ -1362,8 +1357,7 @@ static CURLcode smtp_connect(struct Curl_easy *data, bool *done) Curl_sasl_init(&smtpc->sasl, data, &saslsmtp); /* Initialise the pingpong layer */ - Curl_pp_setup(pp); - Curl_pp_init(data, pp); + Curl_pp_init(pp); /* Parse the URL options */ result = smtp_parse_url_options(conn); @@ -1541,6 +1535,8 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, static CURLcode smtp_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; + DEBUGASSERT(data); + DEBUGASSERT(data->conn); *done = FALSE; /* default to false */ /* Parse the custom request */ diff --git a/Utilities/cmcurl/lib/socketpair.c b/Utilities/cmcurl/lib/socketpair.c index e3d40ff..d01b255 100644 --- a/Utilities/cmcurl/lib/socketpair.c +++ b/Utilities/cmcurl/lib/socketpair.c @@ -33,9 +33,6 @@ * This is a socketpair() implementation for Windows. */ #include <string.h> -#include <winsock2.h> -#include <ws2tcpip.h> -#include <windows.h> #include <io.h> #else #ifdef HAVE_NETDB_H diff --git a/Utilities/cmcurl/lib/socks.c b/Utilities/cmcurl/lib/socks.c index 3a396de..ecd2f7e 100644 --- a/Utilities/cmcurl/lib/socks.c +++ b/Utilities/cmcurl/lib/socks.c @@ -71,9 +71,18 @@ enum connect_t { CONNECT_DONE /* 17 connected fine to the remote or the SOCKS proxy */ }; +#define CURL_SOCKS_BUF_SIZE 600 + +/* make sure we configure it not too low */ +#if CURL_SOCKS_BUF_SIZE < 600 +#error CURL_SOCKS_BUF_SIZE must be at least 600 +#endif + + struct socks_state { enum connect_t state; ssize_t outstanding; /* send this many bytes more */ + unsigned char buffer[CURL_SOCKS_BUF_SIZE]; unsigned char *outp; /* send from this pointer */ const char *hostname; @@ -249,7 +258,7 @@ static CURLproxycode socks_state_recv(struct Curl_cfilter *cf, failf(data, "connection to proxy closed"); return CURLPX_CLOSED; } - failf(data, "SOCKS4: Failed receiving %s: %s", description, + failf(data, "SOCKS: Failed receiving %s: %s", description, curl_easy_strerror(result)); return failcode; } @@ -278,14 +287,11 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, struct connectdata *conn = cf->conn; const bool protocol4a = (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE; - unsigned char *socksreq = (unsigned char *)data->state.buffer; + unsigned char *socksreq = sx->buffer; CURLcode result; CURLproxycode presult; struct Curl_dns_entry *dns = NULL; - /* make sure that the buffer is at least 600 bytes */ - DEBUGASSERT(READBUFFER_MIN >= 600); - switch(sx->state) { case CONNECT_SOCKS_INIT: /* SOCKS4 can only do IPv4, insist! */ @@ -339,8 +345,8 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, if(dns) { #ifdef CURLRES_ASYNCH - conn->resolve_async.dns = dns; - conn->resolve_async.done = TRUE; + data->state.async.dns = dns; + data->state.async.done = TRUE; #endif infof(data, "Hostname '%s' was found", sx->hostname); sxstate(sx, data, CONNECT_RESOLVED); @@ -353,9 +359,10 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, return CURLPX_OK; } } - /* FALLTHROUGH */ + FALLTHROUGH(); + case CONNECT_RESOLVED: CONNECT_RESOLVED: - case CONNECT_RESOLVED: { + { struct Curl_addrinfo *hp = NULL; /* * We cannot use 'hostent' as a struct that Curl_resolv() returns. It @@ -393,9 +400,9 @@ CONNECT_RESOLVED: if(!hp) return CURLPX_RESOLVE_HOST; } - /* FALLTHROUGH */ -CONNECT_REQ_INIT: + FALLTHROUGH(); case CONNECT_REQ_INIT: +CONNECT_REQ_INIT: /* * This is currently not supporting "Identification Protocol (RFC1413)". */ @@ -430,7 +437,7 @@ CONNECT_REQ_INIT: /* append hostname */ hostnamelen = strlen(sx->hostname) + 1; /* length including NUL */ if((hostnamelen <= 255) && - (packetsize + hostnamelen < data->set.buffer_size)) + (packetsize + hostnamelen < sizeof(sx->buffer))) strcpy((char *)socksreq + packetsize, sx->hostname); else { failf(data, "SOCKS4: too long host name"); @@ -439,10 +446,11 @@ CONNECT_REQ_INIT: packetsize += hostnamelen; } sx->outp = socksreq; + DEBUGASSERT(packetsize <= sizeof(sx->buffer)); sx->outstanding = packetsize; sxstate(sx, data, CONNECT_REQ_SENDING); } - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_SENDING: /* Send request */ presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, @@ -458,7 +466,7 @@ CONNECT_REQ_INIT: sx->outp = socksreq; sxstate(sx, data, CONNECT_SOCKS_READ); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_SOCKS_READ: /* Receive response */ presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, @@ -570,14 +578,14 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, o X'00' succeeded */ struct connectdata *conn = cf->conn; - unsigned char *socksreq = (unsigned char *)data->state.buffer; - int idx; + unsigned char *socksreq = sx->buffer; + size_t idx; CURLcode result; CURLproxycode presult; bool socks5_resolve_local = (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE; const size_t hostname_len = strlen(sx->hostname); - ssize_t len = 0; + size_t len = 0; const unsigned char auth = data->set.socks5auth; bool allow_gssapi = FALSE; struct Curl_dns_entry *dns = NULL; @@ -620,6 +628,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, socksreq[1] = (unsigned char) (idx - 2); sx->outp = socksreq; + DEBUGASSERT(idx <= sizeof(sx->buffer)); sx->outstanding = idx; presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, "initial SOCKS5 request"); @@ -640,12 +649,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* remain in sending state */ return CURLPX_OK; } - /* FALLTHROUGH */ -CONNECT_SOCKS_READ_INIT: + FALLTHROUGH(); case CONNECT_SOCKS_READ_INIT: +CONNECT_SOCKS_READ_INIT: sx->outstanding = 2; /* expect two bytes */ sx->outp = socksreq; /* store it here */ - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_SOCKS_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, "initial SOCKS5 response"); @@ -746,10 +755,11 @@ CONNECT_AUTH_INIT: } len += proxy_password_len; sxstate(sx, data, CONNECT_AUTH_SEND); + DEBUGASSERT(len <= sizeof(sx->buffer)); sx->outstanding = len; sx->outp = socksreq; } - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_AUTH_SEND: presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH, "SOCKS5 sub-negotiation request"); @@ -762,7 +772,7 @@ CONNECT_AUTH_INIT: sx->outp = socksreq; sx->outstanding = 2; sxstate(sx, data, CONNECT_AUTH_READ); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_AUTH_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH, "SOCKS5 sub-negotiation response"); @@ -781,9 +791,9 @@ CONNECT_AUTH_INIT: /* Everything is good so far, user was authenticated! */ sxstate(sx, data, CONNECT_REQ_INIT); - /* FALLTHROUGH */ -CONNECT_REQ_INIT: + FALLTHROUGH(); case CONNECT_REQ_INIT: +CONNECT_REQ_INIT: if(socks5_resolve_local) { enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns); @@ -806,8 +816,8 @@ CONNECT_REQ_INIT: if(dns) { #ifdef CURLRES_ASYNCH - conn->resolve_async.dns = dns; - conn->resolve_async.done = TRUE; + data->state.async.dns = dns; + data->state.async.done = TRUE; #endif infof(data, "SOCKS5: hostname '%s' found", sx->hostname); } @@ -820,9 +830,10 @@ CONNECT_REQ_INIT: return CURLPX_OK; } } - /* FALLTHROUGH */ + FALLTHROUGH(); + case CONNECT_RESOLVED: CONNECT_RESOLVED: - case CONNECT_RESOLVED: { + { char dest[MAX_IPADR_LEN]; /* printable address */ struct Curl_addrinfo *hp = NULL; if(dns) @@ -923,10 +934,10 @@ CONNECT_RESOLVE_REMOTE: infof(data, "SOCKS5 connect to %s:%d (remotely resolved)", sx->hostname, sx->remote_port); } - /* FALLTHROUGH */ + FALLTHROUGH(); -CONNECT_REQ_SEND: case CONNECT_REQ_SEND: +CONNECT_REQ_SEND: /* PORT MSB */ socksreq[len++] = (unsigned char)((sx->remote_port >> 8) & 0xff); /* PORT LSB */ @@ -939,9 +950,10 @@ CONNECT_REQ_SEND: } #endif sx->outp = socksreq; + DEBUGASSERT(len <= sizeof(sx->buffer)); sx->outstanding = len; sxstate(sx, data, CONNECT_REQ_SENDING); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_SENDING: presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST, "SOCKS5 connect request"); @@ -960,7 +972,7 @@ CONNECT_REQ_SEND: sx->outstanding = 10; /* minimum packet size is 10 */ sx->outp = socksreq; sxstate(sx, data, CONNECT_REQ_READ); - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_READ: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK, "SOCKS5 connect request ack"); @@ -1038,6 +1050,7 @@ CONNECT_REQ_SEND: /* decrypt_gssapi_blockread already read the whole packet */ #endif if(len > 10) { + DEBUGASSERT(len <= sizeof(sx->buffer)); sx->outstanding = len - 10; /* get the rest */ sx->outp = &socksreq[10]; sxstate(sx, data, CONNECT_REQ_READ_MORE); @@ -1049,7 +1062,7 @@ CONNECT_REQ_SEND: #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) } #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CONNECT_REQ_READ_MORE: presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS, "SOCKS5 connect request address"); diff --git a/Utilities/cmcurl/lib/socks_gssapi.c b/Utilities/cmcurl/lib/socks_gssapi.c index 2ede8c7..2437150 100644 --- a/Utilities/cmcurl/lib/socks_gssapi.c +++ b/Utilities/cmcurl/lib/socks_gssapi.c @@ -35,6 +35,7 @@ #include "timeval.h" #include "socks.h" #include "warnless.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -139,10 +140,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, /* prepare service name */ if(strchr(serviceptr, '/')) { service.length = serviceptr_length; - service.value = malloc(service.length); + service.value = Curl_memdup(serviceptr, service.length); if(!service.value) return CURLE_OUT_OF_MEMORY; - memcpy(service.value, serviceptr, service.length); gss_major_status = gss_import_name(&gss_minor_status, &service, (gss_OID) GSS_C_NULL_OID, &server); @@ -387,12 +387,11 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, } else { gss_send_token.length = 1; - gss_send_token.value = malloc(1); + gss_send_token.value = Curl_memdup(&gss_enc, 1); if(!gss_send_token.value) { gss_delete_sec_context(&gss_status, &gss_context, NULL); return CURLE_OUT_OF_MEMORY; } - memcpy(gss_send_token.value, &gss_enc, 1); gss_major_status = gss_wrap(&gss_minor_status, gss_context, 0, GSS_C_QOP_DEFAULT, &gss_send_token, diff --git a/Utilities/cmcurl/lib/socks_sspi.c b/Utilities/cmcurl/lib/socks_sspi.c index d1200ea..2baae2c 100644 --- a/Utilities/cmcurl/lib/socks_sspi.c +++ b/Utilities/cmcurl/lib/socks_sspi.c @@ -331,9 +331,15 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, failf(data, "Failed to determine user name."); return CURLE_COULDNT_CONNECT; } - infof(data, "SOCKS5 server authenticated user %s with GSS-API.", - names.sUserName); - s_pSecFn->FreeContextBuffer(names.sUserName); + else { +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char *user_utf8 = curlx_convert_tchar_to_UTF8(names.sUserName); + infof(data, "SOCKS5 server authenticated user %s with GSS-API.", + (user_utf8 ? user_utf8 : "(unknown)")); + curlx_unicodefree(user_utf8); +#endif + s_pSecFn->FreeContextBuffer(names.sUserName); + } /* Do encryption */ socksreq[0] = 1; /* GSS-API subnegotiation version */ diff --git a/Utilities/cmcurl/lib/strdup.c b/Utilities/cmcurl/lib/strdup.c index 2578441..299c9cc 100644 --- a/Utilities/cmcurl/lib/strdup.c +++ b/Utilities/cmcurl/lib/strdup.c @@ -101,21 +101,17 @@ void *Curl_memdup(const void *src, size_t length) /*************************************************************************** * - * Curl_strndup(source, length) + * Curl_memdup0(source, length) * * Copies the 'source' string to a newly allocated buffer (that is returned). - * Copies not more than 'length' bytes (up to a null terminator) then adds a - * null terminator. + * Copies 'length' bytes then adds a null terminator. * * Returns the new pointer or NULL on failure. * ***************************************************************************/ -void *Curl_strndup(const char *src, size_t length) +void *Curl_memdup0(const char *src, size_t length) { - char *buf = memchr(src, '\0', length); - if(buf) - length = buf - src; - buf = malloc(length + 1); + char *buf = malloc(length + 1); if(!buf) return NULL; memcpy(buf, src, length); diff --git a/Utilities/cmcurl/lib/strdup.h b/Utilities/cmcurl/lib/strdup.h index 9f12b25..238a261 100644 --- a/Utilities/cmcurl/lib/strdup.h +++ b/Utilities/cmcurl/lib/strdup.h @@ -33,6 +33,6 @@ wchar_t* Curl_wcsdup(const wchar_t* src); #endif void *Curl_memdup(const void *src, size_t buffer_length); void *Curl_saferealloc(void *ptr, size_t size); -void *Curl_strndup(const char *src, size_t length); +void *Curl_memdup0(const char *src, size_t length); #endif /* HEADER_CURL_STRDUP_H */ diff --git a/Utilities/cmcurl/lib/strerror.c b/Utilities/cmcurl/lib/strerror.c index 0d5f927..a900e78 100644 --- a/Utilities/cmcurl/lib/strerror.c +++ b/Utilities/cmcurl/lib/strerror.c @@ -319,6 +319,9 @@ curl_easy_strerror(CURLcode error) case CURLE_UNRECOVERABLE_POLL: return "Unrecoverable error in select/poll"; + case CURLE_TOO_LARGE: + return "A value or data field grew larger than allowed"; + /* error codes not used by current libcurl */ case CURLE_OBSOLETE20: case CURLE_OBSOLETE24: @@ -553,6 +556,9 @@ curl_url_strerror(CURLUcode error) case CURLUE_LACKS_IDN: return "libcurl lacks IDN support"; + case CURLUE_TOO_LARGE: + return "A value or data field is larger than allowed"; + case CURLUE_LAST: break; } @@ -572,10 +578,11 @@ curl_url_strerror(CURLUcode error) * Returns NULL if no error message was found for error code. */ static const char * -get_winsock_error (int err, char *buf, size_t len) +get_winsock_error(int err, char *buf, size_t len) { #ifndef CURL_DISABLE_VERBOSE_STRINGS const char *p; + size_t alen; #endif if(!len) @@ -755,8 +762,9 @@ get_winsock_error (int err, char *buf, size_t len) default: return NULL; } - strncpy(buf, p, len); - buf [len-1] = '\0'; + alen = strlen(p); + if(alen < len) + strcpy(buf, p); return buf; #endif } @@ -832,7 +840,6 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) #endif int old_errno = errno; char *p; - size_t max; if(!buflen) return NULL; @@ -841,23 +848,22 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) DEBUGASSERT(err >= 0); #endif - max = buflen - 1; *buf = '\0'; #if defined(_WIN32) || defined(_WIN32_WCE) #if defined(_WIN32) /* 'sys_nerr' is the maximum errno number, it is not widely portable */ if(err >= 0 && err < sys_nerr) - strncpy(buf, sys_errlist[err], max); + msnprintf(buf, buflen, "%s", sys_errlist[err]); else #endif { if( #ifdef USE_WINSOCK - !get_winsock_error(err, buf, max) && + !get_winsock_error(err, buf, buflen) && #endif - !get_winapi_error((DWORD)err, buf, max)) - msnprintf(buf, max, "Unknown error %d (%#x)", err, err); + !get_winapi_error((DWORD)err, buf, buflen)) + msnprintf(buf, buflen, "Unknown error %d (%#x)", err, err); } #else /* not Windows coming up */ @@ -867,9 +873,9 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) * storage is supplied via 'strerrbuf' and 'buflen' to hold the generated * message string, or EINVAL if 'errnum' is not a valid error number. */ - if(0 != strerror_r(err, buf, max)) { + if(0 != strerror_r(err, buf, buflen)) { if('\0' == buf[0]) - msnprintf(buf, max, "Unknown error %d", err); + msnprintf(buf, buflen, "Unknown error %d", err); } #elif defined(HAVE_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R) /* @@ -881,25 +887,23 @@ const char *Curl_strerror(int err, char *buf, size_t buflen) char buffer[256]; char *msg = strerror_r(err, buffer, sizeof(buffer)); if(msg) - strncpy(buf, msg, max); + msnprintf(buf, buflen, "%s", msg); else - msnprintf(buf, max, "Unknown error %d", err); + msnprintf(buf, buflen, "Unknown error %d", err); } #else { /* !checksrc! disable STRERROR 1 */ const char *msg = strerror(err); if(msg) - strncpy(buf, msg, max); + msnprintf(buf, buflen, "%s", msg); else - msnprintf(buf, max, "Unknown error %d", err); + msnprintf(buf, buflen, "Unknown error %d", err); } #endif #endif /* end of not Windows */ - buf[max] = '\0'; /* make sure the string is null-terminated */ - /* strip trailing '\r\n' or '\n'. */ p = strrchr(buf, '\n'); if(p && (p - buf) >= 2) @@ -943,8 +947,8 @@ const char *Curl_winapi_strerror(DWORD err, char *buf, size_t buflen) #else { const char *txt = (err == ERROR_SUCCESS) ? "No error" : "Error"; - strncpy(buf, txt, buflen); - buf[buflen - 1] = '\0'; + if(strlen(txt) < buflen) + strcpy(buf, txt); } #endif @@ -1081,17 +1085,11 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) err); } else { - char txtbuf[80]; char msgbuf[256]; - - msnprintf(txtbuf, sizeof(txtbuf), "%s (0x%08X)", txt, err); - if(get_winapi_error(err, msgbuf, sizeof(msgbuf))) - msnprintf(buf, buflen, "%s - %s", txtbuf, msgbuf); - else { - strncpy(buf, txtbuf, buflen); - buf[buflen - 1] = '\0'; - } + msnprintf(buf, buflen, "%s (0x%08X) - %s", txt, err, msgbuf); + else + msnprintf(buf, buflen, "%s (0x%08X)", txt, err); } #else @@ -1099,8 +1097,8 @@ const char *Curl_sspi_strerror(int err, char *buf, size_t buflen) txt = "No error"; else txt = "Error"; - strncpy(buf, txt, buflen); - buf[buflen - 1] = '\0'; + if(buflen > strlen(txt)) + strcpy(buf, txt); #endif if(errno != old_errno) diff --git a/Utilities/cmcurl/lib/system_win32.c b/Utilities/cmcurl/lib/system_win32.c index 9408d02..d2862de 100644 --- a/Utilities/cmcurl/lib/system_win32.c +++ b/Utilities/cmcurl/lib/system_win32.c @@ -38,16 +38,23 @@ LARGE_INTEGER Curl_freq; bool Curl_isVistaOrGreater; +bool Curl_isWindows8OrGreater; /* Handle of iphlpapp.dll */ static HMODULE s_hIpHlpApiDll = NULL; -/* Pointer to the if_nametoindex function */ +/* Function pointers */ IF_NAMETOINDEX_FN Curl_if_nametoindex = NULL; +FREEADDRINFOEXW_FN Curl_FreeAddrInfoExW = NULL; +GETADDRINFOEXCANCEL_FN Curl_GetAddrInfoExCancel = NULL; +GETADDRINFOEXW_FN Curl_GetAddrInfoExW = NULL; /* Curl_win32_init() performs win32 global initialization */ CURLcode Curl_win32_init(long flags) { +#ifdef USE_WINSOCK + HMODULE ws2_32Dll; +#endif /* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which is just for Winsock at the moment. Any required win32 initialization should take place after this block. */ @@ -104,6 +111,18 @@ CURLcode Curl_win32_init(long flags) Curl_if_nametoindex = pIfNameToIndex; } +#ifdef USE_WINSOCK + ws2_32Dll = GetModuleHandleA("ws2_32"); + if(ws2_32Dll) { + Curl_FreeAddrInfoExW = CURLX_FUNCTION_CAST(FREEADDRINFOEXW_FN, + GetProcAddress(ws2_32Dll, "FreeAddrInfoExW")); + Curl_GetAddrInfoExCancel = CURLX_FUNCTION_CAST(GETADDRINFOEXCANCEL_FN, + GetProcAddress(ws2_32Dll, "GetAddrInfoExCancel")); + Curl_GetAddrInfoExW = CURLX_FUNCTION_CAST(GETADDRINFOEXW_FN, + GetProcAddress(ws2_32Dll, "GetAddrInfoExW")); + } +#endif + /* curlx_verify_windows_version must be called during init at least once because it has its own initialization routine. */ if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, @@ -113,6 +132,13 @@ CURLcode Curl_win32_init(long flags) else Curl_isVistaOrGreater = FALSE; + if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + Curl_isWindows8OrGreater = TRUE; + } + else + Curl_isWindows8OrGreater = FALSE; + QueryPerformanceFrequency(&Curl_freq); return CURLE_OK; } @@ -120,6 +146,9 @@ CURLcode Curl_win32_init(long flags) /* Curl_win32_cleanup() is the opposite of Curl_win32_init() */ void Curl_win32_cleanup(long init_flags) { + Curl_FreeAddrInfoExW = NULL; + Curl_GetAddrInfoExCancel = NULL; + Curl_GetAddrInfoExW = NULL; if(s_hIpHlpApiDll) { FreeLibrary(s_hIpHlpApiDll); s_hIpHlpApiDll = NULL; diff --git a/Utilities/cmcurl/lib/system_win32.h b/Utilities/cmcurl/lib/system_win32.h index 2566766..bd490ca 100644 --- a/Utilities/cmcurl/lib/system_win32.h +++ b/Utilities/cmcurl/lib/system_win32.h @@ -26,10 +26,11 @@ #include "curl_setup.h" -#if defined(_WIN32) +#ifdef _WIN32 extern LARGE_INTEGER Curl_freq; extern bool Curl_isVistaOrGreater; +extern bool Curl_isWindows8OrGreater; CURLcode Curl_win32_init(long flags); void Curl_win32_cleanup(long init_flags); @@ -40,6 +41,33 @@ typedef unsigned int(WINAPI *IF_NAMETOINDEX_FN)(const char *); /* This is used instead of if_nametoindex if available on Windows */ extern IF_NAMETOINDEX_FN Curl_if_nametoindex; +/* Identical copy of addrinfoexW/ADDRINFOEXW */ +typedef struct addrinfoexW_ +{ + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + PWSTR ai_canonname; + struct sockaddr *ai_addr; + void *ai_blob; + size_t ai_bloblen; + LPGUID ai_provider; + struct addrinfoexW_ *ai_next; +} ADDRINFOEXW_; + +typedef void (CALLBACK *LOOKUP_COMPLETION_FN)(DWORD, DWORD, LPWSAOVERLAPPED); +typedef void (WSAAPI *FREEADDRINFOEXW_FN)(ADDRINFOEXW_*); +typedef int (WSAAPI *GETADDRINFOEXCANCEL_FN)(LPHANDLE); +typedef int (WSAAPI *GETADDRINFOEXW_FN)(PCWSTR, PCWSTR, DWORD, LPGUID, + const ADDRINFOEXW_*, ADDRINFOEXW_**, struct timeval*, LPOVERLAPPED, + LOOKUP_COMPLETION_FN, LPHANDLE); + +extern FREEADDRINFOEXW_FN Curl_FreeAddrInfoExW; +extern GETADDRINFOEXCANCEL_FN Curl_GetAddrInfoExCancel; +extern GETADDRINFOEXW_FN Curl_GetAddrInfoExW; + /* This is used to dynamically load DLLs */ HMODULE Curl_load_library(LPCTSTR filename); #else /* _WIN32 */ diff --git a/Utilities/cmcurl/lib/telnet.c b/Utilities/cmcurl/lib/telnet.c index 836e255..34dc5e8 100644 --- a/Utilities/cmcurl/lib/telnet.c +++ b/Utilities/cmcurl/lib/telnet.c @@ -160,6 +160,7 @@ struct TELNET { unsigned short subopt_wsy; /* Set with suboption NAWS */ TelnetReceive telrcv_state; struct curl_slist *telnet_vars; /* Environment variables */ + struct dynbuf out; /* output buffer */ /* suboptions */ unsigned char subbuffer[SUBBUFSIZE]; @@ -185,7 +186,7 @@ const struct Curl_handler Curl_handler_telnet = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_TELNET, /* defport */ @@ -204,6 +205,7 @@ CURLcode init_telnet(struct Curl_easy *data) if(!tn) return CURLE_OUT_OF_MEMORY; + Curl_dyn_init(&tn->out, 0xffff); data->req.p.telnet = tn; /* make us known */ tn->telrcv_state = CURL_TS_DATA; @@ -799,8 +801,10 @@ static CURLcode check_telnet_options(struct Curl_easy *data) was given on the command line */ if(data->state.aptr.user) { char buffer[256]; - if(str_is_nonascii(data->conn->user)) + if(str_is_nonascii(data->conn->user)) { + DEBUGF(infof(data, "set a non ASCII user name in telnet")); return CURLE_BAD_FUNCTION_ARGUMENT; + } msnprintf(buffer, sizeof(buffer), "USER,%s", data->conn->user); beg = curl_slist_append(tn->telnet_vars, buffer); if(!beg) { @@ -826,23 +830,27 @@ static CURLcode check_telnet_options(struct Curl_easy *data) case 5: /* Terminal type */ if(strncasecompare(option, "TTYPE", 5)) { - strncpy(tn->subopt_ttype, arg, 31); - tn->subopt_ttype[31] = 0; /* String termination */ - tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES; + size_t l = strlen(arg); + if(l < sizeof(tn->subopt_ttype)) { + strcpy(tn->subopt_ttype, arg); + tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES; + break; + } } - else - result = CURLE_UNKNOWN_OPTION; + result = CURLE_UNKNOWN_OPTION; break; case 8: /* Display variable */ if(strncasecompare(option, "XDISPLOC", 8)) { - strncpy(tn->subopt_xdisploc, arg, 127); - tn->subopt_xdisploc[127] = 0; /* String termination */ - tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; + size_t l = strlen(arg); + if(l < sizeof(tn->subopt_xdisploc)) { + strcpy(tn->subopt_xdisploc, arg); + tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; + break; + } } - else - result = CURLE_UNKNOWN_OPTION; + result = CURLE_UNKNOWN_OPTION; break; case 7: @@ -1223,37 +1231,33 @@ process_iac: static CURLcode send_telnet_data(struct Curl_easy *data, char *buffer, ssize_t nread) { - ssize_t escapes, i, outlen; - unsigned char *outbuf = NULL; + ssize_t i, outlen; + unsigned char *outbuf; CURLcode result = CURLE_OK; - ssize_t bytes_written, total_written; + ssize_t bytes_written, total_written = 0; struct connectdata *conn = data->conn; + struct TELNET *tn = data->req.p.telnet; - /* Determine size of new buffer after escaping */ - escapes = 0; - for(i = 0; i < nread; i++) - if((unsigned char)buffer[i] == CURL_IAC) - escapes++; - outlen = nread + escapes; + DEBUGASSERT(tn); - if(outlen == nread) - outbuf = (unsigned char *)buffer; - else { - ssize_t j; - outbuf = malloc(nread + escapes + 1); - if(!outbuf) - return CURLE_OUT_OF_MEMORY; + if(memchr(buffer, CURL_IAC, nread)) { + /* only use the escape buffer when necessary */ + Curl_dyn_reset(&tn->out); - j = 0; - for(i = 0; i < nread; i++) { - outbuf[j++] = (unsigned char)buffer[i]; - if((unsigned char)buffer[i] == CURL_IAC) - outbuf[j++] = CURL_IAC; + for(i = 0; i < nread && !result; i++) { + result = Curl_dyn_addn(&tn->out, &buffer[i], 1); + if(!result && ((unsigned char)buffer[i] == CURL_IAC)) + /* IAC is FF in hex */ + result = Curl_dyn_addn(&tn->out, "\xff", 1); } - outbuf[j] = '\0'; - } - total_written = 0; + outlen = Curl_dyn_len(&tn->out); + outbuf = Curl_dyn_uptr(&tn->out); + } + else { + outlen = nread; + outbuf = (unsigned char *)buffer; + } while(!result && total_written < outlen) { /* Make sure socket is writable to avoid EWOULDBLOCK condition */ struct pollfd pfd[1]; @@ -1266,19 +1270,13 @@ static CURLcode send_telnet_data(struct Curl_easy *data, break; default: /* write! */ bytes_written = 0; - result = Curl_nwrite(data, FIRSTSOCKET, - outbuf + total_written, - outlen - total_written, - &bytes_written); + result = Curl_nwrite(data, FIRSTSOCKET, outbuf + total_written, + outlen - total_written, &bytes_written); total_written += bytes_written; break; } } - /* Free malloc copy if escaped */ - if(outbuf != (unsigned char *)buffer) - free(outbuf); - return result; } @@ -1294,6 +1292,7 @@ static CURLcode telnet_done(struct Curl_easy *data, curl_slist_free_all(tn->telnet_vars); tn->telnet_vars = NULL; + Curl_dyn_free(&tn->out); return CURLE_OK; } @@ -1321,7 +1320,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) ssize_t nread; struct curltime now; bool keepon = TRUE; - char *buf = data->state.buffer; + char buffer[4*1024]; struct TELNET *tn; *done = TRUE; /* unconditionally */ @@ -1378,7 +1377,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) /* Keep on listening and act on events */ while(keepon) { - const DWORD buf_size = (DWORD)data->set.buffer_size; + const DWORD buf_size = (DWORD)sizeof(buffer); DWORD waitret = WaitForMultipleObjects(obj_count, objs, FALSE, wait_timeout); switch(waitret) { @@ -1389,7 +1388,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) if(data->set.is_fread_set) { size_t n; /* read from user-supplied method */ - n = data->state.fread_func(buf, 1, buf_size, data->state.in); + n = data->state.fread_func(buffer, 1, buf_size, data->state.in); if(n == CURL_READFUNC_ABORT) { keepon = FALSE; result = CURLE_READ_ERROR; @@ -1417,7 +1416,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) if(!readfile_read) break; - if(!ReadFile(stdin_handle, buf, buf_size, + if(!ReadFile(stdin_handle, buffer, buf_size, &readfile_read, NULL)) { keepon = FALSE; result = CURLE_READ_ERROR; @@ -1425,7 +1424,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } } - result = send_telnet_data(data, buf, readfile_read); + result = send_telnet_data(data, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1436,14 +1435,14 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) case WAIT_OBJECT_0 + 1: { - if(!ReadFile(stdin_handle, buf, buf_size, + if(!ReadFile(stdin_handle, buffer, buf_size, &readfile_read, NULL)) { keepon = FALSE; result = CURLE_READ_ERROR; break; } - result = send_telnet_data(data, buf, readfile_read); + result = send_telnet_data(data, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1465,7 +1464,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } if(events.lNetworkEvents & FD_READ) { /* read data from network */ - result = Curl_read(data, sockfd, buf, data->set.buffer_size, &nread); + result = Curl_read(data, sockfd, buffer, sizeof(buffer), &nread); /* read would've blocked. Loop again */ if(result == CURLE_AGAIN) break; @@ -1481,7 +1480,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) break; } - result = telrcv(data, (unsigned char *) buf, nread); + result = telrcv(data, (unsigned char *) buffer, nread); if(result) { keepon = FALSE; break; @@ -1542,11 +1541,11 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) case 0: /* timeout */ pfd[0].revents = 0; pfd[1].revents = 0; - /* FALLTHROUGH */ + FALLTHROUGH(); default: /* read! */ if(pfd[0].revents & POLLIN) { /* read data from network */ - result = Curl_read(data, sockfd, buf, data->set.buffer_size, &nread); + result = Curl_read(data, sockfd, buffer, sizeof(buffer), &nread); /* read would've blocked. Loop again */ if(result == CURLE_AGAIN) break; @@ -1572,7 +1571,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 *)buf, nread); + result = telrcv(data, (unsigned char *)buffer, nread); if(result) { keepon = FALSE; break; @@ -1590,12 +1589,12 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) nread = 0; if(poll_cnt == 2) { if(pfd[1].revents & POLLIN) { /* read from in file */ - nread = read(pfd[1].fd, buf, data->set.buffer_size); + nread = read(pfd[1].fd, buffer, sizeof(buffer)); } } else { /* read from user-supplied method */ - nread = (int)data->state.fread_func(buf, 1, data->set.buffer_size, + nread = (int)data->state.fread_func(buffer, 1, sizeof(buffer), data->state.in); if(nread == CURL_READFUNC_ABORT) { keepon = FALSE; @@ -1606,7 +1605,7 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } if(nread > 0) { - result = send_telnet_data(data, buf, nread); + result = send_telnet_data(data, buffer, nread); if(result) { keepon = FALSE; break; diff --git a/Utilities/cmcurl/lib/tftp.c b/Utilities/cmcurl/lib/tftp.c index 6630155..4288110 100644 --- a/Utilities/cmcurl/lib/tftp.c +++ b/Utilities/cmcurl/lib/tftp.c @@ -181,7 +181,7 @@ const struct Curl_handler Curl_handler_tftp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ tftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_TFTP, /* defport */ diff --git a/Utilities/cmcurl/lib/transfer.c b/Utilities/cmcurl/lib/transfer.c index 96f1fde..3ae4b61 100644 --- a/Utilities/cmcurl/lib/transfer.c +++ b/Utilities/cmcurl/lib/transfer.c @@ -413,321 +413,148 @@ bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc) return TRUE; } +/** + * Receive raw response data for the transfer. + * @param data the transfer + * @param buf buffer to keep response data received + * @param blen length of `buf` + * @param eos_reliable if EOS detection in underlying connection is reliable + * @param err error code in case of -1 return + * @return number of bytes read or -1 for error + */ +static ssize_t Curl_xfer_recv_resp(struct Curl_easy *data, + char *buf, size_t blen, + bool eos_reliable, + CURLcode *err) +{ + ssize_t nread; + + DEBUGASSERT(blen > 0); + /* If we are reading BODY data and the connection does NOT handle EOF + * and we know the size of the BODY data, limit the read amount */ + if(!eos_reliable && !data->req.header && data->req.size != -1) { + curl_off_t totalleft = data->req.size - data->req.bytecount; + if(totalleft <= 0) + blen = 0; + else if(totalleft < (curl_off_t)blen) + blen = (size_t)totalleft; + } + + if(!blen) { + /* want nothing - continue as if read nothing. */ + DEBUGF(infof(data, "readwrite_data: we're done")); + *err = CURLE_OK; + return 0; + } + + *err = Curl_read(data, data->conn->sockfd, buf, blen, &nread); + if(*err) + return -1; + DEBUGASSERT(nread >= 0); + *err = CURLE_OK; + return nread; +} + /* * Go ahead and do a read if we have a readable socket or if * the stream was rewound (in which case we have data in a * buffer) - * - * return '*comeback' TRUE if we didn't properly drain the socket so this - * function should get called again without select() or similar in between! */ static CURLcode readwrite_data(struct Curl_easy *data, - struct connectdata *conn, struct SingleRequest *k, - int *didwhat, bool *done, - bool *comeback) + int *didwhat, bool *done) { + struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; char *buf; size_t blen; - size_t consumed; - int maxloops = 100; - curl_off_t max_recv = data->set.max_recv_speed? - data->set.max_recv_speed : CURL_OFF_T_MAX; - bool data_eof_handled = FALSE; + int maxloops = 10; + curl_off_t total_received = 0; + bool is_multiplex = FALSE; DEBUGASSERT(data->state.buffer); *done = FALSE; - *comeback = FALSE; /* This is where we loop until we have read everything there is to read or we get a CURLE_AGAIN */ do { - bool is_empty_data = FALSE; - size_t bytestoread = data->set.buffer_size; - /* For HTTP/2 and HTTP/3, read data without caring about the content - length. This is safe because body in HTTP/2 is always segmented - thanks to its framing layer. Meanwhile, we have to call Curl_read - to ensure that http2_handle_stream_close is called when we read all - incoming bytes for a particular stream. */ - bool is_http3 = Curl_conn_is_http3(data, conn, FIRSTSOCKET); - data_eof_handled = is_http3 || Curl_conn_is_http2(data, conn, FIRSTSOCKET); - - /* Each loop iteration starts with a fresh buffer and handles - * all data read into it. */ + bool is_eos = FALSE; + size_t bytestoread; + ssize_t nread; + + if(!is_multiplex) { + /* Multiplexed connection have inherent handling of EOF and we do not + * have to carefully restrict the amount we try to read. + * Multiplexed changes only in one direction. */ + is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET); + } + buf = data->state.buffer; - blen = 0; - - /* If we are reading BODY data and the connection does NOT handle EOF - * and we know the size of the BODY data, limit the read amount */ - if(!k->header && !data_eof_handled && k->size != -1) { - curl_off_t totalleft = k->size - k->bytecount; - if(totalleft <= 0) - bytestoread = 0; - else if(totalleft < (curl_off_t)bytestoread) - bytestoread = (size_t)totalleft; + bytestoread = data->set.buffer_size; + + /* Observe any imposed speed limit */ + if(bytestoread && data->set.max_recv_speed) { + curl_off_t net_limit = data->set.max_recv_speed - total_received; + if(net_limit <= 0) + break; + if((size_t)net_limit < bytestoread) + bytestoread = (size_t)net_limit; } - if(bytestoread) { - /* receive data from the network! */ - ssize_t nread; /* number of bytes read */ - result = Curl_read(data, conn->sockfd, buf, bytestoread, &nread); + nread = Curl_xfer_recv_resp(data, buf, bytestoread, + is_multiplex, &result); + if(nread < 0) { if(CURLE_AGAIN == result) { result = CURLE_OK; break; /* get out of loop */ } - else if(result) - goto out; - DEBUGASSERT(nread >= 0); - blen = (size_t)nread; - } - else { - /* read nothing but since we wanted nothing we consider this an OK - situation to proceed from */ - DEBUGF(infof(data, "readwrite_data: we're done")); - } - - if(!k->bytecount) { - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - if(k->exp100 > EXP100_SEND_DATA) - /* set time stamp to compare with when waiting for the 100 */ - k->start100 = Curl_now(); + goto out; /* real error */ } + /* We only get a 0-length read on EndOfStream */ + blen = (size_t)nread; + is_eos = (blen == 0); *didwhat |= KEEP_RECV; - /* indicates data of zero size, i.e. empty file */ - is_empty_data = ((blen == 0) && (k->bodywrites == 0)) ? TRUE : FALSE; - - if(0 < blen || is_empty_data) { - /* data->state.buffer is allocated 1 byte larger than - * data->set.buffer_size admits. *wink* */ - /* TODO: we should really not rely on this being 0-terminated, since - * the actual data read might contain 0s. */ - buf[blen] = 0; - } if(!blen) { /* if we receive 0 or less here, either the data transfer is done or the server closed the connection and we bail out from this! */ - if(data_eof_handled) + if(is_multiplex) DEBUGF(infof(data, "nread == 0, stream closed, bailing")); else DEBUGF(infof(data, "nread <= 0, server closed connection, bailing")); - k->keepon = 0; /* stop sending as well */ - if(!is_empty_data) - break; - } - - if(conn->handler->readwrite) { - bool readmore = FALSE; /* indicates data is incomplete, need more */ - consumed = 0; - result = conn->handler->readwrite(data, conn, buf, blen, - &consumed, &readmore); - if(result) - goto out; - if(readmore) - break; - buf += consumed; - blen -= consumed; - if(k->download_done) { - /* We've stopped dealing with input, get out of the do-while loop */ - if(blen > 0) { - infof(data, - "Excess found:" - " excess = %zu" - " url = %s (zero-length body)", - blen, data->state.up.path); - } - - /* we make sure that this socket isn't read more now */ - k->keepon &= ~KEEP_RECV; + if(k->eos_written) { /* already did write this to client, leave */ + k->keepon = 0; /* stop sending as well */ break; } } + total_received += blen; -#ifndef CURL_DISABLE_HTTP - /* Since this is a two-state thing, we check if we are parsing - headers at the moment or not. */ - if(k->header) { - consumed = 0; - result = Curl_http_readwrite_headers(data, conn, buf, blen, &consumed); - if(result) - goto out; - buf += consumed; - blen -= consumed; - - if(conn->handler->readwrite && - (k->maxdownload <= 0 && blen > 0)) { - bool readmore = FALSE; /* indicates data is incomplete, need more */ - consumed = 0; - result = conn->handler->readwrite(data, conn, buf, blen, - &consumed, &readmore); - if(result) - goto out; - if(readmore) - break; - buf += consumed; - blen -= consumed; - } - - if(k->download_done) { - /* We've stopped dealing with input, get out of the do-while loop */ - if(blen > 0) { - infof(data, - "Excess found:" - " excess = %zu" - " url = %s (zero-length body)", - blen, data->state.up.path); - } - - /* we make sure that this socket isn't read more now */ - k->keepon &= ~KEEP_RECV; - break; - } - } -#endif /* CURL_DISABLE_HTTP */ - - - /* This is not an 'else if' since it may be a rest from the header - parsing, where the beginning of the buffer is headers and the end - is non-headers. */ - if(!k->header && (blen > 0 || is_empty_data)) { - - if(data->req.no_body && blen > 0) { - /* data arrives although we want none, bail out */ - streamclose(conn, "ignoring body"); - DEBUGF(infof(data, "did not want a BODY, but seeing %zu bytes", - blen)); - *done = TRUE; - result = CURLE_WEIRD_SERVER_REPLY; - goto out; - } - -#ifndef CURL_DISABLE_HTTP - if(0 == k->bodywrites && !is_empty_data) { - /* These checks are only made the first time we are about to - write a piece of the body */ - if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) { - /* HTTP-only checks */ - result = Curl_http_firstwrite(data, conn, done); - if(result || *done) - goto out; - } - } /* this is the first time we write a body part */ -#endif /* CURL_DISABLE_HTTP */ - -#ifndef CURL_DISABLE_HTTP - if(k->chunk) { - /* - * Here comes a chunked transfer flying and we need to decode this - * properly. While the name says read, this function both reads - * and writes away the data. - */ - CURLcode extra; - CHUNKcode res; - - consumed = 0; - res = Curl_httpchunk_read(data, buf, blen, &consumed, &extra); - - if(CHUNKE_OK < res) { - if(CHUNKE_PASSTHRU_ERROR == res) { - failf(data, "Failed reading the chunked-encoded stream"); - result = extra; - goto out; - } - failf(data, "%s in chunked-encoding", Curl_chunked_strerror(res)); - result = CURLE_RECV_ERROR; - goto out; - } - - buf += consumed; - blen -= consumed; - if(CHUNKE_STOP == res) { - /* we're done reading chunks! */ - k->keepon &= ~KEEP_RECV; /* read no more */ - /* chunks read successfully, download is complete */ - k->download_done = TRUE; - - /* N number of bytes at the end of the str buffer that weren't - written to the client. */ - if(conn->chunk.datasize) { - infof(data, "Leftovers after chunking: % " - CURL_FORMAT_CURL_OFF_T "u bytes", - conn->chunk.datasize); - } - } - /* If it returned OK, we just keep going */ - } -#endif /* CURL_DISABLE_HTTP */ - - max_recv -= blen; - - if(!k->chunk && (blen || k->badheader || is_empty_data)) { - /* If this is chunky transfer, it was already written */ - - if(k->badheader) { - /* we parsed a piece of data wrongly assuming it was a header - and now we output it as body instead */ - size_t headlen = Curl_dyn_len(&data->state.headerb); - - /* Don't let excess data pollute body writes */ - if(k->maxdownload != -1 && (curl_off_t)headlen > k->maxdownload) - headlen = (size_t)k->maxdownload; - - result = Curl_client_write(data, CLIENTWRITE_BODY, - Curl_dyn_ptr(&data->state.headerb), - headlen); - if(result) - goto out; - } - - if(blen) { -#ifndef CURL_DISABLE_POP3 - if(conn->handler->protocol & PROTO_FAMILY_POP3) { - result = k->ignorebody? CURLE_OK : - Curl_pop3_write(data, buf, blen); - } - else -#endif /* CURL_DISABLE_POP3 */ - result = Curl_client_write(data, CLIENTWRITE_BODY, buf, blen); - } - k->badheader = FALSE; /* taken care of now */ - - if(result) - goto out; - } - - if(k->download_done && !is_http3) { - /* HTTP/3 over QUIC should keep reading until QUIC connection - is closed. In contrast to HTTP/2 which can stop reading - from TCP connection, HTTP/3 over QUIC needs ACK from server - to ensure stream closure. It should keep reading. */ - k->keepon &= ~KEEP_RECV; /* we're done reading */ - } - } /* if(!header and data to read) */ + result = Curl_xfer_write_resp(data, buf, blen, is_eos, done); + if(result || *done) + goto out; - if(is_empty_data) { - /* if we received nothing, the server closed the connection and we - are done */ - k->keepon &= ~KEEP_RECV; - k->download_done = TRUE; + /* if we are done, we stop receiving. On multiplexed connections, + * we should read the EOS. Which may arrive as meta data after + * the bytes. Not taking it in might lead to RST of streams. */ + if((!is_multiplex && data->req.download_done) || is_eos) { + data->req.keepon &= ~KEEP_RECV; } - - if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) { - /* this is a paused or stopped transfer */ + /* if we are PAUSEd or stopped receiving, leave the loop */ + if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) break; - } - } while((max_recv > 0) && data_pending(data) && maxloops--); + } while(maxloops-- && data_pending(data)); - if(maxloops <= 0 || max_recv <= 0) { - /* we mark it as read-again-please */ - data->state.dselect_bits = CURL_CSELECT_IN; - *comeback = TRUE; + if(maxloops <= 0) { + /* did not read until EAGAIN, mark read-again-please */ + data->state.select_bits = CURL_CSELECT_IN; + if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) + data->state.select_bits |= CURL_CSELECT_OUT; } if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) && - (conn->bits.close || data_eof_handled)) { + (conn->bits.close || is_multiplex)) { /* When we've read the entire thing and the close bit is set, the server may now close the connection. If there's now any kind of sending going on from our side, we need to stop that immediately. */ @@ -1023,46 +850,41 @@ static int select_bits_paused(struct Curl_easy *data, int select_bits) * of our state machine are handling PAUSED transfers correctly. So, we * do not want to go there. * NOTE: we are only interested in PAUSE, not HOLD. */ - return (((select_bits & CURL_CSELECT_IN) && - (data->req.keepon & KEEP_RECV_PAUSE)) || - ((select_bits & CURL_CSELECT_OUT) && - (data->req.keepon & KEEP_SEND_PAUSE))); + + /* if there is data in a direction not paused, return false */ + if(((select_bits & CURL_CSELECT_IN) && + !(data->req.keepon & KEEP_RECV_PAUSE)) || + ((select_bits & CURL_CSELECT_OUT) && + !(data->req.keepon & KEEP_SEND_PAUSE))) + return FALSE; + + return (data->req.keepon & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)); } /* * Curl_readwrite() is the low-level function to be called when data is to * be read and written to/from the connection. - * - * return '*comeback' TRUE if we didn't properly drain the socket so this - * function should get called again without select() or similar in between! */ -CURLcode Curl_readwrite(struct connectdata *conn, - struct Curl_easy *data, - bool *done, - bool *comeback) +CURLcode Curl_readwrite(struct Curl_easy *data, + bool *done) { + struct connectdata *conn = data->conn; struct SingleRequest *k = &data->req; CURLcode result; struct curltime now; int didwhat = 0; int select_bits; - if(data->state.dselect_bits) { - if(select_bits_paused(data, data->state.dselect_bits)) { + if(data->state.select_bits) { + if(select_bits_paused(data, data->state.select_bits)) { /* leave the bits unchanged, so they'll tell us what to do when * this transfer gets unpaused. */ - DEBUGF(infof(data, "readwrite, dselect_bits, early return on PAUSED")); + DEBUGF(infof(data, "readwrite, select_bits, early return on PAUSED")); result = CURLE_OK; goto out; } - select_bits = data->state.dselect_bits; - data->state.dselect_bits = 0; - } - else if(conn->cselect_bits) { - /* CAVEAT: adding `select_bits_paused()` check here makes test640 hang - * (among others). Which hints at strange state handling in FTP land... */ - select_bits = conn->cselect_bits; - conn->cselect_bits = 0; + select_bits = data->state.select_bits; + data->state.select_bits = 0; } else { curl_socket_t fd_read; @@ -1100,7 +922,7 @@ CURLcode Curl_readwrite(struct connectdata *conn, the stream was rewound (in which case we have data in a buffer) */ if((k->keepon & KEEP_RECV) && (select_bits & CURL_CSELECT_IN)) { - result = readwrite_data(data, conn, k, &didwhat, done, comeback); + result = readwrite_data(data, k, &didwhat, done); if(result || *done) goto out; } @@ -1196,21 +1018,6 @@ CURLcode Curl_readwrite(struct connectdata *conn, result = CURLE_PARTIAL_FILE; goto out; } - if(!(data->req.no_body) && k->chunk && - (conn->chunk.state != CHUNK_STOP)) { - /* - * In chunked mode, return an error if the connection is closed prior to - * the empty (terminating) chunk is read. - * - * The condition above used to check for - * conn->proto.http->chunk.datasize != 0 which is true after reading - * *any* chunk, not just the empty chunk. - * - */ - failf(data, "transfer closed with outstanding read data remaining"); - result = CURLE_PARTIAL_FILE; - goto out; - } if(Curl_pgrsUpdate(data)) { result = CURLE_ABORTED_BY_CALLBACK; goto out; @@ -1225,52 +1032,6 @@ out: return result; } -/* - * Curl_single_getsock() gets called by the multi interface code when the app - * has requested to get the sockets for the current connection. This function - * will then be called once for every connection that the multi interface - * keeps track of. This function will only be called for connections that are - * in the proper state to have this information available. - */ -int Curl_single_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *sock) -{ - int bitmap = GETSOCK_BLANK; - unsigned sockindex = 0; - - if(conn->handler->perform_getsock) - return conn->handler->perform_getsock(data, conn, sock); - - /* don't include HOLD and PAUSE connections */ - if((data->req.keepon & KEEP_RECVBITS) == KEEP_RECV) { - - DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); - - bitmap |= GETSOCK_READSOCK(sockindex); - sock[sockindex] = conn->sockfd; - } - - /* don't include HOLD and PAUSE connections */ - if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) { - if((conn->sockfd != conn->writesockfd) || - bitmap == GETSOCK_BLANK) { - /* only if they are not the same socket and we have a readable - one, we increase index */ - if(bitmap != GETSOCK_BLANK) - sockindex++; /* increase index if we need two entries */ - - DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); - - sock[sockindex] = conn->writesockfd; - } - - bitmap |= GETSOCK_WRITESOCK(sockindex); - } - - return bitmap; -} - /* Curl_init_CONNECT() gets called each time the handle switches to CONNECT which means this gets called once for each subsequent redirect etc */ void Curl_init_CONNECT(struct Curl_easy *data) @@ -1917,3 +1678,41 @@ Curl_setup_transfer( } /* if(k->getheader || !data->req.no_body) */ } + +CURLcode Curl_xfer_write_resp(struct Curl_easy *data, + char *buf, size_t blen, + bool is_eos, bool *done) +{ + CURLcode result = CURLE_OK; + + if(data->conn->handler->write_resp) { + /* protocol handlers offering this function take full responsibility + * for writing all received download data to the client. */ + result = data->conn->handler->write_resp(data, buf, blen, is_eos, done); + } + else { + /* No special handling by protocol handler, write all received data + * as BODY to the client. */ + if(blen || is_eos) { + int cwtype = CLIENTWRITE_BODY; + if(is_eos) + cwtype |= CLIENTWRITE_EOS; + +#ifndef CURL_DISABLE_POP3 + if(blen && data->conn->handler->protocol & PROTO_FAMILY_POP3) { + result = data->req.ignorebody? CURLE_OK : + Curl_pop3_write(data, buf, blen); + } + else +#endif /* CURL_DISABLE_POP3 */ + result = Curl_client_write(data, cwtype, buf, blen); + } + } + + if(!result && is_eos) { + /* If we wrote the EOS, we are definitely done */ + data->req.eos_written = TRUE; + data->req.download_done = TRUE; + } + return result; +} diff --git a/Utilities/cmcurl/lib/transfer.h b/Utilities/cmcurl/lib/transfer.h index 536ac24..0507f1a 100644 --- a/Utilities/cmcurl/lib/transfer.h +++ b/Utilities/cmcurl/lib/transfer.h @@ -45,9 +45,7 @@ typedef enum { CURLcode Curl_follow(struct Curl_easy *data, char *newurl, followtype type); -CURLcode Curl_readwrite(struct connectdata *conn, - struct Curl_easy *data, bool *done, - bool *comeback); +CURLcode Curl_readwrite(struct Curl_easy *data, bool *done); int Curl_single_getsock(struct Curl_easy *data, struct connectdata *conn, curl_socket_t *socks); CURLcode Curl_fillreadbuffer(struct Curl_easy *data, size_t bytes, @@ -59,6 +57,23 @@ CURLcode Curl_get_upload_buffer(struct Curl_easy *data); CURLcode Curl_done_sending(struct Curl_easy *data, struct SingleRequest *k); +/** + * Write the transfer raw response bytes, as received from the connection. + * Will handle all passed bytes or return an error. By default, this will + * write the bytes as BODY to the client. Protocols may provide a + * "write_resp" callback in their handler to add specific treatment. E.g. + * HTTP parses response headers and passes them differently to the client. + * @param data the transfer + * @param buf the raw response bytes + * @param blen the amount of bytes in `buf` + * @param is_eos TRUE iff the connection indicates this to be the last + * bytes of the response + * @param done on returnm, TRUE iff the response is complete + */ +CURLcode Curl_xfer_write_resp(struct Curl_easy *data, + char *buf, size_t blen, + bool is_eos, bool *done); + /* This sets up a forthcoming transfer */ void Curl_setup_transfer (struct Curl_easy *data, diff --git a/Utilities/cmcurl/lib/url.c b/Utilities/cmcurl/lib/url.c index b81785f..36395a1 100644 --- a/Utilities/cmcurl/lib/url.c +++ b/Utilities/cmcurl/lib/url.c @@ -298,6 +298,10 @@ CURLcode Curl_close(struct Curl_easy **datap) 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); + data_priority_cleanup(data); /* No longer a dirty share, if it exists */ @@ -430,11 +434,13 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) /* Set the default CA cert bundle/path detected/specified at build time. * - * If Schannel is the selected SSL backend then these locations are - * ignored. We allow setting CA location for schannel only when explicitly - * specified by the user via CURLOPT_CAINFO / --cacert. + * If Schannel or SecureTransport is the selected SSL backend then these + * locations are ignored. We allow setting CA location for schannel and + * securetransport when explicitly specified by the user via + * CURLOPT_CAINFO / --cacert. */ - if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL && + Curl_ssl_backend() != CURLSSLBACKEND_SECURETRANSPORT) { #if defined(CURL_CA_BUNDLE) result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); if(result) @@ -514,6 +520,13 @@ CURLcode Curl_open(struct Curl_easy **curl) data->magic = CURLEASY_MAGIC_NUMBER; + result = Curl_resolver_init(data, &data->state.async.resolver); + if(result) { + DEBUGF(fprintf(stderr, "Error: resolver_init failed\n")); + free(data); + return result; + } + result = Curl_init_userdefined(data); if(!result) { Curl_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); @@ -530,6 +543,7 @@ CURLcode Curl_open(struct Curl_easy **curl) } if(result) { + Curl_resolver_cleanup(data->state.async.resolver); Curl_dyn_free(&data->state.headerb); Curl_freeset(data); free(data); @@ -563,7 +577,6 @@ static void conn_free(struct Curl_easy *data, struct connectdata *conn) Curl_conn_cf_discard_all(data, conn, (int)i); } - Curl_resolver_cleanup(conn->resolve_async.resolver); Curl_free_idnconverted_hostname(&conn->host); Curl_free_idnconverted_hostname(&conn->conn_to_host); #ifndef CURL_DISABLE_PROXY @@ -581,9 +594,6 @@ static void conn_free(struct Curl_easy *data, struct connectdata *conn) Curl_safefree(conn->sasl_authzid); Curl_safefree(conn->options); Curl_safefree(conn->oauth_bearer); -#ifndef CURL_DISABLE_HTTP - Curl_dyn_free(&conn->trailer); -#endif Curl_safefree(conn->host.rawalloc); /* host name buffer */ Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */ Curl_safefree(conn->hostname_resolve); @@ -663,7 +673,6 @@ void Curl_disconnect(struct Curl_easy *data, conn->handler->disconnect(data, conn, dead_connection); conn_shutdown(data); - Curl_resolver_cancel(data); /* detach it again */ Curl_detach_connection(data); @@ -1346,6 +1355,8 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ + conn->sockfd = CURL_SOCKET_BAD; + conn->writesockfd = CURL_SOCKET_BAD; conn->connection_id = -1; /* no ID */ conn->port = -1; /* unknown at this point */ conn->remote_port = -1; /* unknown at this point */ @@ -1680,8 +1691,9 @@ static CURLcode findprotocol(struct Curl_easy *data, /* The protocol was not found in the table, but we don't have to assign it to anything since it is already assigned to a dummy-struct in the create_conn() function when the connectdata struct is allocated. */ - failf(data, "Protocol \"%s\" not supported or disabled in " LIBCURL_NAME, - protostr); + failf(data, "Protocol \"%s\" %s%s", protostr, + p ? "disabled" : "not supported", + data->state.this_is_a_follow ? " (in redirect)":""); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -3739,35 +3751,7 @@ static CURLcode create_conn(struct Curl_easy *data, goto out; } - result = Curl_resolver_init(data, &conn->resolve_async.resolver); - if(result) { - DEBUGF(fprintf(stderr, "Error: resolver_init failed\n")); - goto out; - } - Curl_attach_connection(data, conn); - -#ifdef USE_ARES - result = Curl_set_dns_servers(data, data->set.str[STRING_DNS_SERVERS]); - if(result && result != CURLE_NOT_BUILT_IN) - goto out; - - result = Curl_set_dns_interface(data, - data->set.str[STRING_DNS_INTERFACE]); - if(result && result != CURLE_NOT_BUILT_IN) - goto out; - - result = Curl_set_dns_local_ip4(data, - data->set.str[STRING_DNS_LOCAL_IP4]); - if(result && result != CURLE_NOT_BUILT_IN) - goto out; - - result = Curl_set_dns_local_ip6(data, - data->set.str[STRING_DNS_LOCAL_IP6]); - if(result && result != CURLE_NOT_BUILT_IN) - goto out; -#endif /* USE_ARES */ - result = Curl_conncache_add_conn(data); if(result) goto out; diff --git a/Utilities/cmcurl/lib/urlapi.c b/Utilities/cmcurl/lib/urlapi.c index 0d11e48..3cd0362 100644 --- a/Utilities/cmcurl/lib/urlapi.c +++ b/Utilities/cmcurl/lib/urlapi.c @@ -126,6 +126,9 @@ static const char *find_host_sep(const char *url) return sep < query ? sep : query; } +/* convert CURLcode to CURLUcode */ +#define cc2cu(x) ((x) == CURLE_TOO_LARGE ? CURLUE_TOO_LARGE : \ + CURLUE_OUT_OF_MEMORY) /* * Decide whether a character in a URL must be escaped. */ @@ -146,6 +149,7 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, bool left = !query; const unsigned char *iptr; const unsigned char *host_sep = (const unsigned char *) url; + CURLcode result; if(!relative) host_sep = (const unsigned char *) find_host_sep(url); @@ -154,20 +158,19 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, len; iptr++, len--) { if(iptr < host_sep) { - if(Curl_dyn_addn(o, iptr, 1)) - return CURLUE_OUT_OF_MEMORY; + result = Curl_dyn_addn(o, iptr, 1); + if(result) + return cc2cu(result); continue; } if(*iptr == ' ') { - if(left) { - if(Curl_dyn_addn(o, "%20", 3)) - return CURLUE_OUT_OF_MEMORY; - } - else { - if(Curl_dyn_addn(o, "+", 1)) - return CURLUE_OUT_OF_MEMORY; - } + if(left) + result = Curl_dyn_addn(o, "%20", 3); + else + result = Curl_dyn_addn(o, "+", 1); + if(result) + return cc2cu(result); continue; } @@ -178,13 +181,12 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, char out[3]={'%'}; out[1] = hexdigits[*iptr>>4]; out[2] = hexdigits[*iptr & 0xf]; - if(Curl_dyn_addn(o, out, 3)) - return CURLUE_OUT_OF_MEMORY; - } - else { - if(Curl_dyn_addn(o, iptr, 1)) - return CURLUE_OUT_OF_MEMORY; + result = Curl_dyn_addn(o, out, 3); } + else + result = Curl_dyn_addn(o, iptr, 1); + if(result) + return cc2cu(result); } return CURLUE_OK; @@ -248,7 +250,7 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, * * Note that this function destroys the 'base' string. */ -static char *concat_url(char *base, const char *relurl) +static CURLcode concat_url(char *base, const char *relurl, char **newurl) { /*** TRY to append this new path to the old URL @@ -260,6 +262,9 @@ static char *concat_url(char *base, const char *relurl) char *pathsep; bool host_changed = FALSE; const char *useurl = relurl; + CURLcode result = CURLE_OK; + CURLUcode uc; + *newurl = NULL; /* protsep points to the start of the host name */ protsep = strstr(base, "//"); @@ -360,21 +365,27 @@ static char *concat_url(char *base, const char *relurl) Curl_dyn_init(&newest, CURL_MAX_INPUT_LENGTH); /* copy over the root url part */ - if(Curl_dyn_add(&newest, base)) - return NULL; + result = Curl_dyn_add(&newest, base); + if(result) + return result; /* check if we need to append a slash */ if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0])) ; else { - if(Curl_dyn_addn(&newest, "/", 1)) - return NULL; + result = Curl_dyn_addn(&newest, "/", 1); + if(result) + return result; } /* then append the new piece on the right side */ - urlencode_str(&newest, useurl, strlen(useurl), !host_changed, FALSE); + uc = urlencode_str(&newest, useurl, strlen(useurl), !host_changed, + FALSE); + if(uc) + return (uc == CURLUE_TOO_LARGE) ? CURLE_TOO_LARGE : CURLE_OUT_OF_MEMORY; - return Curl_dyn_ptr(&newest); + *newurl = Curl_dyn_ptr(&newest); + return CURLE_OK; } /* scan for byte values <= 31, 127 and sometimes space */ @@ -712,24 +723,30 @@ static int ipv4_normalize(struct dynbuf *host) Curl_dyn_reset(host); result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0] >> 24, (parts[0] >> 16) & 0xff, - (parts[0] >> 8) & 0xff, parts[0] & 0xff); + (unsigned int)(parts[0] >> 24), + (unsigned int)((parts[0] >> 16) & 0xff), + (unsigned int)((parts[0] >> 8) & 0xff), + (unsigned int)(parts[0] & 0xff)); break; case 1: /* a.b -- 8.24 bits */ if((parts[0] > 0xff) || (parts[1] > 0xffffff)) return HOST_NAME; Curl_dyn_reset(host); result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0], (parts[1] >> 16) & 0xff, - (parts[1] >> 8) & 0xff, parts[1] & 0xff); + (unsigned int)(parts[0]), + (unsigned int)((parts[1] >> 16) & 0xff), + (unsigned int)((parts[1] >> 8) & 0xff), + (unsigned int)(parts[1] & 0xff)); break; case 2: /* a.b.c -- 8.8.16 bits */ if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff)) return HOST_NAME; Curl_dyn_reset(host); result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0], parts[1], (parts[2] >> 8) & 0xff, - parts[2] & 0xff); + (unsigned int)(parts[0]), + (unsigned int)(parts[1]), + (unsigned int)((parts[2] >> 8) & 0xff), + (unsigned int)(parts[2] & 0xff)); break; case 3: /* a.b.c.d -- 8.8.8.8 bits */ if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) || @@ -737,7 +754,10 @@ static int ipv4_normalize(struct dynbuf *host) return HOST_NAME; Curl_dyn_reset(host); result = Curl_dyn_addf(host, "%u.%u.%u.%u", - parts[0], parts[1], parts[2], parts[3]); + (unsigned int)(parts[0]), + (unsigned int)(parts[1]), + (unsigned int)(parts[2]), + (unsigned int)(parts[3])); break; } if(result) @@ -766,7 +786,7 @@ static CURLUcode urldecode_host(struct dynbuf *host) result = Curl_dyn_addn(host, decoded, dlen); free(decoded); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } return CURLUE_OK; @@ -779,22 +799,24 @@ static CURLUcode parse_authority(struct Curl_URL *u, bool has_scheme) { size_t offset; - CURLUcode result; + CURLUcode uc; + CURLcode result; /* * Parse the login details and strip them out of the host name. */ - result = parse_hostname_login(u, auth, authlen, flags, &offset); - if(result) + uc = parse_hostname_login(u, auth, authlen, flags, &offset); + if(uc) goto out; - if(Curl_dyn_addn(host, auth + offset, authlen - offset)) { - result = CURLUE_OUT_OF_MEMORY; + result = Curl_dyn_addn(host, auth + offset, authlen - offset); + if(result) { + uc = cc2cu(result); goto out; } - result = Curl_parse_port(u, host, has_scheme); - if(result) + uc = Curl_parse_port(u, host, has_scheme); + if(uc) goto out; if(!Curl_dyn_len(host)) @@ -804,24 +826,24 @@ static CURLUcode parse_authority(struct Curl_URL *u, case HOST_IPV4: break; case HOST_IPV6: - result = ipv6_parse(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + uc = ipv6_parse(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); break; case HOST_NAME: - result = urldecode_host(host); - if(!result) - result = hostname_check(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + uc = urldecode_host(host); + if(!uc) + uc = hostname_check(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); break; case HOST_ERROR: - result = CURLUE_OUT_OF_MEMORY; + uc = CURLUE_OUT_OF_MEMORY; break; case HOST_BAD: default: - result = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ + uc = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ break; } out: - return result; + return uc; } CURLUcode Curl_url_set_authority(CURLU *u, const char *authority, @@ -1070,8 +1092,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) len = path - ptr; if(len) { - if(Curl_dyn_addn(&host, ptr, len)) { - result = CURLUE_OUT_OF_MEMORY; + CURLcode code = Curl_dyn_addn(&host, ptr, len); + if(code) { + result = cc2cu(code); goto fail; } uncpath = TRUE; @@ -1224,14 +1247,13 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(flags & CURLU_URLENCODE) { struct dynbuf enc; Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, FALSE)) { - result = CURLUE_OUT_OF_MEMORY; + result = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, FALSE); + if(result) goto fail; - } u->fragment = Curl_dyn_ptr(&enc); } else { - u->fragment = Curl_strndup(fragment + 1, fraglen - 1); + u->fragment = Curl_memdup0(fragment + 1, fraglen - 1); if(!u->fragment) { result = CURLUE_OUT_OF_MEMORY; goto fail; @@ -1242,7 +1264,6 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) pathlen -= fraglen; } - DEBUGASSERT(pathlen < urllen); query = memchr(path, '?', pathlen); if(query) { size_t qlen = fragment ? (size_t)(fragment - query) : @@ -1253,14 +1274,13 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) struct dynbuf enc; Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); /* skip the leading question mark */ - if(urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE)) { - result = CURLUE_OUT_OF_MEMORY; + result = urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE); + if(result) goto fail; - } u->query = Curl_dyn_ptr(&enc); } else { - u->query = Curl_strndup(query + 1, qlen - 1); + u->query = Curl_memdup0(query + 1, qlen - 1); if(!u->query) { result = CURLUE_OUT_OF_MEMORY; goto fail; @@ -1280,10 +1300,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(pathlen && (flags & CURLU_URLENCODE)) { struct dynbuf enc; Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(urlencode_str(&enc, path, pathlen, TRUE, FALSE)) { - result = CURLUE_OUT_OF_MEMORY; + result = urlencode_str(&enc, path, pathlen, TRUE, FALSE); + if(result) goto fail; - } pathlen = Curl_dyn_len(&enc); path = u->path = Curl_dyn_ptr(&enc); } @@ -1294,7 +1313,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) } else { if(!u->path) { - u->path = Curl_strndup(path, pathlen); + u->path = Curl_memdup0(path, pathlen); if(!u->path) { result = CURLUE_OUT_OF_MEMORY; goto fail; @@ -1592,7 +1611,7 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, if(ptr) { size_t partlen = strlen(ptr); size_t i = 0; - *part = Curl_strndup(ptr, partlen); + *part = Curl_memdup0(ptr, partlen); if(!*part) return CURLUE_OUT_OF_MEMORY; if(plusdecode) { @@ -1619,10 +1638,11 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what, } if(urlencode) { struct dynbuf enc; + CURLUcode uc; Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); - if(urlencode_str(&enc, *part, partlen, TRUE, - what == CURLUPART_QUERY)) - return CURLUE_OUT_OF_MEMORY; + uc = urlencode_str(&enc, *part, partlen, TRUE, what == CURLUPART_QUERY); + if(uc) + return uc; free(*part); *part = Curl_dyn_ptr(&enc); } @@ -1807,7 +1827,8 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, * If the existing contents is enough for a URL, allow a relative URL to * replace it. */ - CURLUcode result; + CURLcode result; + CURLUcode uc; char *oldurl; char *redired_url; @@ -1827,14 +1848,14 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, /* apply the relative part to create a new URL * and replace the existing one with it. */ - redired_url = concat_url(oldurl, part); + result = concat_url(oldurl, part, &redired_url); free(oldurl); - if(!redired_url) - return CURLUE_OUT_OF_MEMORY; + if(result) + return cc2cu(result); - result = parseurl_and_replace(redired_url, u, flags); + uc = parseurl_and_replace(redired_url, u, flags); free(redired_url); - return result; + return uc; } default: return CURLUE_UNKNOWN_PART; @@ -1848,7 +1869,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, if(leadingslash && (part[0] != '/')) { CURLcode result = Curl_dyn_addn(&enc, "/", 1); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } if(urlencode) { const unsigned char *i; @@ -1868,7 +1889,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, equalsencode = FALSE; result = Curl_dyn_addn(&enc, i, 1); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } else { char out[3]={'%'}; @@ -1876,7 +1897,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, out[2] = hexdigits[*i & 0xf]; result = Curl_dyn_addn(&enc, out, 3); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); } } } @@ -1884,7 +1905,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, char *p; CURLcode result = Curl_dyn_add(&enc, part); if(result) - return CURLUE_OUT_OF_MEMORY; + return cc2cu(result); p = Curl_dyn_ptr(&enc); while(*p) { /* make sure percent encoded are lower case */ diff --git a/Utilities/cmcurl/lib/urldata.h b/Utilities/cmcurl/lib/urldata.h index ff66148..9dcccc7 100644 --- a/Utilities/cmcurl/lib/urldata.h +++ b/Utilities/cmcurl/lib/urldata.h @@ -583,7 +583,7 @@ struct hostname { (((data)->req.keepon & KEEP_SENDBITS) == KEEP_SEND) /* transfer receive is not on PAUSE or HOLD */ #define CURL_WANT_RECV(data) \ - (!((data)->req.keepon & (KEEP_RECV_PAUSE|KEEP_RECV_HOLD))) + (((data)->req.keepon & KEEP_RECVBITS) == KEEP_RECV) #if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) #define USE_CURL_ASYNC @@ -683,7 +683,8 @@ struct SingleRequest { enum expect100 exp100; /* expect 100 continue state */ enum upgrade101 upgr101; /* 101 upgrade state */ - /* Content unencoding stack. See sec 3.5, RFC2616. */ + /* Client Writer stack, handles trasnfer- and content-encodings, protocol + * checks, pausing by client callbacks. */ struct Curl_cwriter *writer_stack; time_t timeofdoc; long bodywrites; @@ -730,11 +731,10 @@ struct SingleRequest { #ifndef CURL_DISABLE_COOKIES unsigned char setcookies; #endif - unsigned char writer_stack_depth; /* Unencoding stack depth. */ BIT(header); /* incoming data has HTTP header */ - BIT(badheader); /* header parsing found sth not a header */ BIT(content_range); /* set TRUE if Content-Range: was found */ BIT(download_done); /* set to TRUE when download is complete */ + BIT(eos_written); /* iff EOS has been written to client */ BIT(upload_done); /* set to TRUE when doing chunked transfer-encoding upload and we're uploading the last chunk */ BIT(ignorebody); /* we read a response-body but we ignore it! */ @@ -816,10 +816,10 @@ struct Curl_handler { bool dead_connection); /* If used, this function gets called from transfer.c:readwrite_data() to - allow the protocol to do extra reads/writes */ - CURLcode (*readwrite)(struct Curl_easy *data, struct connectdata *conn, - const char *buf, size_t blen, - size_t *pconsumed, bool *readmore); + allow the protocol to do extra handling in writing response to + the client. */ + CURLcode (*write_resp)(struct Curl_easy *data, const char *buf, size_t blen, + bool is_eos, bool *done); /* This function can perform various checks on the connection. See CONNCHECK_* for more information about the checks that can be performed, @@ -898,11 +898,6 @@ struct ldapconninfo; struct connectdata { struct Curl_llist_element bundle_node; /* conncache */ - /* chunk is for HTTP chunked encoding, but is in the general connectdata - struct only because we can do just about any protocol through an HTTP - proxy and an HTTP proxy may in fact respond using chunked encoding */ - struct Curl_chunker chunk; - curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ void *closesocket_client; @@ -921,9 +916,6 @@ struct connectdata { 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; -#ifdef USE_CURL_ASYNC - struct Curl_async resolve_async; /* asynchronous name resolver data */ -#endif /* 'remote_addr' is the particular IP we connected to. it is owned, set * and NULLed by the connected socket filter (if there is one). */ @@ -1028,11 +1020,6 @@ struct connectdata { struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */ #endif -#ifndef CURL_DISABLE_HTTP - /* for chunked-encoded trailer */ - struct dynbuf trailer; -#endif - union { #ifndef CURL_DISABLE_FTP struct ftp_conn ftpc; @@ -1103,7 +1090,6 @@ struct connectdata { unsigned short localport; unsigned short secondary_port; /* secondary socket remote port to connect to (ftp) */ - unsigned char cselect_bits; /* bitmask of socket events */ unsigned char alpn; /* APLN TLS negotiated protocol, a CURL_HTTP_VERSION* value */ #ifndef CURL_DISABLE_PROXY @@ -1193,6 +1179,7 @@ struct Progress { curl_off_t dlspeed; curl_off_t ulspeed; + timediff_t t_postqueue; timediff_t t_nslookup; timediff_t t_connect; timediff_t t_appconnect; @@ -1382,6 +1369,9 @@ struct UrlState { #endif struct auth authhost; /* auth details for host */ struct auth authproxy; /* auth details for proxy */ +#ifdef USE_CURL_ASYNC + struct Curl_async async; /* asynchronous name resolver data */ +#endif #if defined(USE_OPENSSL) /* void instead of ENGINE to avoid bleeding OpenSSL into this header */ @@ -1479,7 +1469,7 @@ struct UrlState { server involved in this request */ unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any) is this */ - unsigned char dselect_bits; /* != 0 -> bitmask of socket events for this + unsigned char select_bits; /* != 0 -> bitmask of socket events for this transfer overriding anything the socket may report */ #ifdef CURLDEBUG diff --git a/Utilities/cmcurl/lib/vauth/digest_sspi.c b/Utilities/cmcurl/lib/vauth/digest_sspi.c index 02e36ea..4696f29 100644 --- a/Utilities/cmcurl/lib/vauth/digest_sspi.c +++ b/Utilities/cmcurl/lib/vauth/digest_sspi.c @@ -211,8 +211,10 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) infof(data, "schannel: InitializeSecurityContext failed: %s", Curl_sspi_strerror(status, buffer, sizeof(buffer))); +#endif return CURLE_AUTH_ERROR; } @@ -603,8 +605,10 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) infof(data, "schannel: InitializeSecurityContext failed: %s", Curl_sspi_strerror(status, buffer, sizeof(buffer))); +#endif return CURLE_AUTH_ERROR; } diff --git a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c index 65eb3e1..16b6e40 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c @@ -226,7 +226,8 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Extract the security layer and the maximum message size */ indata = output_token.value; sec_layer = indata[0]; - max_size = (indata[1] << 16) | (indata[2] << 8) | indata[3]; + max_size = ((unsigned int)indata[1] << 16) | + ((unsigned int)indata[2] << 8) | indata[3]; /* Free the challenge as it is not required anymore */ gss_release_buffer(&unused_status, &output_token); diff --git a/Utilities/cmcurl/lib/vauth/krb5_sspi.c b/Utilities/cmcurl/lib/vauth/krb5_sspi.c index c487149..17a517a 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_sspi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_sspi.c @@ -319,7 +319,8 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, /* Extract the security layer and the maximum message size */ indata = input_buf[1].pvBuffer; sec_layer = indata[0]; - max_size = (indata[1] << 16) | (indata[2] << 8) | indata[3]; + max_size = ((unsigned long)indata[1] << 16) | + ((unsigned long)indata[2] << 8) | indata[3]; /* Free the challenge as it is not required anymore */ s_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer); diff --git a/Utilities/cmcurl/lib/vauth/ntlm.c b/Utilities/cmcurl/lib/vauth/ntlm.c index ed7cee8..018e6a6 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm.c +++ b/Utilities/cmcurl/lib/vauth/ntlm.c @@ -44,6 +44,7 @@ #include "warnless.h" #include "rand.h" #include "vtls/vtls.h" +#include "strdup.h" #define BUILDING_CURL_NTLM_MSGS_C #include "vauth/vauth.h" @@ -184,11 +185,10 @@ static CURLcode ntlm_decode_type2_target(struct Curl_easy *data, } free(ntlm->target_info); /* replace any previous data */ - ntlm->target_info = malloc(target_info_len); + ntlm->target_info = Curl_memdup(&type2[target_info_offset], + target_info_len); if(!ntlm->target_info) return CURLE_OUT_OF_MEMORY; - - memcpy(ntlm->target_info, &type2[target_info_offset], target_info_len); } } diff --git a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c index 5118963..9205431 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c +++ b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c @@ -34,6 +34,7 @@ #include "warnless.h" #include "curl_multibyte.h" #include "sendf.h" +#include "strdup.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -213,11 +214,10 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, } /* Store the challenge for later use */ - ntlm->input_token = malloc(Curl_bufref_len(type2) + 1); + ntlm->input_token = Curl_memdup0((const char *)Curl_bufref_ptr(type2), + Curl_bufref_len(type2)); if(!ntlm->input_token) return CURLE_OUT_OF_MEMORY; - memcpy(ntlm->input_token, Curl_bufref_ptr(type2), Curl_bufref_len(type2)); - ntlm->input_token[Curl_bufref_len(type2)] = '\0'; ntlm->input_token_len = Curl_bufref_len(type2); return CURLE_OK; @@ -314,7 +314,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, &type_3_desc, &attrs, &expiry); if(status != SEC_E_OK) { - infof(data, "NTLM handshake failure (type-3 message): Status=%x", + infof(data, "NTLM handshake failure (type-3 message): Status=%lx", status); if(status == SEC_E_INSUFFICIENT_MEMORY) diff --git a/Utilities/cmcurl/lib/version.c b/Utilities/cmcurl/lib/version.c index 31bd0a4..34d4d7c 100644 --- a/Utilities/cmcurl/lib/version.c +++ b/Utilities/cmcurl/lib/version.c @@ -211,8 +211,12 @@ char *curl_version(void) #endif #ifdef USE_LIBPSL - msnprintf(psl_version, sizeof(psl_version), "libpsl/%s", psl_get_version()); - src[i++] = psl_version; + { + int num = psl_check_version_number(0); + msnprintf(psl_version, sizeof(psl_version), "libpsl/%d.%d.%d", + num >> 16, (num >> 8) & 0xff, num & 0xff); + src[i++] = psl_version; + } #endif #ifdef USE_SSH diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.c b/Utilities/cmcurl/lib/vquic/curl_msh3.c index 8ae3672..7674bc1 100644 --- a/Utilities/cmcurl/lib/vquic/curl_msh3.c +++ b/Utilities/cmcurl/lib/vquic/curl_msh3.c @@ -204,8 +204,8 @@ static void drain_stream_from_other_thread(struct Curl_easy *data, bits = CURL_CSELECT_IN; if(stream && !stream->upload_done) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; /* cannot expire from other thread */ } } @@ -220,8 +220,8 @@ static void drain_stream(struct Curl_cfilter *cf, bits = CURL_CSELECT_IN; if(stream && !stream->upload_done) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c index f09b10b..a26b3e4 100644 --- a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c +++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c @@ -41,7 +41,6 @@ #include "vtls/gtls.h" #elif defined(USE_WOLFSSL) #include <ngtcp2/ngtcp2_crypto_wolfssl.h> -#include "vtls/wolfssl.h" #endif #include "urldata.h" @@ -61,6 +60,7 @@ #include "inet_pton.h" #include "vquic.h" #include "vquic_int.h" +#include "vquic-tls.h" #include "vtls/keylog.h" #include "vtls/vtls.h" #include "curl_ngtcp2.h" @@ -73,9 +73,6 @@ #include "memdebug.h" -#define H3_ALPN_H3_29 "\x5h3-29" -#define H3_ALPN_H3 "\x2h3" - #define QUIC_MAX_STREAMS (256*1024) #define QUIC_MAX_DATA (1*1024*1024) #define QUIC_HANDSHAKE_TIMEOUT (10*NGTCP2_SECONDS) @@ -101,25 +98,6 @@ (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) -#ifdef USE_OPENSSL -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:X25519:P-384:P-521" -#elif defined(USE_GNUTLS) -#define QUIC_PRIORITY \ - "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ - "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ - "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ - "%DISABLE_TLS13_COMPAT_MODE" -#elif defined(USE_WOLFSSL) -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:P-384:P-521" -#endif - - /* * Store ngtcp2 version info in this buffer. */ @@ -134,6 +112,7 @@ void Curl_ngtcp2_ver(char *p, size_t len) struct cf_ngtcp2_ctx { struct cf_quic_ctx q; struct ssl_peer peer; + struct quic_tls_ctx tls; ngtcp2_path connected_path; ngtcp2_conn *qconn; ngtcp2_cid dcid; @@ -143,30 +122,16 @@ struct cf_ngtcp2_ctx { ngtcp2_transport_params transport_params; ngtcp2_ccerr last_error; ngtcp2_crypto_conn_ref conn_ref; -#ifdef USE_OPENSSL - SSL_CTX *sslctx; - SSL *ssl; -#elif defined(USE_GNUTLS) - struct gtls_instance *gtls; -#elif defined(USE_WOLFSSL) - WOLFSSL_CTX *sslctx; - WOLFSSL *ssl; -#endif struct cf_call_data call_data; nghttp3_conn *h3conn; nghttp3_settings h3settings; struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ - struct curltime first_byte_at; /* when first byte was recvd */ struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ size_t max_stream_window; /* max flow window for one stream */ uint64_t max_idle_ms; /* max idle time for QUIC connection */ int qlogfd; - BIT(got_first_byte); /* if first byte was received */ -#ifdef USE_OPENSSL - BIT(x509_store_setup); /* if x509 store has been set up */ -#endif }; /* How to access `call_data` from a cf_ngtcp2 filter */ @@ -292,8 +257,8 @@ static void h3_drain_stream(struct Curl_cfilter *cf, bits = CURL_CSELECT_IN; if(stream && stream->upload_left && !stream->send_closed) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } @@ -413,388 +378,8 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx, } } -#ifdef USE_OPENSSL -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#elif defined(USE_GNUTLS) -static int keylog_callback(gnutls_session_t session, const char *label, - const gnutls_datum_t *secret) -{ - gnutls_datum_t crandom; - gnutls_datum_t srandom; - - gnutls_session_get_random(session, &crandom, &srandom); - if(crandom.size != 32) { - return -1; - } - - Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); - return 0; -} -#elif defined(USE_WOLFSSL) -#if defined(HAVE_SECRET_CALLBACK) -static void keylog_callback(const WOLFSSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#endif -#endif - static int init_ngh3_conn(struct Curl_cfilter *cf); -#ifdef USE_OPENSSL -static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx, - struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct ssl_primary_config *conn_config; - CURLcode result = CURLE_FAILED_INIT; - - SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); - if(!ssl_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - conn_config = Curl_ssl_cf_get_primary_config(cf); - if(!conn_config) { - result = CURLE_FAILED_INIT; - goto out; - } - -#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) - if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); - goto out; - } -#else - if(ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); - goto out; - } -#endif - - SSL_CTX_set_default_verify_paths(ssl_ctx); - - { - const char *curves = conn_config->curves ? - conn_config->curves : QUIC_GROUPS; - if(!SSL_CTX_set1_curves_list(ssl_ctx, curves)) { - failf(data, "failed setting curves list for QUIC: '%s'", curves); - return CURLE_SSL_CIPHER; - } - } - -#ifndef OPENSSL_IS_BORINGSSL - { - const char *ciphers13 = conn_config->cipher_list13 ? - conn_config->cipher_list13 : QUIC_CIPHERS; - if(SSL_CTX_set_ciphersuites(ssl_ctx, ciphers13) != 1) { - failf(data, "failed setting QUIC cipher suite: %s", ciphers13); - return CURLE_SSL_CIPHER; - } - infof(data, "QUIC cipher selection: %s", ciphers13); - } -#endif - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); - } - - /* OpenSSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(ssl_ctx, conn_config->verifypeer ? - SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - /* When a user callback is installed to modify the SSL_CTX, - * we need to do the full initialization before calling it. - * See: #11800 */ - if(!ctx->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, ssl_ctx); - if(result) - goto out; - ctx->x509_store_setup = TRUE; - } - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, ssl_ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - goto out; - } - } - result = CURLE_OK; - -out: - *pssl_ctx = result? NULL : ssl_ctx; - if(result && ssl_ctx) - SSL_CTX_free(ssl_ctx); - return result; -} - -static CURLcode quic_set_client_cert(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - SSL_CTX *ssl_ctx = ctx->sslctx; - const struct ssl_config_data *ssl_config; - - ssl_config = Curl_ssl_cf_get_config(cf, data); - DEBUGASSERT(ssl_config); - - if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob - || ssl_config->cert_type) { - return Curl_ossl_set_client_cert( - data, ssl_ctx, ssl_config->primary.clientcert, - ssl_config->primary.cert_blob, ssl_config->cert_type, - ssl_config->key, ssl_config->key_blob, - ssl_config->key_type, ssl_config->key_passwd); - } - - return CURLE_OK; -} - -/** SSL callbacks ***/ - -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - - DEBUGASSERT(!ctx->ssl); - ctx->ssl = SSL_new(ctx->sslctx); - - SSL_set_app_data(ctx->ssl, &ctx->conn_ref); - SSL_set_connect_state(ctx->ssl); - SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - SSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); - - /* set SNI */ - if(ctx->peer.sni) { - if(!SSL_set_tlsext_host_name(ctx->ssl, ctx->peer.sni)) { - failf(data, "Failed set SNI"); - SSL_free(ctx->ssl); - ctx->ssl = NULL; - return CURLE_QUIC_CONNECT_ERROR; - } - } - return CURLE_OK; -} -#elif defined(USE_GNUTLS) -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct ssl_primary_config *conn_config; - CURLcode result; - gnutls_datum_t alpn[2]; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - long * const pverifyresult = &data->set.ssl.certverifyresult; - int rc; - - conn_config = Curl_ssl_cf_get_primary_config(cf); - if(!conn_config) - return CURLE_FAILED_INIT; - - DEBUGASSERT(ctx->gtls == NULL); - ctx->gtls = calloc(1, sizeof(*(ctx->gtls))); - if(!ctx->gtls) - return CURLE_OUT_OF_MEMORY; - - result = gtls_client_init(data, conn_config, &data->set.ssl, - &ctx->peer, ctx->gtls, pverifyresult); - if(result) - return result; - - gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref); - - if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) { - CURL_TRC_CF(data, cf, - "ngtcp2_crypto_gnutls_configure_client_session failed\n"); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL); - if(rc < 0) { - CURL_TRC_CF(data, cf, "gnutls_priority_set_direct failed: %s\n", - gnutls_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback); - } - - /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */ - alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1; - alpn[0].size = sizeof(H3_ALPN_H3_29) - 2; - alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1; - alpn[1].size = sizeof(H3_ALPN_H3) - 2; - - gnutls_alpn_set_protocols(ctx->gtls->session, - alpn, 2, GNUTLS_ALPN_MANDATORY); - return CURLE_OK; -} -#elif defined(USE_WOLFSSL) - -static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx, - struct Curl_cfilter *cf, struct Curl_easy *data) -{ - CURLcode result = CURLE_FAILED_INIT; - struct ssl_primary_config *conn_config; - WOLFSSL_CTX *ssl_ctx = NULL; - - conn_config = Curl_ssl_cf_get_primary_config(cf); - if(!conn_config) { - result = CURLE_FAILED_INIT; - goto out; - } - - ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); - if(!ssl_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); - result = CURLE_FAILED_INIT; - goto out; - } - - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - - if(wolfSSL_CTX_set_cipher_list(ssl_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); - goto out; - } - - if(wolfSSL_CTX_set1_groups_list(ssl_ctx, conn_config->curves ? - conn_config->curves : - (char *)QUIC_GROUPS) != 1) { - failf(data, "wolfSSL failed to set curves"); - goto out; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { -#if defined(HAVE_SECRET_CALLBACK) - wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); -#else - failf(data, "wolfSSL was built without keylog callback"); - goto out; -#endif - } - - if(conn_config->verifypeer) { - const char * const ssl_cafile = conn_config->CAfile; - const char * const ssl_capath = conn_config->CApath; - - wolfSSL_CTX_set_verify(ssl_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(ssl_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"); - 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 won't work so - use wolfssl's built-in default as fallback */ - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - } -#endif - } - else { - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); - } - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, ssl_ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - goto out; - } - } - result = CURLE_OK; - -out: - *pssl_ctx = result? NULL : ssl_ctx; - if(result && ssl_ctx) - SSL_CTX_free(ssl_ctx); - return result; -} - -/** SSL callbacks ***/ - -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = cf->conn->host.name; - - (void)data; - DEBUGASSERT(!ctx->ssl); - ctx->ssl = wolfSSL_new(ctx->sslctx); - - wolfSSL_set_app_data(ctx->ssl, &ctx->conn_ref); - wolfSSL_set_connect_state(ctx->ssl); - wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - wolfSSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); - - /* set SNI */ - wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME, - hostname, (unsigned short)strlen(hostname)); - - return CURLE_OK; -} -#endif /* defined(USE_WOLFSSL) */ - static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) { (void)user_data; @@ -1157,18 +742,22 @@ static void cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - bool want_recv = CURL_WANT_RECV(data); - bool want_send = CURL_WANT_SEND(data); + bool want_recv, want_send; - if(ctx->qconn && (want_recv || want_send)) { + if(!ctx->qconn) + return; + + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { struct h3_stream_ctx *stream = H3_STREAM_CTX(data); struct cf_call_data save; bool c_exhaust, s_exhaust; CF_DATA_SAVE(save, cf, data); - c_exhaust = !ngtcp2_conn_get_cwnd_left(ctx->qconn) || - !ngtcp2_conn_get_max_data_left(ctx->qconn); - s_exhaust = stream && stream->id >= 0 && stream->quic_flow_blocked; + c_exhaust = want_send && (!ngtcp2_conn_get_cwnd_left(ctx->qconn) || + !ngtcp2_conn_get_max_data_left(ctx->qconn)); + s_exhaust = want_send && stream && stream->id >= 0 && + stream->quic_flow_blocked; want_recv = (want_recv || c_exhaust || s_exhaust); want_send = (!s_exhaust && want_send) || !Curl_bufq_is_empty(&ctx->q.sendbuf); @@ -1894,6 +1483,8 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, sent = (ssize_t)len; goto out; } + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); *err = CURLE_HTTP3; sent = -1; goto out; @@ -1944,49 +1535,12 @@ static CURLcode qng_verify_peer(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct ssl_primary_config *conn_config; - CURLcode result = CURLE_OK; - - conn_config = Curl_ssl_cf_get_primary_config(cf); - if(!conn_config) - return CURLE_FAILED_INIT; cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ cf->conn->httpversion = 30; cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; - if(conn_config->verifyhost) { -#ifdef USE_OPENSSL - X509 *server_cert; - server_cert = SSL_get1_peer_certificate(ctx->ssl); - if(!server_cert) { - return CURLE_PEER_FAILED_VERIFICATION; - } - result = Curl_ossl_verifyhost(data, cf->conn, &ctx->peer, server_cert); - X509_free(server_cert); - if(result) - return result; -#elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(data, ctx->gtls->session, - conn_config, &data->set.ssl, &ctx->peer, - data->set.str[STRING_SSL_PINNEDPUBLICKEY]); - if(result) - return result; -#elif defined(USE_WOLFSSL) - if(!ctx->peer.sni || - wolfSSL_check_domain_name(ctx->ssl, ctx->peer.sni) == SSL_FAILURE) - return CURLE_PEER_FAILED_VERIFICATION; -#endif - infof(data, "Verified certificate just fine"); - } - else - infof(data, "Skipped certificate verification"); -#ifdef USE_OPENSSL - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, ctx->ssl); -#endif - return result; + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); } static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, @@ -2050,14 +1604,9 @@ static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, pktx_update_time(pktx, cf); } -#ifdef USE_OPENSSL - if(!ctx->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, ctx->sslctx); - if(result) - return result; - ctx->x509_store_setup = TRUE; - } -#endif + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + return result; for(i = 0; i < pkts_max; i += pkts_chunk) { pktx->pkt_count = 0; @@ -2382,25 +1931,7 @@ static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx) if(ctx->qlogfd != -1) { close(ctx->qlogfd); } -#ifdef USE_OPENSSL - if(ctx->ssl) - SSL_free(ctx->ssl); - if(ctx->sslctx) - SSL_CTX_free(ctx->sslctx); -#elif defined(USE_GNUTLS) - if(ctx->gtls) { - if(ctx->gtls->cred) - gnutls_certificate_free_credentials(ctx->gtls->cred); - if(ctx->gtls->session) - gnutls_deinit(ctx->gtls->session); - free(ctx->gtls); - } -#elif defined(USE_WOLFSSL) - if(ctx->ssl) - wolfSSL_free(ctx->ssl); - if(ctx->sslctx) - wolfSSL_CTX_free(ctx->sslctx); -#endif + Curl_vquic_tls_cleanup(&ctx->tls); vquic_ctx_free(&ctx->q); if(ctx->h3conn) nghttp3_conn_del(ctx->h3conn); @@ -2459,6 +1990,37 @@ static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) (void)save; } +static CURLcode tls_ctx_setup(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)cf; +#ifdef USE_OPENSSL +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#else + if(ngtcp2_crypto_quictls_configure_client_context(ctx->ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */ +#elif defined(USE_GNUTLS) + if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) { + failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed"); + return CURLE_FAILED_INIT; + } +#elif defined(USE_WOLFSSL) + if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + /* * Might be called twice for happy eyeballs. */ @@ -2483,21 +2045,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, if(result) return result; -#ifdef USE_OPENSSL - result = quic_ssl_ctx(&ctx->sslctx, cf, data); - if(result) - return result; - - result = quic_set_client_cert(cf, data); - if(result) - return result; -#elif defined(USE_WOLFSSL) - result = quic_ssl_ctx(&ctx->sslctx, cf, data); - if(result) - return result; -#endif - - result = quic_init_ssl(cf, data); +#define H3_ALPN "\x2h3\x5h3-29" + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + H3_ALPN, sizeof(H3_ALPN) - 1, + tls_ctx_setup, &ctx->conn_ref); if(result) return result; @@ -2544,9 +2095,9 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, return CURLE_QUIC_CONNECT_ERROR; #ifdef USE_GNUTLS - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session); + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls->session); #else - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl); + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ssl); #endif ngtcp2_ccerr_default(&ctx->last_error); @@ -2677,8 +2228,8 @@ static CURLcode cf_ngtcp2_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); + if(ctx->q.got_first_byte) { + timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at); *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; } else @@ -2686,8 +2237,8 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, return CURLE_OK; case CF_QUERY_TIMER_CONNECT: { struct curltime *when = pres2; - if(ctx->got_first_byte) - *when = ctx->first_byte_at; + if(ctx->q.got_first_byte) + *when = ctx->q.first_byte_at; return CURLE_OK; } case CF_QUERY_TIMER_APPCONNECT: { diff --git a/Utilities/cmcurl/lib/vquic/curl_osslq.c b/Utilities/cmcurl/lib/vquic/curl_osslq.c new file mode 100644 index 0000000..c499a00 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_osslq.c @@ -0,0 +1,2237 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + +#include <openssl/ssl.h> +#include <openssl/bio.h> +#include <openssl/err.h> +#include <nghttp3/nghttp3.h> + +#include "urldata.h" +#include "sendf.h" +#include "strdup.h" +#include "rand.h" +#include "multiif.h" +#include "strcase.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "connect.h" +#include "progress.h" +#include "strerror.h" +#include "dynbuf.h" +#include "http1.h" +#include "select.h" +#include "inet_pton.h" +#include "vquic.h" +#include "vquic_int.h" +#include "vquic-tls.h" +#include "vtls/keylog.h" +#include "vtls/vtls.h" +#include "vtls/openssl.h" +#include "curl_osslq.h" + +#include "warnless.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* A stream window is the maximum amount we need to buffer for + * each active transfer. We use HTTP/3 flow control and only ACK + * when we take things out of the buffer. + * Chunk size is large enough to take a full DATA frame */ +#define H3_STREAM_WINDOW_SIZE (128 * 1024) +#define H3_STREAM_CHUNK_SIZE (16 * 1024) +/* The pool keeps spares around and half of a full stream windows + * seems good. More does not seem to improve performance. + * The benefit of the pool is that stream buffer to not keep + * spares. So memory consumption goes down when streams run empty, + * have a large upload done, etc. */ +#define H3_STREAM_POOL_SPARES \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE ) / 2 +/* Receive and Send max number of chunks just follows from the + * chunk size and window size */ +#define H3_STREAM_RECV_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) +#define H3_STREAM_SEND_CHUNKS \ + (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef uint32_t sslerr_t; +#else +typedef unsigned long sslerr_t; +#endif + + +/* How to access `call_data` from a cf_osslq filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_osslq_ctx *)(cf)->ctx)->call_data + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data); + +static const char *SSL_ERROR_to_str(int err) +{ + switch(err) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; +#if defined(SSL_ERROR_WANT_ASYNC) + case SSL_ERROR_WANT_ASYNC: + return "SSL_ERROR_WANT_ASYNC"; +#endif +#if defined(SSL_ERROR_WANT_ASYNC_JOB) + case SSL_ERROR_WANT_ASYNC_JOB: + return "SSL_ERROR_WANT_ASYNC_JOB"; +#endif +#if defined(SSL_ERROR_WANT_EARLY) + case SSL_ERROR_WANT_EARLY: + return "SSL_ERROR_WANT_EARLY"; +#endif + default: + return "SSL_ERROR unknown"; + } +} + +/* Return error string for last OpenSSL error */ +static char *ossl_strerror(unsigned long error, char *buf, size_t size) +{ + DEBUGASSERT(size); + *buf = '\0'; + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + ERR_error_string_n((uint32_t)error, buf, size); +#else + ERR_error_string_n(error, buf, size); +#endif + + if(!*buf) { + const char *msg = error ? "Unknown error" : "No error"; + if(strlen(msg) < size) + strcpy(buf, msg); + } + + return buf; +} + +static CURLcode make_bio_addr(BIO_ADDR **pbio_addr, + const struct Curl_sockaddr_ex *addr) +{ + BIO_ADDR *ba; + CURLcode result = CURLE_FAILED_INIT; + + ba = BIO_ADDR_new(); + if(!ba) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + switch(addr->family) { + case AF_INET: { + struct sockaddr_in * const sin = + (struct sockaddr_in * const)(void *)&addr->sa_addr; + if(!BIO_ADDR_rawmake(ba, AF_INET, &sin->sin_addr, + sizeof(sin->sin_addr), sin->sin_port)) { + goto out; + } + result = CURLE_OK; + break; + } +#ifdef ENABLE_IPV6 + case AF_INET6: { + struct sockaddr_in6 * const sin = + (struct sockaddr_in6 * const)(void *)&addr->sa_addr; + if(!BIO_ADDR_rawmake(ba, AF_INET6, &sin->sin6_addr, + sizeof(sin->sin6_addr), sin->sin6_port)) { + } + result = CURLE_OK; + break; + } +#endif /* ENABLE_IPV6 */ + default: + /* sunsupported */ + DEBUGASSERT(0); + break; + } + +out: + if(result && ba) { + BIO_ADDR_free(ba); + ba = NULL; + } + *pbio_addr = ba; + return result; +} + +/* QUIC stream (not necessarily H3) */ +struct cf_osslq_stream { + int64_t id; + SSL *ssl; + struct bufq recvbuf; /* QUIC war data recv buffer */ + BIT(recvd_eos); + BIT(closed); + BIT(reset); + BIT(send_blocked); +}; + +static CURLcode cf_osslq_stream_open(struct cf_osslq_stream *s, + SSL *conn, + uint64_t flags, + struct bufc_pool *bufcp, + void *user_data) +{ + DEBUGASSERT(!s->ssl); + Curl_bufq_initp(&s->recvbuf, bufcp, 1, BUFQ_OPT_NONE); + s->ssl = SSL_new_stream(conn, flags); + if(!s->ssl) { + return CURLE_FAILED_INIT; + } + s->id = SSL_get_stream_id(s->ssl); + SSL_set_app_data(s->ssl, user_data); + return CURLE_OK; +} + +static void cf_osslq_stream_cleanup(struct cf_osslq_stream *s) +{ + if(s->ssl) { + SSL_set_app_data(s->ssl, NULL); + SSL_free(s->ssl); + } + Curl_bufq_free(&s->recvbuf); + memset(s, 0, sizeof(*s)); +} + +static void cf_osslq_stream_close(struct cf_osslq_stream *s) +{ + if(s->ssl) { + SSL_free(s->ssl); + s->ssl = NULL; + } +} + +struct cf_osslq_h3conn { + nghttp3_conn *conn; + nghttp3_settings settings; + struct cf_osslq_stream s_ctrl; + struct cf_osslq_stream s_qpack_enc; + struct cf_osslq_stream s_qpack_dec; + struct cf_osslq_stream remote_ctrl[3]; /* uni streams opened by the peer */ + size_t remote_ctrl_n; /* number of peer streams opened */ +}; + +static void cf_osslq_h3conn_cleanup(struct cf_osslq_h3conn *h3) +{ + size_t i; + + if(h3->conn) + nghttp3_conn_del(h3->conn); + cf_osslq_stream_cleanup(&h3->s_ctrl); + cf_osslq_stream_cleanup(&h3->s_qpack_enc); + cf_osslq_stream_cleanup(&h3->s_qpack_dec); + for(i = 0; i < h3->remote_ctrl_n; ++i) { + cf_osslq_stream_cleanup(&h3->remote_ctrl[i]); + } +} + +struct cf_osslq_ctx { + struct cf_quic_ctx q; + struct ssl_peer peer; + struct quic_tls_ctx tls; + struct cf_call_data call_data; + struct cf_osslq_h3conn h3; + struct curltime started_at; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + struct curltime first_byte_at; /* when first byte was recvd */ + struct curltime reconnect_at; /* time the next attempt should start */ + struct bufc_pool stream_bufcp; /* chunk pool for streams */ + size_t max_stream_window; /* max flow window for one stream */ + uint64_t max_idle_ms; /* max idle time for QUIC connection */ + BIT(got_first_byte); /* if first byte was received */ +#ifdef USE_OPENSSL + BIT(x509_store_setup); /* if x509 store has been set up */ + BIT(protocol_shutdown); /* QUIC connection is shut down */ +#endif +}; + +static void cf_osslq_ctx_clear(struct cf_osslq_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + cf_osslq_h3conn_cleanup(&ctx->h3); + Curl_vquic_tls_cleanup(&ctx->tls); + vquic_ctx_free(&ctx->q); + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_ssl_peer_cleanup(&ctx->peer); + + memset(ctx, 0, sizeof(*ctx)); + ctx->call_data = save; +} + +static void cf_osslq_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + if(ctx && ctx->tls.ssl) { + /* TODO: send connection close */ + CURL_TRC_CF(data, cf, "cf_osslq_close()"); + cf_osslq_ctx_clear(ctx); + } + + cf->connected = FALSE; + CF_DATA_RESTORE(cf, save); +} + +static void cf_osslq_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + CURL_TRC_CF(data, cf, "destroy"); + if(ctx) { + CURL_TRC_CF(data, cf, "cf_osslq_destroy()"); + cf_osslq_ctx_clear(ctx); + free(ctx); + } + cf->ctx = NULL; + /* No CF_DATA_RESTORE(cf, save) possible */ + (void)save; +} + +static CURLcode cf_osslq_h3conn_add_stream(struct cf_osslq_h3conn *h3, + SSL *stream_ssl, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + int64_t stream_id = SSL_get_stream_id(stream_ssl); + + if(h3->remote_ctrl_n >= ARRAYSIZE(h3->remote_ctrl)) { + /* rejected, we are full */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] rejecting additional remote stream", + stream_id); + SSL_free(stream_ssl); + return CURLE_FAILED_INIT; + } + switch(SSL_get_stream_type(stream_ssl)) { + case SSL_STREAM_TYPE_READ: { + struct cf_osslq_stream *nstream = &h3->remote_ctrl[h3->remote_ctrl_n++]; + nstream->id = stream_id; + nstream->ssl = stream_ssl; + Curl_bufq_initp(&nstream->recvbuf, &ctx->stream_bufcp, 1, BUFQ_OPT_NONE); + CURL_TRC_CF(data, cf, "[%" PRId64 "] accepted new remote uni stream", + stream_id); + break; + } + default: + CURL_TRC_CF(data, cf, "[%" PRId64 "] rejecting remote non-uni-read" + " stream", stream_id); + SSL_free(stream_ssl); + return CURLE_FAILED_INIT; + } + return CURLE_OK; + +} + +static CURLcode cf_osslq_ssl_err(struct Curl_cfilter *cf, + struct Curl_easy *data, + int detail, CURLcode def_result) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = def_result; + sslerr_t errdetail; + char ebuf[256] = "unknown"; + const char *err_descr = ebuf; + long lerr; + int lib; + int reason; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + + errdetail = ERR_get_error(); + lib = ERR_GET_LIB(errdetail); + reason = ERR_GET_REASON(errdetail); + + if((lib == ERR_LIB_SSL) && + ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) || + (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { + result = CURLE_PEER_FAILED_VERIFICATION; + + lerr = SSL_get_verify_result(ctx->tls.ssl); + if(lerr != X509_V_OK) { + ssl_config->certverifyresult = lerr; + msnprintf(ebuf, sizeof(ebuf), + "SSL certificate problem: %s", + X509_verify_cert_error_string(lerr)); + } + else + err_descr = "SSL certificate verification failed"; + } +#if defined(SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED) + /* SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED is only available on + OpenSSL version above v1.1.1, not LibreSSL, BoringSSL, or AWS-LC */ + else if((lib == ERR_LIB_SSL) && + (reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED)) { + /* If client certificate is required, communicate the + error to client */ + result = CURLE_SSL_CLIENTCERT; + ossl_strerror(errdetail, ebuf, sizeof(ebuf)); + } +#endif + else if((lib == ERR_LIB_SSL) && (reason == SSL_R_PROTOCOL_IS_SHUTDOWN)) { + ctx->protocol_shutdown = TRUE; + err_descr = "QUIC connectin has been shut down"; + result = def_result; + } + else { + result = def_result; + ossl_strerror(errdetail, ebuf, sizeof(ebuf)); + } + + /* detail is already set to the SSL error above */ + + /* If we e.g. use SSLv2 request-method and the server doesn't like us + * (RST connection, etc.), OpenSSL gives no explanation whatsoever and + * the SO_ERROR is also lost. + */ + if(CURLE_SSL_CONNECT_ERROR == result && errdetail == 0) { + char extramsg[80]=""; + int sockerr = SOCKERRNO; + const char *r_ip = NULL; + int r_port = 0; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + if(sockerr && detail == SSL_ERROR_SYSCALL) + Curl_strerror(sockerr, extramsg, sizeof(extramsg)); + failf(data, "QUIC connect: %s in connection to %s:%d (%s)", + extramsg[0] ? extramsg : SSL_ERROR_to_str(detail), + ctx->peer.dispname, r_port, r_ip); + } + else { + /* Could be a CERT problem */ + failf(data, "%s", err_descr); + } + return result; +} + +static CURLcode cf_osslq_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); +} + +/** + * All about the H3 internals of a stream + */ +struct h3_stream_ctx { + struct cf_osslq_stream s; + struct bufq sendbuf; /* h3 request body */ + struct bufq recvbuf; /* h3 response body */ + struct h1_req_parser h1; /* h1 request parsing */ + size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ + size_t upload_blocked_len; /* the amount written last and EGAINed */ + size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */ + uint64_t error3; /* HTTP/3 stream error code */ + curl_off_t upload_left; /* number of request bytes left to upload */ + curl_off_t download_recvd; /* number of response DATA bytes received */ + int status_code; /* HTTP status code */ + bool resp_hds_complete; /* we have a complete, final response */ + bool closed; /* TRUE on stream close */ + bool reset; /* TRUE on stream reset */ + bool send_closed; /* stream is local closed */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ +}; + +#define H3_STREAM_CTX(d) ((struct h3_stream_ctx *)(((d) && (d)->req.p.http)? \ + ((struct HTTP *)(d)->req.p.http)->h3_ctx \ + : NULL)) +#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx +#define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ + H3_STREAM_CTX(d)->s.id : -2) + +static CURLcode h3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + + if(!data || !data->req.p.http) { + failf(data, "initialization failure, transfer not http initialized"); + return CURLE_FAILED_INIT; + } + + if(stream) + return CURLE_OK; + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->s.id = -1; + /* on send, we control how much we put into the buffer */ + Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, + H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); + stream->sendbuf_len_in_flight = 0; + /* on recv, we need a flexible buffer limit since we also write + * headers to it that are not counted against the nghttp3 flow limits. */ + Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, + H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + stream->recv_buf_nonflow = 0; + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + H3_STREAM_LCTX(data) = stream; + return CURLE_OK; +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->s.id); + if(ctx->h3.conn && !stream->closed) { + nghttp3_conn_shutdown_stream_read(ctx->h3.conn, stream->s.id); + nghttp3_conn_close_stream(ctx->h3.conn, stream->s.id, + NGHTTP3_H3_REQUEST_CANCELLED); + nghttp3_conn_set_stream_user_data(ctx->h3.conn, stream->s.id, NULL); + stream->closed = TRUE; + } + + cf_osslq_stream_cleanup(&stream->s); + Curl_bufq_free(&stream->sendbuf); + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); + H3_STREAM_LCTX(data) = NULL; + } +} + +static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream_id) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct Curl_easy *sdata; + + if(stream && stream->s.id == stream_id) { + return &stream->s; + } + else if(ctx->h3.s_ctrl.id == stream_id) { + return &ctx->h3.s_ctrl; + } + else if(ctx->h3.s_qpack_enc.id == stream_id) { + return &ctx->h3.s_qpack_enc; + } + else if(ctx->h3.s_qpack_dec.id == stream_id) { + return &ctx->h3.s_qpack_dec; + } + else { + DEBUGASSERT(data->multi); + for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { + if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream_id) { + stream = H3_STREAM_CTX(sdata); + return stream? &stream->s : NULL; + } + } + } + return NULL; +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && stream->upload_left && !stream->send_closed) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static CURLcode h3_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + if(!pause) { + /* unpaused. make it run again right away */ + h3_drain_stream(cf, data); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + return CURLE_OK; +} + +static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + (void)conn; + (void)stream_id; + + /* we might be called by nghttp3 after we already cleaned up */ + if(!stream) + return 0; + + stream->closed = TRUE; + stream->error3 = app_error_code; + if(stream->error3 != NGHTTP3_H3_NO_ERROR) { + stream->reset = TRUE; + stream->send_closed = TRUE; + CURL_TRC_CF(data, cf, "[%" PRId64 "] RESET: error %" PRId64, + stream->s.id, stream->error3); + } + else { + CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->s.id); + } + h3_drain_stream(cf, data); + return 0; +} + +/* + * write_resp_raw() copies response data in raw format to the `data`'s + * receive buffer. If not enough space is available, it appends to the + * `data`'s overflow buffer. + */ +static CURLcode write_resp_raw(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, size_t memlen, + bool flow) +{ + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result = CURLE_OK; + ssize_t nwritten; + + (void)cf; + if(!stream) { + return CURLE_RECV_ERROR; + } + nwritten = Curl_bufq_write(&stream->recvbuf, mem, memlen, &result); + if(nwritten < 0) { + return result; + } + + if(!flow) + stream->recv_buf_nonflow += (size_t)nwritten; + + if((size_t)nwritten < memlen) { + /* This MUST not happen. Our recbuf is dimensioned to hold the + * full max_stream_window and then some for this very reason. */ + DEBUGASSERT(0); + return CURLE_RECV_ERROR; + } + return result; +} + +static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, + const uint8_t *buf, size_t buflen, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result; + + (void)conn; + (void)stream3_id; + + if(!stream) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + result = write_resp_raw(cf, data, buf, buflen, TRUE); + if(result) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, ERROR receiving %d", + stream->s.id, buflen, result); + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + stream->download_recvd += (curl_off_t)buflen; + CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, total=%zd", + stream->s.id, buflen, stream->download_recvd); + h3_drain_stream(cf, data); + return 0; +} + +static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t consumed, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + + (void)conn; + (void)stream_id; + if(stream) + CURL_TRC_CF(data, cf, "[%" PRId64 "] deferred consume %zu bytes", + stream->s.id, consumed); + return 0; +} + +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, + int32_t token, nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); + nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)token; + (void)flags; + (void)cf; + + /* we might have cleaned up this transfer already */ + if(!stream) + return 0; + + if(token == NGHTTP3_QPACK_TOKEN__STATUS) { + char line[14]; /* status line is always 13 characters long */ + size_t ncopy; + + result = Curl_http_decode_status(&stream->status_code, + (const char *)h3val.base, h3val.len); + if(result) + return -1; + ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", + stream->status_code); + CURL_TRC_CF(data, cf, "[%" PRId64 "] status: %s", stream_id, line); + result = write_resp_raw(cf, data, line, ncopy, FALSE); + if(result) { + return -1; + } + } + else { + /* store as an HTTP1-style header */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s", + stream_id, (int)h3name.len, h3name.base, + (int)h3val.len, h3val.base); + result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, ": ", 2, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE); + if(result) { + return -1; + } + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + } + return 0; +} + +static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, + int fin, void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)fin; + (void)cf; + + if(!stream) + return 0; + /* add a CRLF only if we've received some headers */ + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + + CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d", + stream_id, stream->status_code); + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; + } + h3_drain_stream(cf, data); + return 0; +} + +static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + (void)conn; + (void)app_error_code; + + if(!stream || !stream->s.ssl) + return 0; + + CURL_TRC_CF(data, cf, "[%" PRId64 "] stop_sending", stream_id); + cf_osslq_stream_close(&stream->s); + return 0; +} + +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + int rv; + (void)conn; + + if(stream && stream->s.ssl) { + SSL_STREAM_RESET_ARGS args = {0}; + args.quic_error_code = app_error_code; + rv = !SSL_stream_reset(stream->s.ssl, &args, sizeof(args)); + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + if(!rv) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static nghttp3_ssize +cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, + uint32_t *pflags, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t nwritten = 0; + size_t nvecs = 0; + (void)cf; + (void)conn; + (void)stream_id; + (void)user_data; + (void)veccnt; + + if(!stream) + return NGHTTP3_ERR_CALLBACK_FAILURE; + /* nghttp3 keeps references to the sendbuf data until it is ACKed + * by the server (see `cb_h3_acked_req_body()` for updates). + * `sendbuf_len_in_flight` is the amount of bytes in `sendbuf` + * that we have already passed to nghttp3, but which have not been + * ACKed yet. + * Any amount beyond `sendbuf_len_in_flight` we need still to pass + * to nghttp3. Do that now, if we can. */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + nvecs = 0; + while(nvecs < veccnt && + Curl_bufq_peek_at(&stream->sendbuf, + stream->sendbuf_len_in_flight, + (const unsigned char **)&vec[nvecs].base, + &vec[nvecs].len)) { + stream->sendbuf_len_in_flight += vec[nvecs].len; + nwritten += vec[nvecs].len; + ++nvecs; + } + DEBUGASSERT(nvecs > 0); /* we SHOULD have been be able to peek */ + } + + if(nwritten > 0 && stream->upload_left != -1) + stream->upload_left -= nwritten; + + /* When we stopped sending and everything in `sendbuf` is "in flight", + * we are at the end of the request body. */ + if(stream->upload_left == 0) { + *pflags = NGHTTP3_DATA_FLAG_EOF; + stream->send_closed = TRUE; + } + else if(!nwritten) { + /* Not EOF, and nothing to give, we signal WOULDBLOCK. */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> AGAIN", + stream->s.id); + return NGHTTP3_ERR_WOULDBLOCK; + } + + CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> " + "%d vecs%s with %zu (buffered=%zu, left=%" + CURL_FORMAT_CURL_OFF_T ")", + stream->s.id, (int)nvecs, + *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"", + nwritten, Curl_bufq_len(&stream->sendbuf), + stream->upload_left); + return (nghttp3_ssize)nvecs; +} + +static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + size_t skiplen; + + (void)cf; + if(!stream) + return 0; + /* The server acknowledged `datalen` of bytes from our request body. + * This is a delta. We have kept this data in `sendbuf` for + * re-transmissions and can free it now. */ + if(datalen >= (uint64_t)stream->sendbuf_len_in_flight) + skiplen = stream->sendbuf_len_in_flight; + else + skiplen = (size_t)datalen; + Curl_bufq_skip(&stream->sendbuf, skiplen); + stream->sendbuf_len_in_flight -= skiplen; + + /* Everything ACKed, we resume upload processing */ + if(!stream->sendbuf_len_in_flight) { + int rv = nghttp3_conn_resume_stream(conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static nghttp3_callbacks ngh3_callbacks = { + cb_h3_acked_stream_data, + cb_h3_stream_close, + cb_h3_recv_data, + cb_h3_deferred_consume, + NULL, /* begin_headers */ + cb_h3_recv_header, + cb_h3_end_headers, + NULL, /* begin_trailers */ + cb_h3_recv_header, + NULL, /* end_trailers */ + cb_h3_stop_sending, + NULL, /* end_stream */ + cb_h3_reset_stream, + NULL, /* shutdown */ + NULL /* recv_settings */ +}; + +static CURLcode cf_osslq_h3conn_init(struct cf_osslq_ctx *ctx, SSL *conn, + void *user_data) +{ + struct cf_osslq_h3conn *h3 = &ctx->h3; + CURLcode result; + int rc; + + nghttp3_settings_default(&h3->settings); + rc = nghttp3_conn_client_new(&h3->conn, + &ngh3_callbacks, + &h3->settings, + nghttp3_mem_default(), + user_data); + if(rc) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = cf_osslq_stream_open(&h3->s_ctrl, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + result = cf_osslq_stream_open(&h3->s_qpack_enc, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + result = cf_osslq_stream_open(&h3->s_qpack_dec, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + + rc = nghttp3_conn_bind_control_stream(h3->conn, h3->s_ctrl.id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + rc = nghttp3_conn_bind_qpack_streams(h3->conn, h3->s_qpack_enc.id, + h3->s_qpack_dec.id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + + result = CURLE_OK; +out: + return result; +} + +static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result; + int rv; + const struct Curl_sockaddr_ex *peer_addr = NULL; + int peer_port; + BIO *bio = NULL; + BIO_ADDR *baddr = NULL; + + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + result = Curl_ssl_peer_init(&ctx->peer, cf); + if(result) + goto out; + +#define H3_ALPN "\x2h3" + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + H3_ALPN, sizeof(H3_ALPN) - 1, + NULL, NULL); + if(result) + goto out; + + result = vquic_ctx_init(&ctx->q); + if(result) + goto out; + + result = CURLE_QUIC_CONNECT_ERROR; + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, + &peer_addr, NULL, &peer_port, NULL, NULL); + if(!peer_addr) + goto out; + + ctx->q.local_addrlen = sizeof(ctx->q.local_addr); + rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, + &ctx->q.local_addrlen); + if(rv == -1) + goto out; + + result = make_bio_addr(&baddr, peer_addr); + if(result) { + failf(data, "error creating BIO_ADDR from sockaddr"); + goto out; + } + + bio = BIO_new_dgram(ctx->q.sockfd, BIO_NOCLOSE); + if(!bio) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(!SSL_set1_initial_peer_addr(ctx->tls.ssl, baddr)) { + failf(data, "failed to set the initial peer address"); + result = CURLE_FAILED_INIT; + goto out; + } + if(!SSL_set_blocking_mode(ctx->tls.ssl, 0)) { + failf(data, "failed to turn off blocking mode"); + result = CURLE_FAILED_INIT; + goto out; + } + + SSL_set_bio(ctx->tls.ssl, bio, bio); + bio = NULL; + SSL_set_connect_state(ctx->tls.ssl); + SSL_set_incoming_stream_policy(ctx->tls.ssl, + SSL_INCOMING_STREAM_POLICY_ACCEPT, 0); + /* setup the H3 things on top of the QUIC connection */ + result = cf_osslq_h3conn_init(ctx, ctx->tls.ssl, cf); + +out: + if(bio) + BIO_free(bio); + if(baddr) + BIO_ADDR_free(baddr); + CURL_TRC_CF(data, cf, "QUIC tls init -> %d", result); + return result; +} + +struct h3_quic_recv_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + struct cf_osslq_stream *s; +}; + +static ssize_t h3_quic_recv(void *reader_ctx, + unsigned char *buf, size_t len, + CURLcode *err) +{ + struct h3_quic_recv_ctx *x = reader_ctx; + size_t nread; + int rv; + + *err = CURLE_OK; + rv = SSL_read_ex(x->s->ssl, buf, len, &nread); + if(rv <= 0) { + int detail = SSL_get_error(x->s->ssl, rv); + if(detail == SSL_ERROR_WANT_READ || detail == SSL_ERROR_WANT_WRITE) { + *err = CURLE_AGAIN; + return -1; + } + else if(detail == SSL_ERROR_ZERO_RETURN) { + CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] h3_quic_recv -> EOS", + x->s->id); + x->s->recvd_eos = TRUE; + return 0; + } + else if(SSL_get_stream_read_state(x->s->ssl) == + SSL_STREAM_STATE_RESET_REMOTE) { + uint64_t app_error_code = NGHTTP3_H3_NO_ERROR; + SSL_get_stream_read_error_code(x->s->ssl, &app_error_code); + CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] h3_quic_recv -> RESET, " + "rv=%d, app_err=%" PRIu64, + x->s->id, rv, app_error_code); + if(app_error_code != NGHTTP3_H3_NO_ERROR) { + x->s->reset = TRUE; + } + x->s->recvd_eos = TRUE; + return 0; + } + else { + *err = cf_osslq_ssl_err(x->cf, x->data, detail, CURLE_RECV_ERROR); + return -1; + } + } + else { + /* CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] h3_quic_recv -> %zu bytes", + x->s->id, nread); */ + } + return (ssize_t)nread; +} + +static CURLcode cf_osslq_stream_recv(struct cf_osslq_stream *s, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + ssize_t nread; + struct h3_quic_recv_ctx x; + int rv, eagain = FALSE; + size_t total_recv_len = 0; + + DEBUGASSERT(s); + if(s->closed) + return CURLE_OK; + + x.cf = cf; + x.data = data; + x.s = s; + while(s->ssl && !s->closed && !eagain && + (total_recv_len < H3_STREAM_CHUNK_SIZE)) { + if(Curl_bufq_is_empty(&s->recvbuf) && !s->recvd_eos) { + while(!eagain && !s->recvd_eos && !Curl_bufq_is_full(&s->recvbuf)) { + nread = Curl_bufq_sipn(&s->recvbuf, 0, h3_quic_recv, &x, &result); + if(nread < 0) { + if(result != CURLE_AGAIN) + goto out; + result = CURLE_OK; + eagain = TRUE; + } + } + } + + /* Forward what we have to nghttp3 */ + if(!Curl_bufq_is_empty(&s->recvbuf)) { + const unsigned char *buf; + size_t blen; + + while(Curl_bufq_peek(&s->recvbuf, &buf, &blen)) { + nread = nghttp3_conn_read_stream(ctx->h3.conn, s->id, + buf, blen, 0); + CURL_TRC_CF(data, cf, "[%" PRId64 "] forward %zu bytes " + "to nghttp3 -> %zd", s->id, blen, nread); + if(nread < 0) { + failf(data, "nghttp3_conn_read_stream(len=%zu) error: %s", + blen, nghttp3_strerror((int)nread)); + result = CURLE_RECV_ERROR; + goto out; + } + /* success, `nread` is the flow for QUIC to count as "consumed", + * not sure how that will work with OpenSSL. Anyways, without error, + * all data that we passed is not owned by nghttp3. */ + Curl_bufq_skip(&s->recvbuf, blen); + total_recv_len += blen; + } + } + + /* When we forwarded everything, handle RESET/EOS */ + if(Curl_bufq_is_empty(&s->recvbuf) && !s->closed) { + result = CURLE_OK; + if(s->reset) { + uint64_t app_error; + if(!SSL_get_stream_read_error_code(s->ssl, &app_error)) { + failf(data, "SSL_get_stream_read_error_code returned error"); + result = CURLE_RECV_ERROR; + goto out; + } + rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, app_error); + s->closed = TRUE; + if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_close_stream returned error: %s", + nghttp3_strerror(rv)); + result = CURLE_RECV_ERROR; + goto out; + } + } + else if(s->recvd_eos) { + rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, + NGHTTP3_H3_NO_ERROR); + s->closed = TRUE; + CURL_TRC_CF(data, cf, "[%" PRId64 "] close nghttp3 stream -> %d", + s->id, rv); + if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_close_stream returned error: %s", + nghttp3_strerror(rv)); + result = CURLE_RECV_ERROR; + goto out; + } + } + } + } +out: + if(result) + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_osslq_stream_recv -> %d", + s->id, result); + return result; +} + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ssl) + goto out; + + ERR_clear_error(); + + /* 1. Check for new incoming streams */ + while(1) { + SSL *snew = SSL_accept_stream(ctx->tls.ssl, SSL_ACCEPT_STREAM_NO_BLOCK); + if(!snew) + break; + + (void)cf_osslq_h3conn_add_stream(&ctx->h3, snew, cf, data); + } + + if(!SSL_handle_events(ctx->tls.ssl)) { + int detail = SSL_get_error(ctx->tls.ssl, 0); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_RECV_ERROR); + } + + if(ctx->h3.conn) { + size_t i; + for(i = 0; i < ctx->h3.remote_ctrl_n; ++i) { + result = cf_osslq_stream_recv(&ctx->h3.remote_ctrl[i], cf, data); + if(result) + goto out; + } + } + + if(ctx->h3.conn) { + struct Curl_easy *sdata; + struct h3_stream_ctx *stream; + /* PULL all open streams */ + DEBUGASSERT(data->multi); + for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { + if(sdata->conn == data->conn && CURL_WANT_RECV(sdata)) { + stream = H3_STREAM_CTX(sdata); + if(stream && !stream->closed && + !Curl_bufq_is_full(&stream->recvbuf)) { + result = cf_osslq_stream_recv(&stream->s, cf, sdata); + if(result) + goto out; + } + } + } + } + +out: + CURL_TRC_CF(data, cf, "progress_ingress -> %d", result); + return result; +} + +/* Iterate over all streams and check if blocked can be unblocked */ +static CURLcode cf_osslq_check_and_unblock(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *sdata; + struct h3_stream_ctx *stream; + + if(ctx->h3.conn) { + for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { + if(sdata->conn == data->conn) { + stream = H3_STREAM_CTX(sdata); + if(stream && stream->s.ssl && stream->s.send_blocked && + !SSL_want_write(stream->s.ssl)) { + nghttp3_conn_unblock_stream(ctx->h3.conn, stream->s.id); + stream->s.send_blocked = FALSE; + h3_drain_stream(cf, sdata); + CURL_TRC_CF(sdata, cf, "unblocked"); + } + } + } + } + return CURLE_OK; +} + +static CURLcode h3_send_streams(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ssl || !ctx->h3.conn) + goto out; + + for(;;) { + struct cf_osslq_stream *s = NULL; + nghttp3_vec vec[16]; + nghttp3_ssize n, i; + int64_t stream_id; + size_t written; + int eos, ok, rv; + size_t total_len, acked_len = 0; + bool blocked = FALSE; + + n = nghttp3_conn_writev_stream(ctx->h3.conn, &stream_id, &eos, + vec, ARRAYSIZE(vec)); + if(n < 0) { + failf(data, "nghttp3_conn_writev_stream returned error: %s", + nghttp3_strerror((int)n)); + result = CURLE_SEND_ERROR; + goto out; + } + if(stream_id < 0) { + result = CURLE_OK; + goto out; + } + + /* Get the stream for this data */ + s = cf_osslq_get_qstream(cf, data, stream_id); + if(!s) { + failf(data, "nghttp3_conn_writev_stream gave unknown stream %" PRId64, + stream_id); + result = CURLE_SEND_ERROR; + goto out; + } + /* Now write the data to the stream's SSL*, it may not all fit! */ + DEBUGASSERT(s->id == stream_id); + for(i = 0, total_len = 0; i < n; ++i) { + total_len += vec[i].len; + } + for(i = 0; (i < n) && !blocked; ++i) { + /* Without stream->s.ssl, we closed that already, so + * pretend the write did succeed. */ + written = vec[i].len; + ok = !s->ssl || SSL_write_ex(s->ssl, vec[i].base, vec[i].len, + &written); + if(ok) { + /* As OpenSSL buffers the data, we count this as acknowledged + * from nghttp3's point of view */ + CURL_TRC_CF(data, cf, "[%"PRId64"] send %zu bytes to QUIC ok", + s->id, vec[i].len); + acked_len += vec[i].len; + } + else { + int detail = SSL_get_error(s->ssl, 0); + switch(detail) { + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + /* QUIC blocked us from writing more */ + CURL_TRC_CF(data, cf, "[%"PRId64"] send %zu bytes to QUIC blocked", + s->id, vec[i].len); + written = 0; + nghttp3_conn_block_stream(ctx->h3.conn, s->id); + s->send_blocked = blocked = TRUE; + break; + default: + failf(data, "[%"PRId64"] send %zu bytes to QUIC, SSL error %d", + s->id, vec[i].len, detail); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_SEND_ERROR); + goto out; + } + } + } + + if(acked_len > 0 || (eos && !s->send_blocked)) { + /* Since QUIC buffers the data written internally, we can tell + * nghttp3 that it can move forward on it */ + rv = nghttp3_conn_add_write_offset(ctx->h3.conn, s->id, acked_len); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", + nghttp3_strerror(rv)); + result = CURLE_SEND_ERROR; + goto out; + } + rv = nghttp3_conn_add_ack_offset(ctx->h3.conn, s->id, acked_len); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_add_ack_offset returned error: %s\n", + nghttp3_strerror(rv)); + result = CURLE_SEND_ERROR; + goto out; + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] forwarded %zu/%zu h3 bytes " + "to QUIC, eos=%d", s->id, acked_len, total_len, eos); + } + + if(eos && !s->send_blocked) { + /* wrote everything and H3 indicates end of stream */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] closing QUIC stream", s->id); + SSL_stream_conclude(s->ssl, 0); + } + } + +out: + CURL_TRC_CF(data, cf, "h3_send_streams -> %d", result); + return result; +} + +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ssl) + goto out; + + ERR_clear_error(); + result = h3_send_streams(cf, data); + if(result) + goto out; + + if(!SSL_handle_events(ctx->tls.ssl)) { + int detail = SSL_get_error(ctx->tls.ssl, 0); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_SEND_ERROR); + } + + result = cf_osslq_check_and_unblock(cf, data); + +out: + CURL_TRC_CF(data, cf, "progress_egress -> %d", result); + return result; +} + +static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct timeval tv; + timediff_t timeoutms; + int is_infinite = TRUE; + + if(ctx->tls.ssl && + SSL_get_event_timeout(ctx->tls.ssl, &tv, &is_infinite) && + !is_infinite) { + timeoutms = curlx_tvtoms(&tv); + /* QUIC want to be called again latest at the returned timeout */ + if(timeoutms <= 0) { + result = cf_progress_ingress(cf, data); + if(result) + goto out; + result = cf_progress_egress(cf, data); + if(result) + goto out; + if(SSL_get_event_timeout(ctx->tls.ssl, &tv, &is_infinite)) { + timeoutms = curlx_tvtoms(&tv); + } + } + if(!is_infinite) { + Curl_expire(data, timeoutms, EXPIRE_QUIC); + CURL_TRC_CF(data, cf, "QUIC expiry in %ldms", (long)timeoutms); + } + } +out: + return result; +} + +static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + struct curltime now; + int err; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* Connect the UDP filter first */ + if(!cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + } + + *done = FALSE; + now = Curl_now(); + CF_DATA_SAVE(save, cf, data); + + if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) { + /* Not time yet to attempt the next connect */ + CURL_TRC_CF(data, cf, "waiting for reconnect time"); + goto out; + } + + if(!ctx->tls.ssl) { + ctx->started_at = now; + result = cf_osslq_ctx_start(cf, data); + if(result) + goto out; + } + + if(!ctx->got_first_byte) { + int readable = SOCKET_READABLE(ctx->q.sockfd, 0); + if(readable > 0 && (readable & CURL_CSELECT_IN)) { + ctx->got_first_byte = TRUE; + ctx->first_byte_at = Curl_now(); + } + } + + ERR_clear_error(); + err = SSL_do_handshake(ctx->tls.ssl); + + if(err == 1) { + /* connected */ + ctx->handshake_at = now; + CURL_TRC_CF(data, cf, "handshake complete after %dms", + (int)Curl_timediff(now, ctx->started_at)); + result = cf_osslq_verify_peer(cf, data); + if(!result) { + CURL_TRC_CF(data, cf, "peer verified"); + cf->connected = TRUE; + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + connkeep(cf->conn, "HTTP/3 default"); + } + } + else { + int detail = SSL_get_error(ctx->tls.ssl, err); + switch(detail) { + case SSL_ERROR_WANT_READ: + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_RECV"); + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + goto out; + case SSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_SEND"); + result = CURLE_OK; + goto out; +#ifdef SSL_ERROR_WANT_ASYNC + case SSL_ERROR_WANT_ASYNC: + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_ASYNC"); + result = CURLE_OK; + goto out; +#endif +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + case SSL_ERROR_WANT_RETRY_VERIFY: + result = CURLE_OK; + goto out; +#endif + default: + result = cf_osslq_ssl_err(cf, data, detail, CURLE_COULDNT_CONNECT); + goto out; + } + } + +out: + if(result == CURLE_RECV_ERROR && ctx->tls.ssl && ctx->protocol_shutdown) { + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE right away. Our connection then enters the DRAINING + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; + } + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(result) { + const char *r_ip = NULL; + int r_port = 0; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + infof(data, "QUIC connect to %s port %u failed: %s", + r_ip, r_port, curl_easy_strerror(result)); + } +#endif + if(!result) + result = check_and_set_expiry(cf, data); + if(result || *done) + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); + CF_DATA_RESTORE(cf, save); + return result; +} + +static ssize_t h3_stream_open(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t len, + CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = NULL; + struct dynhds h2_headers; + size_t nheader; + nghttp3_nv *nva = NULL; + int rc = 0; + unsigned int i; + ssize_t nwritten = -1; + nghttp3_data_reader reader; + nghttp3_data_reader *preader = NULL; + + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + + *err = h3_data_setup(cf, data); + if(*err) + goto out; + stream = H3_STREAM_CTX(data); + DEBUGASSERT(stream); + if(!stream) { + *err = CURLE_FAILED_INIT; + goto out; + } + + nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); + if(nwritten < 0) + goto out; + if(!stream->h1.done) { + /* need more data */ + goto out; + } + DEBUGASSERT(stream->h1.req); + + *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); + if(*err) { + nwritten = -1; + goto out; + } + /* no longer needed */ + Curl_h1_req_parse_free(&stream->h1); + + nheader = Curl_dynhds_count(&h2_headers); + nva = malloc(sizeof(nghttp3_nv) * nheader); + if(!nva) { + *err = CURLE_OUT_OF_MEMORY; + nwritten = -1; + goto out; + } + + for(i = 0; i < nheader; ++i) { + struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i); + nva[i].name = (unsigned char *)e->name; + nva[i].namelen = e->namelen; + nva[i].value = (unsigned char *)e->value; + nva[i].valuelen = e->valuelen; + nva[i].flags = NGHTTP3_NV_FLAG_NONE; + } + + DEBUGASSERT(stream->s.id == -1); + *err = cf_osslq_stream_open(&stream->s, ctx->tls.ssl, 0, + &ctx->stream_bufcp, data); + if(*err) { + failf(data, "can't get bidi streams"); + *err = CURLE_SEND_ERROR; + goto out; + } + + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: + /* known request body size or -1 */ + if(data->state.infilesize != -1) + stream->upload_left = data->state.infilesize; + else + /* data sending without specifying the data amount up front */ + stream->upload_left = -1; /* unknown */ + break; + default: + /* there is not request body */ + stream->upload_left = 0; /* no request body */ + break; + } + + stream->send_closed = (stream->upload_left == 0); + if(!stream->send_closed) { + reader.read_data = cb_h3_read_req_body; + preader = &reader; + } + + rc = nghttp3_conn_submit_request(ctx->h3.conn, stream->s.id, + nva, nheader, preader, data); + if(rc) { + switch(rc) { + case NGHTTP3_ERR_CONN_CLOSING: + CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send, " + "connection is closing", stream->s.id); + break; + default: + CURL_TRC_CF(data, cf, "h3sid[%"PRId64"] failed to send -> %d (%s)", + stream->s.id, rc, nghttp3_strerror(rc)); + break; + } + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s", + stream->s.id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" PRId64 "] [%.*s: %.*s]", stream->s.id, + (int)nva[i].namelen, nva[i].name, + (int)nva[i].valuelen, nva[i].value); + } + } + +out: + free(nva); + Curl_dynhds_free(&h2_headers); + return nwritten; +} + +static ssize_t cf_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_call_data save; + ssize_t nwritten; + CURLcode result; + + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx->tls.ssl); + DEBUGASSERT(ctx->h3.conn); + *err = CURLE_OK; + + result = cf_progress_ingress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + result = cf_progress_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + if(!stream || stream->s.id < 0) { + nwritten = h3_stream_open(cf, data, buf, len, err); + if(nwritten < 0) { + CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); + goto out; + } + stream = H3_STREAM_CTX(data); + } + else if(stream->upload_blocked_len) { + /* the data in `buf` has already been submitted or added to the + * buffers, but have been EAGAINed on the last invocation. */ + DEBUGASSERT(len >= stream->upload_blocked_len); + if(len < stream->upload_blocked_len) { + /* Did we get called again with a smaller `len`? This should not + * happen. We are not prepared to handle that. */ + failf(data, "HTTP/3 send again with decreased length"); + *err = CURLE_HTTP3; + nwritten = -1; + goto out; + } + nwritten = (ssize_t)stream->upload_blocked_len; + stream->upload_blocked_len = 0; + } + else if(stream->closed) { + if(stream->resp_hds_complete) { + /* Server decided to close the stream after having sent us a final + * response. This is valid if it is not interested in the request + * body. This happens on 30x or 40x responses. + * We silently discard the data sent, since this is not a transport + * error situation. */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" + "on closed stream with response", stream->s.id); + *err = CURLE_OK; + nwritten = (ssize_t)len; + goto out; + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->s.id, len); + *err = CURLE_HTTP3; + nwritten = -1; + goto out; + } + else { + nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send, add to " + "sendbuf(len=%zu) -> %zd, %d", + stream->s.id, len, nwritten, *err); + if(nwritten < 0) { + goto out; + } + + (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); + } + + result = cf_progress_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + } + + if(stream && nwritten > 0 && stream->sendbuf_len_in_flight) { + /* We have unacknowledged DATA and cannot report success to our + * caller. Instead we EAGAIN and remember how much we have already + * "written" into our various internal connection buffers. */ + stream->upload_blocked_len = nwritten; + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu), " + "%zu bytes in flight -> EGAIN", stream->s.id, len, + stream->sendbuf_len_in_flight); + *err = CURLE_AGAIN; + nwritten = -1; + } + +out: + result = check_and_set_expiry(cf, data); + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %zd, %d", + stream? stream->s.id : -1, len, nwritten, *err); + CF_DATA_RESTORE(cf, save); + return nwritten; +} + +static ssize_t recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + CURLcode *err) +{ + ssize_t nread = -1; + + (void)cf; + if(stream->reset) { + failf(data, + "HTTP/3 stream %" PRId64 " reset by server", stream->s.id); + *err = stream->resp_hds_complete? CURLE_PARTIAL_FILE : CURLE_HTTP3; + goto out; + } + else if(!stream->resp_hds_complete) { + failf(data, + "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" + " all response header fields, treated as error", + stream->s.id); + *err = CURLE_HTTP3; + goto out; + } + *err = CURLE_OK; + nread = 0; + +out: + return nread; +} + +static ssize_t cf_osslq_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + ssize_t nread = -1; + struct cf_call_data save; + CURLcode result; + + (void)ctx; + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->tls.ssl); + DEBUGASSERT(ctx->h3.conn); + *err = CURLE_OK; + + if(!stream) { + *err = CURLE_RECV_ERROR; + goto out; + } + + if(!Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->s.id, len, nread, *err); + goto out; + } + } + + result = cf_progress_ingress(cf, data); + if(result) { + *err = result; + nread = -1; + goto out; + } + + /* recvbuf had nothing before, maybe after progressing ingress? */ + if(nread < 0 && !Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, + (unsigned char *)buf, len, err); + if(nread < 0) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->s.id, len, nread, *err); + goto out; + } + } + + if(nread > 0) { + h3_drain_stream(cf, data); + } + else { + if(stream->closed) { + nread = recv_closed_stream(cf, data, stream, err); + goto out; + } + *err = CURLE_AGAIN; + nread = -1; + } + +out: + if(cf_progress_egress(cf, data)) { + *err = CURLE_SEND_ERROR; + nread = -1; + } + else { + CURLcode result2 = check_and_set_expiry(cf, data); + if(result2) { + *err = result2; + nread = -1; + } + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(len=%zu) -> %zd, %d", + stream? stream->s.id : -1, len, nread, *err); + CF_DATA_RESTORE(cf, save); + return nread; +} + +/* + * Called from transfer.c:data_pending to know if we should keep looping + * to receive more data from the connection. + */ +static bool cf_osslq_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + const struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + (void)cf; + return stream && !Curl_bufq_is_empty(&stream->recvbuf); +} + +static CURLcode cf_osslq_data_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_SETUP: + break; + case CF_CTRL_DATA_PAUSE: + result = h3_data_pause(cf, data, (arg1 != 0)); + break; + case CF_CTRL_DATA_DETACH: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE_SEND: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + if(stream && !stream->send_closed) { + stream->send_closed = TRUE; + stream->upload_left = Curl_bufq_len(&stream->sendbuf); + (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); + } + break; + } + case CF_CTRL_DATA_IDLE: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + CURL_TRC_CF(data, cf, "data idle"); + if(stream && !stream->closed) { + result = check_and_set_expiry(cf, data); + } + break; + } + default: + break; + } + CF_DATA_RESTORE(cf, save); + return result; +} + +static bool cf_osslq_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + bool alive = FALSE; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + *input_pending = FALSE; + if(!ctx->tls.ssl) + goto out; + + /* TODO: how to check negotiated connection idle time? */ + + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + goto out; + + alive = TRUE; + if(*input_pending) { + CURLcode result; + /* This happens before we've sent off a request and the connection is + not in use by any other transfer, there shouldn't be any data here, + only "protocol frames" */ + *input_pending = FALSE; + result = cf_progress_ingress(cf, data); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + alive = result? FALSE : TRUE; + } + +out: + CF_DATA_RESTORE(cf, save); + return alive; +} + +static void cf_osslq_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + if(!ctx->tls.ssl) { + /* NOP */ + } + else if(!cf->connected) { + /* during handshake, transfer has not started yet. we always + * add our socket for polling if SSL wants to send/recv */ + Curl_pollset_set(data, ps, ctx->q.sockfd, + SSL_net_read_desired(ctx->tls.ssl), + SSL_net_write_desired(ctx->tls.ssl)); + } + else { + /* once connected, we only modify the socket if it is present. + * this avoids adding it for paused transfers. */ + bool want_recv, want_send; + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { + Curl_pollset_set(data, ps, ctx->q.sockfd, + SSL_net_read_desired(ctx->tls.ssl), + SSL_net_write_desired(ctx->tls.ssl)); + } + } +} + +static CURLcode cf_osslq_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { + /* TODO: how to get this? */ + CF_DATA_SAVE(save, cf, data); + *pres1 = 100; + CURL_TRC_CF(data, cf, "query max_conncurrent -> %d", *pres1); + CF_DATA_RESTORE(cf, save); + return CURLE_OK; + } + case CF_QUERY_CONNECT_REPLY_MS: + if(ctx->got_first_byte) { + timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; + } + else + *pres1 = -1; + return CURLE_OK; + case CF_QUERY_TIMER_CONNECT: { + struct curltime *when = pres2; + if(ctx->got_first_byte) + *when = ctx->first_byte_at; + return CURLE_OK; + } + case CF_QUERY_TIMER_APPCONNECT: { + struct curltime *when = pres2; + if(cf->connected) + *when = ctx->handshake_at; + return CURLE_OK; + } + default: + break; + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +struct Curl_cftype Curl_cft_http3 = { + "HTTP/3", + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + 0, + cf_osslq_destroy, + cf_osslq_connect, + cf_osslq_close, + Curl_cf_def_get_host, + cf_osslq_adjust_pollset, + cf_osslq_data_pending, + cf_osslq_send, + cf_osslq_recv, + cf_osslq_data_event, + cf_osslq_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_osslq_query, +}; + +CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_osslq_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL, *udp_cf = NULL; + CURLcode result; + + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_osslq_ctx_clear(ctx); + + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + if(result) + goto out; + + result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC); + if(result) + goto out; + + cf->conn = conn; + udp_cf->conn = cf->conn; + udp_cf->sockindex = cf->sockindex; + cf->next = udp_cf; + +out: + *pcf = (!result)? cf : NULL; + if(result) { + if(udp_cf) + Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE); + Curl_safefree(cf); + Curl_safefree(ctx); + } + return result; +} + +bool Curl_conn_is_osslq(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + + (void)data; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_http3) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; + } + return FALSE; +} + +/* + * Store ngtcp2 version info in this buffer. + */ +void Curl_osslq_ver(char *p, size_t len) +{ + const nghttp3_info *ht3 = nghttp3_version(0); + (void)msnprintf(p, len, "nghttp3/%s", ht3->version_str); +} + +#endif /* USE_OPENSSL_QUIC && USE_NGHTTP3 */ diff --git a/Utilities/cmcurl/lib/vquic/curl_osslq.h b/Utilities/cmcurl/lib/vquic/curl_osslq.h new file mode 100644 index 0000000..0e12d70 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_osslq.h @@ -0,0 +1,51 @@ +#ifndef HEADER_CURL_VQUIC_CURL_OSSLQ_H +#define HEADER_CURL_VQUIC_CURL_OSSLQ_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + +#ifdef HAVE_NETINET_UDP_H +#include <netinet/udp.h> +#endif + +struct Curl_cfilter; + +#include "urldata.h" + +void Curl_osslq_ver(char *p, size_t len); + +CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_osslq(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); +#endif + +#endif /* HEADER_CURL_VQUIC_CURL_OSSLQ_H */ diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.c b/Utilities/cmcurl/lib/vquic/curl_quiche.c index 7123d63..fcb0eb8 100644 --- a/Utilities/cmcurl/lib/vquic/curl_quiche.c +++ b/Utilities/cmcurl/lib/vquic/curl_quiche.c @@ -43,6 +43,7 @@ #include "http1.h" #include "vquic.h" #include "vquic_int.h" +#include "vquic-tls.h" #include "curl_quiche.h" #include "transfer.h" #include "inet_pton.h" @@ -84,31 +85,22 @@ void Curl_quiche_ver(char *p, size_t len) (void)msnprintf(p, len, "quiche/%s", quiche_version()); } -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} - struct cf_quiche_ctx { struct cf_quic_ctx q; struct ssl_peer peer; + struct quic_tls_ctx tls; quiche_conn *qconn; quiche_config *cfg; quiche_h3_conn *h3c; quiche_h3_config *h3config; uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; - SSL_CTX *sslctx; - SSL *ssl; struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ - struct curltime first_byte_at; /* when first byte was recvd */ struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ curl_off_t data_recvd; uint64_t max_idle_ms; /* max idle time for QUIC conn */ BIT(goaway); /* got GOAWAY from server */ - BIT(got_first_byte); /* if first byte was received */ BIT(x509_store_setup); /* if x509 store has been set up */ }; @@ -123,121 +115,25 @@ static void quiche_debug_log(const char *line, void *argp) static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx) { if(ctx) { - vquic_ctx_free(&ctx->q); - if(ctx->qconn) - quiche_conn_free(ctx->qconn); - if(ctx->h3config) - quiche_h3_config_free(ctx->h3config); if(ctx->h3c) quiche_h3_conn_free(ctx->h3c); + if(ctx->h3config) + quiche_h3_config_free(ctx->h3config); + if(ctx->qconn) + quiche_conn_free(ctx->qconn); if(ctx->cfg) quiche_config_free(ctx->cfg); - Curl_bufcp_free(&ctx->stream_bufcp); + /* quiche just freed ctx->tls.ssl */ + ctx->tls.ssl = NULL; + Curl_vquic_tls_cleanup(&ctx->tls); Curl_ssl_peer_cleanup(&ctx->peer); + vquic_ctx_free(&ctx->q); + Curl_bufcp_free(&ctx->stream_bufcp); memset(ctx, 0, sizeof(*ctx)); } } -static CURLcode quic_x509_store_setup(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_quiche_ctx *ctx = cf->ctx; - struct ssl_primary_config *conn_config; - - conn_config = Curl_ssl_cf_get_primary_config(cf); - if(!conn_config) - return CURLE_FAILED_INIT; - - if(!ctx->x509_store_setup) { - if(conn_config->verifypeer) { - const char * const ssl_cafile = conn_config->CAfile; - const char * const ssl_capath = conn_config->CApath; - if(ssl_cafile || ssl_capath) { - SSL_CTX_set_verify(ctx->sslctx, SSL_VERIFY_PEER, NULL); - /* tell OpenSSL where to find CA certificates that are used to verify - the server's certificate. */ - if(!SSL_CTX_load_verify_locations(ctx->sslctx, ssl_cafile, - ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - return CURLE_SSL_CACERT_BADFILE; - } - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); - } -#ifdef CURL_CA_FALLBACK - else { - /* verifying the peer without any CA certificates won't work so - use openssl's built-in default as fallback */ - SSL_CTX_set_default_verify_paths(ctx->sslctx); - } -#endif - } - ctx->x509_store_setup = TRUE; - } - return CURLE_OK; -} - -static CURLcode quic_ssl_setup(struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_quiche_ctx *ctx = cf->ctx; - struct ssl_primary_config *conn_config; - CURLcode result; - - conn_config = Curl_ssl_cf_get_primary_config(cf); - if(!conn_config) - return CURLE_FAILED_INIT; - - result = Curl_ssl_peer_init(&ctx->peer, cf); - if(result) - return result; - - DEBUGASSERT(!ctx->sslctx); - ctx->sslctx = SSL_CTX_new(TLS_method()); - if(!ctx->sslctx) - return CURLE_OUT_OF_MEMORY; - - SSL_CTX_set_alpn_protos(ctx->sslctx, - (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL, - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); - - SSL_CTX_set_default_verify_paths(ctx->sslctx); - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ctx->sslctx, keylog_callback); - } - - if(conn_config->curves && - !SSL_CTX_set1_curves_list(ctx->sslctx, conn_config->curves)) { - failf(data, "failed setting curves list for QUIC: '%s'", - conn_config->curves); - return CURLE_SSL_CIPHER; - } - - ctx->ssl = SSL_new(ctx->sslctx); - if(!ctx->ssl) - return CURLE_QUIC_CONNECT_ERROR; - - SSL_set_app_data(ctx->ssl, cf); - - if(ctx->peer.sni) { - if(!SSL_set_tlsext_host_name(ctx->ssl, ctx->peer.sni)) { - failf(data, "Failed set SNI"); - SSL_free(ctx->ssl); - ctx->ssl = NULL; - return CURLE_QUIC_CONNECT_ERROR; - } - } - - return CURLE_OK; -} - /** * All about the H3 internals of a stream */ @@ -337,8 +233,8 @@ static void drain_stream(struct Curl_cfilter *cf, bits = CURL_CSELECT_IN; if(stream && !stream->send_closed && stream->upload_left) bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; Curl_expire(data, 0, EXPIRE_RUN_NOW); } } @@ -668,7 +564,7 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, return CURLE_OK; } else if(QUICHE_ERR_TLS_FAIL == nread) { - long verify_ok = SSL_get_verify_result(ctx->ssl); + long verify_ok = SSL_get_verify_result(ctx->tls.ssl); if(verify_ok != X509_V_OK) { failf(r->data, "SSL certificate problem: %s", X509_verify_cert_error_string(verify_ok)); @@ -696,7 +592,7 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf, CURLcode result; DEBUGASSERT(ctx->qconn); - result = quic_x509_store_setup(cf, data); + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); if(result) return result; @@ -836,7 +732,7 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, if(stream->reset) { failf(data, "HTTP/3 stream %" PRId64 " reset by server", stream->id); - *err = stream->resp_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + *err = stream->resp_got_header? CURLE_PARTIAL_FILE : CURLE_HTTP3; CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, was reset -> %d", stream->id, *err); } @@ -846,7 +742,7 @@ static ssize_t recv_closed_stream(struct Curl_cfilter *cf, " all response header fields, treated as error", stream->id); /* *err = CURLE_PARTIAL_FILE; */ - *err = CURLE_RECV_ERROR; + *err = CURLE_HTTP3; CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv, closed incomplete" " -> %d", stream->id, *err); } @@ -1078,6 +974,28 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; stream = H3_STREAM_CTX(data); } + else if(stream->closed) { + if(stream->resp_hds_complete) { + /* sending request body on a stream that has been closed by the + * server. If the server has send us a final response, we should + * silently discard the send data. + * This happens for example on redirects where the server, instead + * of reading the full request body just closed the stream after + * sending the 30x response. + * This is sort of a race: had the transfer loop called recv first, + * it would see the response and stop/discard sending on its own- */ + CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" + "on closed stream with response", stream->id); + *err = CURLE_OK; + nwritten = (ssize_t)len; + goto out; + } + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); + *err = CURLE_HTTP3; + nwritten = -1; + goto out; + } else { bool eof = (stream->upload_left >= 0 && (curl_off_t)len >= stream->upload_left); @@ -1095,20 +1013,11 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, nwritten = -1; goto out; } - else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE && - stream->closed && stream->resp_hds_complete) { - /* sending request body on a stream that has been closed by the - * server. If the server has send us a final response, we should - * silently discard the send data. - * This happens for example on redirects where the server, instead - * of reading the full request body just closed the stream after - * sending the 30x response. - * This is sort of a race: had the transfer loop called recv first, - * it would see the response and stop/discard sending on its own- */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] discarding data" - "on closed stream with response", stream->id); - *err = CURLE_OK; - nwritten = (ssize_t)len; + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> invalid stream state", stream->id, len); + *err = CURLE_HTTP3; + nwritten = -1; goto out; } else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) { @@ -1167,16 +1076,19 @@ static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { struct cf_quiche_ctx *ctx = cf->ctx; - bool want_recv = CURL_WANT_RECV(data); - bool want_send = CURL_WANT_SEND(data); + bool want_recv, want_send; + + if(!ctx->qconn) + return; - if(ctx->qconn && (want_recv || want_send)) { + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { struct stream_ctx *stream = H3_STREAM_CTX(data); bool c_exhaust, s_exhaust; c_exhaust = FALSE; /* Have not found any call in quiche that tells us if the connection itself is blocked */ - s_exhaust = stream && stream->id >= 0 && + s_exhaust = want_send && stream && stream->id >= 0 && (stream->quic_flow_blocked || !stream_is_writeable(cf, data)); want_recv = (want_recv || c_exhaust || s_exhaust); want_send = (!s_exhaust && want_send) || @@ -1261,66 +1173,6 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, return result; } -static CURLcode cf_verify_peer(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_quiche_ctx *ctx = cf->ctx; - struct ssl_primary_config *conn_config; - CURLcode result = CURLE_OK; - - conn_config = Curl_ssl_cf_get_primary_config(cf); - if(!conn_config) - return CURLE_FAILED_INIT; - - cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - cf->conn->httpversion = 30; - cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; - - if(conn_config->verifyhost) { - X509 *server_cert; - server_cert = SSL_get_peer_certificate(ctx->ssl); - if(!server_cert) { - result = CURLE_PEER_FAILED_VERIFICATION; - goto out; - } - result = Curl_ossl_verifyhost(data, cf->conn, &ctx->peer, server_cert); - X509_free(server_cert); - if(result) - goto out; - } - else - CURL_TRC_CF(data, cf, "Skipped certificate verification"); - - ctx->h3config = quiche_h3_config_new(); - if(!ctx->h3config) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - /* Create a new HTTP/3 connection on the QUIC connection. */ - ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config); - if(!ctx->h3c) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, ctx->ssl); - -out: - if(result) { - if(ctx->h3config) { - quiche_h3_config_free(ctx->h3config); - ctx->h3config = NULL; - } - if(ctx->h3c) { - quiche_h3_conn_free(ctx->h3c); - ctx->h3c = NULL; - } - } - return result; -} - static CURLcode cf_connect_start(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -1348,6 +1200,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, if(result) return result; + result = Curl_ssl_peer_init(&ctx->peer, cf); + if(result) + return result; + ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION); if(!ctx->cfg) { failf(data, "can't create quiche config"); @@ -1376,9 +1232,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); - DEBUGASSERT(!ctx->ssl); - DEBUGASSERT(!ctx->sslctx); - result = quic_ssl_setup(cf, data); + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + QUICHE_H3_APPLICATION_PROTOCOL, + sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1, + NULL, cf); if(result) return result; @@ -1399,7 +1256,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, (struct sockaddr *)&ctx->q.local_addr, ctx->q.local_addrlen, &sockaddr->sa_addr, sockaddr->addrlen, - ctx->cfg, ctx->ssl, false); + ctx->cfg, ctx->tls.ssl, false); if(!ctx->qconn) { failf(data, "can't create quiche connection"); return CURLE_OUT_OF_MEMORY; @@ -1438,6 +1295,18 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, return CURLE_OK; } +static CURLcode cf_quiche_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); +} + static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool blocking, bool *done) @@ -1489,9 +1358,21 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, 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)); - result = cf_verify_peer(cf, data); + result = cf_quiche_verify_peer(cf, data); if(!result) { CURL_TRC_CF(data, cf, "peer verified"); + ctx->h3config = quiche_h3_config_new(); + if(!ctx->h3config) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + /* Create a new HTTP/3 connection on the QUIC connection. */ + ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config); + if(!ctx->h3c) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } cf->connected = TRUE; cf->conn->alpn = CURL_HTTP_VERSION_3; *done = TRUE; @@ -1564,8 +1445,8 @@ static CURLcode cf_quiche_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); + if(ctx->q.got_first_byte) { + timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at); *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; } else @@ -1573,8 +1454,8 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf, return CURLE_OK; case CF_QUERY_TIMER_CONNECT: { struct curltime *when = pres2; - if(ctx->got_first_byte) - *when = ctx->first_byte_at; + if(ctx->q.got_first_byte) + *when = ctx->q.first_byte_at; return CURLE_OK; } case CF_QUERY_TIMER_APPCONNECT: { diff --git a/Utilities/cmcurl/lib/vquic/vquic-tls.c b/Utilities/cmcurl/lib/vquic/vquic-tls.c new file mode 100644 index 0000000..cc7794e --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/vquic-tls.c @@ -0,0 +1,609 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(ENABLE_QUIC) && \ + (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) + +#ifdef USE_OPENSSL +#include <openssl/err.h> +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <gnutls/crypto.h> +#include <nettle/sha2.h> +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> +#include <wolfssl/quic.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 "vquic-tls.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#ifdef USE_OPENSSL +#define QUIC_CIPHERS \ + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ + "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" +#define QUIC_GROUPS "P-256:X25519:P-384:P-521" +#elif defined(USE_GNUTLS) +#define QUIC_PRIORITY \ + "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ + "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ + "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ + "%DISABLE_TLS13_COMPAT_MODE" +#elif defined(USE_WOLFSSL) +#define QUIC_CIPHERS \ + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ + "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" +#define QUIC_GROUPS "P-256:P-384:P-521" +#endif + + +#ifdef USE_OPENSSL + +static void keylog_callback(const SSL *ssl, const char *line) +{ + (void)ssl; + Curl_tls_keylog_write_line(line); +} + +static CURLcode curl_ossl_init_ctx(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + Curl_vquic_tls_ctx_setup *ctx_setup) +{ + struct ssl_primary_config *conn_config; + CURLcode result = CURLE_FAILED_INIT; + + DEBUGASSERT(!ctx->ssl_ctx); +#ifdef USE_OPENSSL_QUIC + ctx->ssl_ctx = SSL_CTX_new(OSSL_QUIC_client_method()); +#else + ctx->ssl_ctx = SSL_CTX_new(TLS_method()); +#endif + if(!ctx->ssl_ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) { + result = CURLE_FAILED_INIT; + goto out; + } + + if(ctx_setup) { + result = ctx_setup(ctx, cf, data); + if(result) + goto out; + } + + SSL_CTX_set_default_verify_paths(ctx->ssl_ctx); + + { + const char *curves = conn_config->curves ? + conn_config->curves : QUIC_GROUPS; + if(!SSL_CTX_set1_curves_list(ctx->ssl_ctx, curves)) { + failf(data, "failed setting curves list for QUIC: '%s'", curves); + return CURLE_SSL_CIPHER; + } + } + +#ifndef OPENSSL_IS_BORINGSSL + { + const char *ciphers13 = conn_config->cipher_list13 ? + conn_config->cipher_list13 : QUIC_CIPHERS; + if(SSL_CTX_set_ciphersuites(ctx->ssl_ctx, ciphers13) != 1) { + failf(data, "failed setting QUIC cipher suite: %s", ciphers13); + return CURLE_SSL_CIPHER; + } + infof(data, "QUIC cipher selection: %s", ciphers13); + } +#endif + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + SSL_CTX_set_keylog_callback(ctx->ssl_ctx, keylog_callback); + } + + /* OpenSSL always tries to verify the peer, this only says whether it should + * fail to connect if the verification fails, or if it should continue + * anyway. In the latter case the result of the verification is checked with + * SSL_get_verify_result() below. */ + SSL_CTX_set_verify(ctx->ssl_ctx, conn_config->verifypeer ? + SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + /* When a user callback is installed to modify the SSL_CTX, + * we need to do the full initialization before calling it. + * See: #11800 */ + if(!ctx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, ctx->ssl_ctx); + if(result) + goto out; + ctx->x509_store_setup = TRUE; + } + Curl_set_in_callback(data, true); + result = (*data->set.ssl.fsslctx)(data, ctx->ssl_ctx, + data->set.ssl.fsslctxp); + Curl_set_in_callback(data, false); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + goto out; + } + } + result = CURLE_OK; + +out: + if(result && ctx->ssl_ctx) { + SSL_CTX_free(ctx->ssl_ctx); + ctx->ssl_ctx = NULL; + } + return result; +} + +static CURLcode curl_ossl_set_client_cert(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + SSL_CTX *ssl_ctx = ctx->ssl_ctx; + const struct ssl_config_data *ssl_config; + + ssl_config = Curl_ssl_cf_get_config(cf, data); + DEBUGASSERT(ssl_config); + + if(ssl_config->primary.clientcert || + ssl_config->primary.cert_blob || + ssl_config->cert_type) { + return Curl_ossl_set_client_cert( + data, ssl_ctx, ssl_config->primary.clientcert, + ssl_config->primary.cert_blob, ssl_config->cert_type, + ssl_config->key, ssl_config->key_blob, + ssl_config->key_type, ssl_config->key_passwd); + } + + return CURLE_OK; +} + +/** SSL callbacks ***/ + +static CURLcode curl_ossl_init_ssl(struct quic_tls_ctx *ctx, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + void *user_data) +{ + DEBUGASSERT(!ctx->ssl); + ctx->ssl = SSL_new(ctx->ssl_ctx); + + SSL_set_app_data(ctx->ssl, user_data); + SSL_set_connect_state(ctx->ssl); +#ifndef USE_OPENSSL_QUIC + SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); +#endif + + if(alpn) + SSL_set_alpn_protos(ctx->ssl, (const uint8_t *)alpn, (int)alpn_len); + + if(peer->sni) { + if(!SSL_set_tlsext_host_name(ctx->ssl, peer->sni)) { + failf(data, "Failed set SNI"); + SSL_free(ctx->ssl); + ctx->ssl = NULL; + return CURLE_QUIC_CONNECT_ERROR; + } + } + return CURLE_OK; +} + +#elif defined(USE_GNUTLS) +static int keylog_callback(gnutls_session_t session, const char *label, + const gnutls_datum_t *secret) +{ + gnutls_datum_t crandom; + gnutls_datum_t srandom; + + gnutls_session_get_random(session, &crandom, &srandom); + if(crandom.size != 32) { + return -1; + } + + Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); + return 0; +} + +static CURLcode curl_gtls_init_ctx(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + Curl_vquic_tls_ctx_setup *ctx_setup, + void *user_data) +{ + struct ssl_primary_config *conn_config; + CURLcode result; + gnutls_datum_t alpns[5]; + /* this will need some attention when HTTPS proxy over QUIC get fixed */ + long * const pverifyresult = &data->set.ssl.certverifyresult; + int rc; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + + DEBUGASSERT(ctx->gtls == NULL); + ctx->gtls = calloc(1, sizeof(*(ctx->gtls))); + if(!ctx->gtls) + return CURLE_OUT_OF_MEMORY; + + result = gtls_client_init(data, conn_config, &data->set.ssl, + peer, ctx->gtls, pverifyresult); + if(result) + return result; + + gnutls_session_set_ptr(ctx->gtls->session, user_data); + + if(ctx_setup) { + result = ctx_setup(ctx, cf, data); + if(result) + return result; + } + + rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL); + if(rc < 0) { + CURL_TRC_CF(data, cf, "gnutls_priority_set_direct failed: %s\n", + gnutls_strerror(rc)); + return CURLE_QUIC_CONNECT_ERROR; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback); + } + + /* convert the ALPN string from our arguments to a list of strings + * that gnutls wants and will convert internally back to this very + * string for sending to the server. nice. */ + if(alpn) { + size_t i, alen = alpn_len; + unsigned char *s = (unsigned char *)alpn; + unsigned char slen; + for(i = 0; (i < ARRAYSIZE(alpns)) && alen; ++i) { + slen = s[0]; + if(slen >= alen) + return CURLE_FAILED_INIT; + alpns[i].data = s + 1; + alpns[i].size = slen; + s += slen + 1; + alen -= (size_t)slen + 1; + } + if(alen) /* not all alpn chars used, wrong format or too many */ + return CURLE_FAILED_INIT; + if(i) { + gnutls_alpn_set_protocols(ctx->gtls->session, + alpns, (unsigned int)i, + GNUTLS_ALPN_MANDATORY); + } + } + + return CURLE_OK; +} +#elif defined(USE_WOLFSSL) + +#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 curl_wssl_init_ctx(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + Curl_vquic_tls_ctx_setup *ctx_setup) +{ + 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->ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if(!ctx->ssl_ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(ctx_setup) { + result = ctx_setup(ctx, cf, data); + if(result) + goto out; + } + + wolfSSL_CTX_set_default_verify_paths(ctx->ssl_ctx); + + if(wolfSSL_CTX_set_cipher_list(ctx->ssl_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); + goto out; + } + + if(wolfSSL_CTX_set1_groups_list(ctx->ssl_ctx, conn_config->curves ? + conn_config->curves : + (char *)QUIC_GROUPS) != 1) { + failf(data, "wolfSSL failed to set curves"); + 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->ssl_ctx, keylog_callback); +#else + failf(data, "wolfSSL was built without keylog callback"); + 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->ssl_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->ssl_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"); + 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 won't work so + use wolfssl's built-in default as fallback */ + wolfSSL_CTX_set_default_verify_paths(ctx->ssl_ctx); + } +#endif + } + else { + wolfSSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_NONE, NULL); + } + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + Curl_set_in_callback(data, true); + result = (*data->set.ssl.fsslctx)(data, ctx->ssl_ctx, + data->set.ssl.fsslctxp); + Curl_set_in_callback(data, false); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + goto out; + } + } + result = CURLE_OK; + +out: + if(result && ctx->ssl_ctx) { + SSL_CTX_free(ctx->ssl_ctx); + ctx->ssl_ctx = NULL; + } + return result; +} + +/** SSL callbacks ***/ + +static CURLcode curl_wssl_init_ssl(struct quic_tls_ctx *ctx, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + void *user_data) +{ + (void)data; + DEBUGASSERT(!ctx->ssl); + DEBUGASSERT(ctx->ssl_ctx); + ctx->ssl = wolfSSL_new(ctx->ssl_ctx); + + wolfSSL_set_app_data(ctx->ssl, user_data); + wolfSSL_set_connect_state(ctx->ssl); + wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); + + if(alpn) + wolfSSL_set_alpn_protos(ctx->ssl, (const unsigned char *)alpn, + (int)alpn_len); + + if(peer->sni) { + wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME, + peer->sni, (unsigned short)strlen(peer->sni)); + } + + return CURLE_OK; +} +#endif /* defined(USE_WOLFSSL) */ + +CURLcode Curl_vquic_tls_init(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + Curl_vquic_tls_ctx_setup *ctx_setup, + void *user_data) +{ + CURLcode result; + +#ifdef USE_OPENSSL + result = curl_ossl_init_ctx(ctx, cf, data, ctx_setup); + if(result) + return result; + + result = curl_ossl_set_client_cert(ctx, cf, data); + if(result) + return result; + + return curl_ossl_init_ssl(ctx, data, peer, alpn, alpn_len, user_data); +#elif defined(USE_GNUTLS) + (void)result; + return curl_gtls_init_ctx(ctx, cf, data, peer, alpn, alpn_len, + ctx_setup, user_data); +#elif defined(USE_WOLFSSL) + result = curl_wssl_init_ctx(ctx, cf, data, ctx_setup); + if(result) + return result; + + return curl_wssl_init_ssl(ctx, data, peer, alpn, alpn_len, user_data); +#else +#error "no TLS lib in used, should not happen" + return CURLE_FAILED_INIT; +#endif +} + +void Curl_vquic_tls_cleanup(struct quic_tls_ctx *ctx) +{ +#ifdef USE_OPENSSL + if(ctx->ssl) + SSL_free(ctx->ssl); + if(ctx->ssl_ctx) + SSL_CTX_free(ctx->ssl_ctx); +#elif defined(USE_GNUTLS) + if(ctx->gtls) { + if(ctx->gtls->cred) + gnutls_certificate_free_credentials(ctx->gtls->cred); + if(ctx->gtls->session) + gnutls_deinit(ctx->gtls->session); + free(ctx->gtls); + } +#elif defined(USE_WOLFSSL) + if(ctx->ssl) + wolfSSL_free(ctx->ssl); + if(ctx->ssl_ctx) + wolfSSL_CTX_free(ctx->ssl_ctx); +#endif + memset(ctx, 0, sizeof(*ctx)); +} + +CURLcode Curl_vquic_tls_before_recv(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ +#ifdef USE_OPENSSL + if(!ctx->x509_store_setup) { + CURLcode result = Curl_ssl_setup_x509_store(cf, data, ctx->ssl_ctx); + if(result) + return result; + ctx->x509_store_setup = TRUE; + } +#else + (void)ctx; (void)cf; (void)data; +#endif + return CURLE_OK; +} + +CURLcode Curl_vquic_tls_verify_peer(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer) +{ + struct ssl_primary_config *conn_config; + CURLcode result = CURLE_OK; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + + if(conn_config->verifyhost) { +#ifdef USE_OPENSSL + X509 *server_cert; + server_cert = SSL_get1_peer_certificate(ctx->ssl); + if(!server_cert) { + return CURLE_PEER_FAILED_VERIFICATION; + } + result = Curl_ossl_verifyhost(data, cf->conn, peer, server_cert); + X509_free(server_cert); + if(result) + return result; +#elif defined(USE_GNUTLS) + result = Curl_gtls_verifyserver(data, ctx->gtls->session, + conn_config, &data->set.ssl, peer, + data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + if(result) + return result; +#elif defined(USE_WOLFSSL) + if(!peer->sni || + wolfSSL_check_domain_name(ctx->ssl, peer->sni) == SSL_FAILURE) + return CURLE_PEER_FAILED_VERIFICATION; +#endif + infof(data, "Verified certificate just fine"); + } + else + infof(data, "Skipped certificate verification"); +#ifdef USE_OPENSSL + if(data->set.ssl.certinfo) + /* asked to gather certificate info */ + (void)Curl_ossl_certchain(data, ctx->ssl); +#endif + return result; +} + + +#endif /* !ENABLE_QUIC && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ diff --git a/Utilities/cmcurl/lib/vquic/vquic-tls.h b/Utilities/cmcurl/lib/vquic/vquic-tls.h new file mode 100644 index 0000000..9c0dfd8 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/vquic-tls.h @@ -0,0 +1,98 @@ +#ifndef HEADER_CURL_VQUIC_TLS_H +#define HEADER_CURL_VQUIC_TLS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "bufq.h" + +#if defined(ENABLE_QUIC) && \ + (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) + +struct quic_tls_ctx { +#ifdef USE_OPENSSL + SSL_CTX *ssl_ctx; + SSL *ssl; +#elif defined(USE_GNUTLS) + struct gtls_instance *gtls; +#elif defined(USE_WOLFSSL) + WOLFSSL_CTX *ssl_ctx; + WOLFSSL *ssl; +#endif + BIT(x509_store_setup); /* if x509 store has been set up */ +}; + +/** + * Callback passed to `Curl_vquic_tls_init()` that can + * do early initializations on the not otherwise configured TLS + * instances created. This varies by TLS backend: + * - openssl/wolfssl: SSL_CTX* has just been created + * - gnutls: gtls_client_init() has run + */ +typedef CURLcode Curl_vquic_tls_ctx_setup(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * Initialize the QUIC TLS instances based of the SSL configurations + * for the connection filter, transfer and peer. + * @param ctx the TLS context to initialize + * @param cf the connection filter involved + * @param data the transfer involved + * @param peer the peer that will be connected to + * @param alpn the ALPN string in protocol format ((len+bytes+)+), + * may be NULL + * @param alpn_len the overall number of bytes in `alpn` + * @param ctx_setup optional callback for very early TLS config + * @param user_data optional pointer to set in TLS application context + */ +CURLcode Curl_vquic_tls_init(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + Curl_vquic_tls_ctx_setup *ctx_setup, + void *user_data); + +/** + * Cleanup all data that has been initialized. + */ +void Curl_vquic_tls_cleanup(struct quic_tls_ctx *ctx); + +CURLcode Curl_vquic_tls_before_recv(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * After the QUIC basic handshake has been, verify that the peer + * (and its certificate) fulfill our requirements. + */ +CURLcode Curl_vquic_tls_verify_peer(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer); + +#endif /* !ENABLE_QUIC && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ + +#endif /* HEADER_CURL_VQUIC_TLS_H */ diff --git a/Utilities/cmcurl/lib/vquic/vquic.c b/Utilities/cmcurl/lib/vquic/vquic.c index 523b807..612d25b 100644 --- a/Utilities/cmcurl/lib/vquic/vquic.c +++ b/Utilities/cmcurl/lib/vquic/vquic.c @@ -46,6 +46,7 @@ #include "curl_trc.h" #include "curl_msh3.h" #include "curl_ngtcp2.h" +#include "curl_osslq.h" #include "curl_quiche.h" #include "rand.h" #include "vquic.h" @@ -74,6 +75,8 @@ void Curl_quic_ver(char *p, size_t len) { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) Curl_ngtcp2_ver(p, len); +#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + Curl_osslq_ver(p, len); #elif defined(USE_QUICHE) Curl_quiche_ver(p, len); #elif defined(USE_MSH3) @@ -179,7 +182,7 @@ static CURLcode do_sendmsg(struct Curl_cfilter *cf, qctx->no_gso = TRUE; return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); } - /* FALLTHROUGH */ + FALLTHROUGH(); default: failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO); return CURLE_SEND_ERROR; @@ -543,8 +546,13 @@ CURLcode vquic_recv_packets(struct Curl_cfilter *cf, #else result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); #endif - if(!result) + if(!result) { + if(!qctx->got_first_byte) { + qctx->got_first_byte = TRUE; + qctx->first_byte_at = qctx->last_op; + } qctx->last_io = qctx->last_op; + } return result; } @@ -603,6 +611,8 @@ CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, DEBUGASSERT(transport == TRNSPRT_QUIC); #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) return Curl_cf_ngtcp2_create(pcf, data, conn, ai); +#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + return Curl_cf_osslq_create(pcf, data, conn, ai); #elif defined(USE_QUICHE) return Curl_cf_quiche_create(pcf, data, conn, ai); #elif defined(USE_MSH3) @@ -622,6 +632,8 @@ bool Curl_conn_is_http3(const struct Curl_easy *data, { #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) return Curl_conn_is_ngtcp2(data, conn, sockindex); +#elif defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + return Curl_conn_is_osslq(data, conn, sockindex); #elif defined(USE_QUICHE) return Curl_conn_is_quiche(data, conn, sockindex); #elif defined(USE_MSH3) diff --git a/Utilities/cmcurl/lib/vquic/vquic_int.h b/Utilities/cmcurl/lib/vquic/vquic_int.h index a820f39..c218a94 100644 --- a/Utilities/cmcurl/lib/vquic/vquic_int.h +++ b/Utilities/cmcurl/lib/vquic/vquic_int.h @@ -40,6 +40,7 @@ struct cf_quic_ctx { socklen_t local_addrlen; /* length of local address */ struct bufq sendbuf; /* buffer for sending one or more packets */ + struct curltime first_byte_at; /* when first byte was recvd */ struct curltime last_op; /* last (attempted) send/recv operation */ struct curltime last_io; /* last successful socket IO */ size_t gsolen; /* length of individual packets in send buf */ @@ -48,7 +49,8 @@ struct cf_quic_ctx { #ifdef DEBUGBUILD int wblock_percent; /* percent of writes doing EAGAIN */ #endif - bool no_gso; /* do not use gso on sending */ + BIT(got_first_byte); /* if first byte was received */ + BIT(no_gso); /* do not use gso on sending */ }; CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx); diff --git a/Utilities/cmcurl/lib/vssh/libssh.c b/Utilities/cmcurl/lib/vssh/libssh.c index 97143c4..c6dc63a 100644 --- a/Utilities/cmcurl/lib/vssh/libssh.c +++ b/Utilities/cmcurl/lib/vssh/libssh.c @@ -31,6 +31,8 @@ #include <limits.h> +/* in 0.10.0 or later, ignore deprecated warnings */ +#define SSH_SUPPRESS_DEPRECATED #include <libssh/libssh.h> #include <libssh/sftp.h> @@ -89,14 +91,6 @@ #include "curl_memory.h" #include "memdebug.h" -/* in 0.10.0 or later, ignore deprecated warnings */ -#if defined(__GNUC__) && \ - (LIBSSH_VERSION_MINOR >= 10) || \ - (LIBSSH_VERSION_MAJOR > 0) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - /* A recent macro provided by libssh. Or make our own. */ #ifndef SSH_STRING_FREE_CHAR #define SSH_STRING_FREE_CHAR(x) \ @@ -167,7 +161,7 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* domore_getsock */ myssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -194,7 +188,7 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* domore_getsock */ myssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -445,11 +439,8 @@ static int myssh_is_known(struct Curl_easy *data) keymatch = CURLKHMATCH_OK; break; case SSH_KNOWN_HOSTS_OTHER: - /* fallthrough */ case SSH_KNOWN_HOSTS_NOT_FOUND: - /* fallthrough */ case SSH_KNOWN_HOSTS_UNKNOWN: - /* fallthrough */ case SSH_KNOWN_HOSTS_ERROR: keymatch = CURLKHMATCH_MISSING; break; @@ -465,7 +456,6 @@ static int myssh_is_known(struct Curl_easy *data) keymatch = CURLKHMATCH_OK; break; case SSH_SERVER_FILE_NOT_FOUND: - /* fallthrough */ case SSH_SERVER_NOT_KNOWN: keymatch = CURLKHMATCH_MISSING; break; @@ -629,7 +619,7 @@ restart: if(rc < 0) return SSH_ERROR; - /* FALLTHROUGH */ + FALLTHROUGH(); case 1: sshc->kbd_state = 1; @@ -704,7 +694,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) ssh_set_blocking(sshc->ssh_session, 0); state(data, SSH_S_STARTUP); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_S_STARTUP: rc = ssh_connect(sshc->ssh_session); @@ -719,7 +709,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) state(data, SSH_HOSTKEY); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_HOSTKEY: rc = myssh_is_known(data); @@ -729,7 +719,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } state(data, SSH_AUTHLIST); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_AUTHLIST:{ sshc->authed = FALSE; @@ -910,7 +900,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } state(data, SSH_AUTH_PASS); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_AUTH_PASS: rc = ssh_userauth_password(sshc->ssh_session, NULL, conn->passwd); @@ -973,7 +963,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } state(data, SSH_SFTP_REALPATH); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SFTP_REALPATH: /* * Get the "home" directory @@ -1161,22 +1151,22 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } else if(statvfs) { #ifdef _MSC_VER - #define LIBSSH_VFS_SIZE_MASK "I64u" + #define CURL_LIBSSH_VFS_SIZE_MASK "I64u" #else - #define LIBSSH_VFS_SIZE_MASK PRIu64 + #define CURL_LIBSSH_VFS_SIZE_MASK PRIu64 #endif char *tmp = aprintf("statvfs:\n" - "f_bsize: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_frsize: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_blocks: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_bfree: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_bavail: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_files: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_ffree: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_favail: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_fsid: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_flag: %" LIBSSH_VFS_SIZE_MASK "\n" - "f_namemax: %" LIBSSH_VFS_SIZE_MASK "\n", + "f_bsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_frsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_blocks: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_bfree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_bavail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_files: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_ffree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_favail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_fsid: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_flag: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_namemax: %" CURL_LIBSSH_VFS_SIZE_MASK "\n", statvfs->f_bsize, statvfs->f_frsize, statvfs->f_blocks, statvfs->f_bfree, statvfs->f_bavail, statvfs->f_files, @@ -1318,13 +1308,14 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) } /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, + data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); passed += actuallyread; @@ -1370,7 +1361,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh sftp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; /* since we don't really wait for anything at this point, we want the state machine to move on as soon as possible so we set a very short @@ -1560,7 +1551,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) sshc->readdir_longentry = NULL; state(data, SSH_SFTP_READDIR_BOTTOM); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SFTP_READDIR_BOTTOM: if(Curl_dyn_addn(&sshc->readdir_buf, "\n", 1)) result = CURLE_OUT_OF_MEMORY; @@ -1740,7 +1731,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; if(result) { /* this should never occur; the close state should be entered @@ -1868,7 +1859,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh scp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; state(data, SSH_STOP); @@ -1884,7 +1875,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; } state(data, SSH_SCP_DOWNLOAD); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SCP_DOWNLOAD:{ curl_off_t bytecount; @@ -1908,7 +1899,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; state(data, SSH_STOP); break; @@ -1948,7 +1939,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) ssh_set_blocking(sshc->ssh_session, 0); state(data, SSH_SESSION_DISCONNECT); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SESSION_DISCONNECT: /* during weird times when we've been prematurely aborted, the channel @@ -1971,7 +1962,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) data->state.most_recent_ftp_entrypath = NULL; state(data, SSH_SESSION_FREE); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_SESSION_FREE: if(sshc->ssh_session) { ssh_free(sshc->ssh_session); @@ -2022,7 +2013,6 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ sshc->nextstate = SSH_NO_STATE; @@ -2613,7 +2603,7 @@ static ssize_t sftp_recv(struct Curl_easy *data, int sockindex, return -1; } - /* FALLTHROUGH */ + FALLTHROUGH(); case 1: conn->proto.sshc.sftp_recv_state = 1; @@ -2957,10 +2947,4 @@ void Curl_ssh_version(char *buffer, size_t buflen) (void)msnprintf(buffer, buflen, "libssh/%s", ssh_version(0)); } -#if defined(__GNUC__) && \ - (LIBSSH_VERSION_MINOR >= 10) || \ - (LIBSSH_VERSION_MAJOR > 0) -#pragma GCC diagnostic pop -#endif - #endif /* USE_LIBSSH */ diff --git a/Utilities/cmcurl/lib/vssh/libssh2.c b/Utilities/cmcurl/lib/vssh/libssh2.c index 11f5f4f..e9dfef9 100644 --- a/Utilities/cmcurl/lib/vssh/libssh2.c +++ b/Utilities/cmcurl/lib/vssh/libssh2.c @@ -138,7 +138,7 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* domore_getsock */ ssh_getsock, /* perform_getsock */ scp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ PORT_SSH, /* defport */ @@ -167,7 +167,7 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* domore_getsock */ ssh_getsock, /* perform_getsock */ sftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ssh_attach, /* attach */ PORT_SSH, /* defport */ @@ -589,10 +589,9 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) switch(rc) { default: /* unknown return codes will equal reject */ - /* FALLTHROUGH */ case CURLKHSTAT_REJECT: state(data, SSH_SESSION_FREE); - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLKHSTAT_DEFER: /* DEFER means bail out but keep the SSH_HOSTKEY state */ result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; @@ -601,9 +600,8 @@ static CURLcode ssh_knownhost(struct Curl_easy *data) /* remove old host+key that doesn't match */ if(host) libssh2_knownhost_del(sshc->kh, host); - /* FALLTHROUGH */ + FALLTHROUGH(); case CURLKHSTAT_FINE: - /* FALLTHROUGH */ case CURLKHSTAT_FINE_ADD_TO_FILE: /* proceed */ if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) { @@ -997,7 +995,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) } state(data, SSH_S_STARTUP); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_S_STARTUP: rc = session_startup(sshc->ssh_session, sock); @@ -1016,7 +1014,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) state(data, SSH_HOSTKEY); - /* FALLTHROUGH */ + FALLTHROUGH(); case SSH_HOSTKEY: /* * Before we authenticate we should check the hostkey's fingerprint @@ -1961,22 +1959,22 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) } else if(rc == 0) { #ifdef _MSC_VER - #define LIBSSH2_VFS_SIZE_MASK "I64u" + #define CURL_LIBSSH2_VFS_SIZE_MASK "I64u" #else - #define LIBSSH2_VFS_SIZE_MASK "llu" + #define CURL_LIBSSH2_VFS_SIZE_MASK "llu" #endif char *tmp = aprintf("statvfs:\n" - "f_bsize: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_frsize: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_blocks: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_bfree: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_bavail: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_files: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_ffree: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_favail: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_fsid: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_flag: %" LIBSSH2_VFS_SIZE_MASK "\n" - "f_namemax: %" LIBSSH2_VFS_SIZE_MASK "\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, @@ -2160,14 +2158,15 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) } /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread; Curl_set_in_callback(data, true); - actuallyread = data->state.fread_func(data->state.buffer, 1, + actuallyread = data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); Curl_set_in_callback(data, false); @@ -2213,7 +2212,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh2 sftp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; /* since we don't really wait for anything at this point, we want the state machine to move on as soon as possible so we set a very short @@ -2602,7 +2601,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh2 recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; if(result) { /* this should never occur; the close state should be entered @@ -2757,7 +2756,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh2 scp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; state(data, SSH_STOP); } @@ -2819,7 +2818,7 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh2 recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; if(result) { state(data, SSH_SCP_CHANNEL_FREE); @@ -3024,7 +3023,6 @@ static CURLcode ssh_statemach_act(struct Curl_easy *data, bool *block) break; case SSH_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ sshc->nextstate = SSH_NO_STATE; @@ -3293,6 +3291,27 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) #ifndef CURL_DISABLE_PROXY if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) { /* + Setup libssh2 callbacks to make it read/write TLS from the socket. + + ssize_t + recvcb(libssh2_socket_t sock, void *buffer, size_t length, + int flags, void **abstract); + + ssize_t + sendcb(libssh2_socket_t sock, const void *buffer, size_t length, + int flags, void **abstract); + + */ +#if LIBSSH2_VERSION_NUM >= 0x010b01 + infof(data, "Uses HTTPS proxy"); + libssh2_session_callback_set2(sshc->ssh_session, + LIBSSH2_CALLBACK_RECV, + (libssh2_cb_generic *)ssh_tls_recv); + libssh2_session_callback_set2(sshc->ssh_session, + LIBSSH2_CALLBACK_SEND, + (libssh2_cb_generic *)ssh_tls_send); +#else + /* * This crazy union dance is here to avoid assigning a void pointer a * function pointer as it is invalid C. The problem is of course that * libssh2 has such an API... @@ -3312,22 +3331,11 @@ static CURLcode ssh_connect(struct Curl_easy *data, bool *done) sshsend.sendptr = ssh_tls_send; infof(data, "Uses HTTPS proxy"); - /* - Setup libssh2 callbacks to make it read/write TLS from the socket. - - ssize_t - recvcb(libssh2_socket_t sock, void *buffer, size_t length, - int flags, void **abstract); - - ssize_t - sendcb(libssh2_socket_t sock, const void *buffer, size_t length, - int flags, void **abstract); - - */ libssh2_session_callback_set(sshc->ssh_session, LIBSSH2_CALLBACK_RECV, sshrecv.recvp); libssh2_session_callback_set(sshc->ssh_session, LIBSSH2_CALLBACK_SEND, sshsend.sendp); +#endif /* Store the underlying TLS recv/send function pointers to be used when reading from the proxy */ diff --git a/Utilities/cmcurl/lib/vssh/wolfssh.c b/Utilities/cmcurl/lib/vssh/wolfssh.c index 4da7e9d..7396791c 100644 --- a/Utilities/cmcurl/lib/vssh/wolfssh.c +++ b/Utilities/cmcurl/lib/vssh/wolfssh.c @@ -42,6 +42,7 @@ #include "select.h" #include "multiif.h" #include "warnless.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -92,7 +93,7 @@ const struct Curl_handler Curl_handler_scp = { ZERO_NULL, /* domore_getsock */ wssh_getsock, /* perform_getsock */ wscp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -121,7 +122,7 @@ const struct Curl_handler Curl_handler_sftp = { ZERO_NULL, /* domore_getsock */ wssh_getsock, /* perform_getsock */ wsftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ PORT_SSH, /* defport */ @@ -512,15 +513,9 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) return CURLE_OK; } else if(name && (rc == WS_SUCCESS)) { - sshc->homedir = malloc(name->fSz + 1); - if(!sshc->homedir) { + sshc->homedir = Curl_memdup0(name->fName, name->fSz); + if(!sshc->homedir) sshc->actualcode = CURLE_OUT_OF_MEMORY; - } - else { - memcpy(sshc->homedir, name->fName, name->fSz); - sshc->homedir[name->fSz] = 0; - infof(data, "wolfssh SFTP realpath succeeded"); - } wolfSSH_SFTPNAME_list_free(name); state(data, SSH_STOP); return CURLE_OK; @@ -646,14 +641,15 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) } /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ do { + char scratch[4*1024]; size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); size_t actuallyread; Curl_set_in_callback(data, true); - actuallyread = data->state.fread_func(data->state.buffer, 1, + actuallyread = data->state.fread_func(scratch, 1, readthisamountnow, data->state.in); Curl_set_in_callback(data, false); @@ -699,7 +695,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _sending_ function even when the socket turns out readable as the underlying libssh2 sftp send function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; + data->state.select_bits = CURL_CSELECT_OUT; /* since we don't really wait for anything at this point, we want the state machine to move on as soon as possible so we set a very short @@ -795,7 +791,7 @@ static CURLcode wssh_statemach_act(struct Curl_easy *data, bool *block) /* we want to use the _receiving_ function even when the socket turns out writableable as the underlying libssh2 recv function will deal with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; + data->state.select_bits = CURL_CSELECT_IN; if(result) { /* this should never occur; the close state should be entered diff --git a/Utilities/cmcurl/lib/vtls/bearssl.c b/Utilities/cmcurl/lib/vtls/bearssl.c index a6566f4..58394ba 100644 --- a/Utilities/cmcurl/lib/vtls/bearssl.c +++ b/Utilities/cmcurl/lib/vtls/bearssl.c @@ -509,7 +509,6 @@ static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data, { uint16_t selected_ciphers[NUM_OF_CIPHERS]; size_t selected_count = 0; - char cipher_name[CIPHER_NAME_BUF_LEN]; const char *cipher_start = ciphers; const char *cipher_end; size_t i, j; @@ -518,41 +517,48 @@ static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data, return CURLE_SSL_CIPHER; while(true) { + const char *cipher; + size_t clen; + /* Extract the next cipher name from the ciphers string */ while(is_separator(*cipher_start)) ++cipher_start; - if(*cipher_start == '\0') + if(!*cipher_start) break; cipher_end = cipher_start; - while(*cipher_end != '\0' && !is_separator(*cipher_end)) + while(*cipher_end && !is_separator(*cipher_end)) ++cipher_end; - j = cipher_end - cipher_start < CIPHER_NAME_BUF_LEN - 1 ? - cipher_end - cipher_start : CIPHER_NAME_BUF_LEN - 1; - strncpy(cipher_name, cipher_start, j); - cipher_name[j] = '\0'; + + clen = cipher_end - cipher_start; + cipher = cipher_start; + cipher_start = cipher_end; /* Lookup the cipher name in the table of available ciphers. If the cipher name starts with "TLS_" we do the lookup by IANA name. Otherwise, we try to match cipher name by an (OpenSSL) alias. */ - if(strncasecompare(cipher_name, "TLS_", 4)) { + if(strncasecompare(cipher, "TLS_", 4)) { for(i = 0; i < NUM_OF_CIPHERS && - !strcasecompare(cipher_name, ciphertable[i].name); ++i); + (strlen(ciphertable[i].name) == clen) && + !strncasecompare(cipher, ciphertable[i].name, clen); ++i); } else { for(i = 0; i < NUM_OF_CIPHERS && - !strcasecompare(cipher_name, ciphertable[i].alias_name); ++i); + (strlen(ciphertable[i].alias_name) == clen) && + !strncasecompare(cipher, ciphertable[i].alias_name, clen); ++i); } if(i == NUM_OF_CIPHERS) { - infof(data, "BearSSL: unknown cipher in list: %s", cipher_name); + infof(data, "BearSSL: unknown cipher in list: %.*s", + (int)clen, cipher); continue; } /* No duplicates allowed */ for(j = 0; j < selected_count && - selected_ciphers[j] != ciphertable[i].num; j++); + selected_ciphers[j] != ciphertable[i].num; j++); if(j < selected_count) { - infof(data, "BearSSL: duplicate cipher in list: %s", cipher_name); + infof(data, "BearSSL: duplicate cipher in list: %.*s", + (int)clen, cipher); continue; } diff --git a/Utilities/cmcurl/lib/vtls/gtls.c b/Utilities/cmcurl/lib/vtls/gtls.c index 4e337f5..b95c5be 100644 --- a/Utilities/cmcurl/lib/vtls/gtls.c +++ b/Utilities/cmcurl/lib/vtls/gtls.c @@ -584,13 +584,9 @@ CURLcode gtls_client_init(struct Curl_easy *data, /* Only add SRP to the cipher list if SRP is requested. Otherwise * GnuTLS will disable TLS 1.3 support. */ if(config->username) { - size_t len = strlen(prioritylist); - - char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1); + char *prioritysrp = aprintf("%s:" GNUTLS_SRP, prioritylist); if(!prioritysrp) return CURLE_OUT_OF_MEMORY; - strcpy(prioritysrp, prioritylist); - strcpy(prioritysrp + len, ":" GNUTLS_SRP); rc = gnutls_priority_set_direct(gtls->session, prioritysrp, &err); free(prioritysrp); @@ -822,16 +818,17 @@ Curl_gtls_verifyserver(struct Curl_easy *data, char certname[65] = ""; /* limited to 64 chars by ASN.1 */ size_t size; time_t certclock; - const char *ptr; int rc; CURLcode result = CURLE_OK; #ifndef CURL_DISABLE_VERBOSE_STRINGS + const char *ptr; unsigned int algo; unsigned int bits; gnutls_protocol_t version = gnutls_protocol_get_version(session); #endif long * const certverifyresult = &ssl_config->certverifyresult; +#ifndef CURL_DISABLE_VERBOSE_STRINGS /* the name of the cipher suite used, e.g. ECDHE_RSA_AES_256_GCM_SHA384. */ ptr = gnutls_cipher_suite_get_name(gnutls_kx_get(session), gnutls_cipher_get(session), @@ -839,6 +836,7 @@ Curl_gtls_verifyserver(struct Curl_easy *data, infof(data, "SSL connection using %s / %s", gnutls_protocol_get_name(version), ptr); +#endif /* This function will return the peer's raw certificate (chain) as sent by the peer. These certificates are in raw format (DER encoded for diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.c b/Utilities/cmcurl/lib/vtls/mbedtls.c index 38f7de7..7d70de5 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls.c +++ b/Utilities/cmcurl/lib/vtls/mbedtls.c @@ -36,6 +36,13 @@ /* Define this to enable lots of debugging for mbedTLS */ /* #define MBEDTLS_DEBUG */ +#ifdef __GNUC__ +#pragma GCC diagnostic push +/* mbedTLS (as of v3.5.1) has a duplicate function declaration + in its public headers. Disable the warning that detects it. */ +#pragma GCC diagnostic ignored "-Wredundant-decls" +#endif + #include <mbedtls/version.h> #if MBEDTLS_VERSION_NUMBER >= 0x02040000 #include <mbedtls/net_sockets.h> @@ -56,6 +63,10 @@ # endif #endif +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + #include "urldata.h" #include "sendf.h" #include "inet_pton.h" @@ -67,6 +78,7 @@ #include "select.h" #include "multiif.h" #include "mbedtls_threadlock.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -153,7 +165,6 @@ static void mbed_debug(void *context, int level, const char *f_name, infof(data, "%s", line); (void) level; } -#else #endif static int mbedtls_bio_cf_write(void *bio, @@ -165,6 +176,9 @@ static int mbedtls_bio_cf_write(void *bio, CURLcode result; DEBUGASSERT(data); + if(!data) + return 0; + nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, &result); CURL_TRC_CF(data, cf, "mbedtls_bio_cf_out_write(len=%zu) -> %zd, err=%d", blen, nwritten, result); @@ -182,6 +196,8 @@ static int mbedtls_bio_cf_read(void *bio, unsigned char *buf, size_t blen) CURLcode result; DEBUGASSERT(data); + if(!data) + return 0; /* OpenSSL catches this case, so should we. */ if(!buf) return 0; @@ -367,11 +383,10 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null terminated even when provided the exact length, forcing us to waste extra memory here. */ - unsigned char *newblob = malloc(ca_info_blob->len + 1); + unsigned char *newblob = Curl_memdup0(ca_info_blob->data, + ca_info_blob->len); if(!newblob) return CURLE_OUT_OF_MEMORY; - memcpy(newblob, ca_info_blob->data, ca_info_blob->len); - newblob[ca_info_blob->len] = 0; /* null terminate */ ret = mbedtls_x509_crt_parse(&backend->cacert, newblob, ca_info_blob->len + 1); free(newblob); @@ -441,11 +456,10 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null terminated even when provided the exact length, forcing us to waste extra memory here. */ - unsigned char *newblob = malloc(ssl_cert_blob->len + 1); + unsigned char *newblob = Curl_memdup0(ssl_cert_blob->data, + ssl_cert_blob->len); if(!newblob) return CURLE_OUT_OF_MEMORY; - memcpy(newblob, ssl_cert_blob->data, ssl_cert_blob->len); - newblob[ssl_cert_blob->len] = 0; /* null terminate */ ret = mbedtls_x509_crt_parse(&backend->clicert, newblob, ssl_cert_blob->len + 1); free(newblob); @@ -1207,6 +1221,9 @@ static int mbedtls_init(void) static void mbedtls_cleanup(void) { +#ifdef THREADING_SUPPORT + mbedtls_entropy_free(&ts_entropy); +#endif /* THREADING_SUPPORT */ (void)Curl_mbedtlsthreadlock_thread_cleanup(); } diff --git a/Utilities/cmcurl/lib/vtls/openssl.c b/Utilities/cmcurl/lib/vtls/openssl.c index 4bcb340..ef5883d 100644 --- a/Utilities/cmcurl/lib/vtls/openssl.c +++ b/Utilities/cmcurl/lib/vtls/openssl.c @@ -962,8 +962,9 @@ static char *ossl_strerror(unsigned long error, char *buf, size_t size) #endif if(!*buf) { - strncpy(buf, (error ? "Unknown error" : "No error"), size); - buf[size - 1] = '\0'; + const char *msg = error ? "Unknown error" : "No error"; + if(strlen(msg) < size) + strcpy(buf, msg); } return buf; @@ -1095,6 +1096,7 @@ static int ssl_ui_reader(UI *ui, UI_STRING *uis) UI_set_result(ui, uis, password); return 1; } + FALLTHROUGH(); default: break; } @@ -1113,6 +1115,7 @@ static int ssl_ui_writer(UI *ui, UI_STRING *uis) (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) { return 1; } + FALLTHROUGH(); default: break; } @@ -1530,7 +1533,7 @@ fail: case SSL_FILETYPE_PEM: if(cert_done) break; - /* FALLTHROUGH */ + FALLTHROUGH(); case SSL_FILETYPE_ASN1: cert_use_result = key_blob ? SSL_CTX_use_PrivateKey_blob(ctx, key_blob, file_type, key_passwd) : @@ -1760,7 +1763,7 @@ static int ossl_init(void) static void ossl_cleanup(void) { #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !defined(LIBRESSL_VERSION_NUMBER) + (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x2070000fL) /* OpenSSL 1.1 deprecates all these cleanup functions and turns them into no-ops in OpenSSL 1.0 compatibility mode */ #else @@ -2864,7 +2867,7 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, failf(data, OSSL_PACKAGE " was built without TLS 1.3 support"); return CURLE_NOT_BUILT_IN; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_2: #if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_1; @@ -2872,7 +2875,7 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, failf(data, OSSL_PACKAGE " was built without TLS 1.2 support"); return CURLE_NOT_BUILT_IN; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_1: #if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1; @@ -2880,7 +2883,7 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, failf(data, OSSL_PACKAGE " was built without TLS 1.1 support"); return CURLE_NOT_BUILT_IN; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_TLSv1_0: case CURL_SSLVERSION_TLSv1: break; @@ -2891,12 +2894,12 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, #if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_1; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_MAX_TLSv1_1: #if OPENSSL_VERSION_NUMBER >= 0x1000100FL *ctx_options |= SSL_OP_NO_TLSv1_2; #endif - /* FALLTHROUGH */ + FALLTHROUGH(); case CURL_SSLVERSION_MAX_TLSv1_2: #ifdef TLS1_3_VERSION *ctx_options |= SSL_OP_NO_TLSv1_3; @@ -3190,6 +3193,8 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf, bool imported_native_ca = false; bool imported_ca_info_blob = false; + CURL_TRC_CF(data, cf, "populate_x509_store, path=%s, blob=%d", + ssl_cafile? ssl_cafile : "none", !!ca_info_blob); if(!store) return CURLE_OUT_OF_MEMORY; @@ -4362,6 +4367,20 @@ static CURLcode servercert(struct Curl_cfilter *cf, /* don't do this after Session ID reuse */ result = verifystatus(cf, data); if(result) { + /* when verifystatus failed, remove the session id from the cache again + if present */ + if(!Curl_ssl_cf_is_proxy(cf)) { + void *old_ssl_sessionid = NULL; + bool incache; + Curl_ssl_sessionid_lock(data); + incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL)); + if(incache) { + infof(data, "Remove session ID again from cache"); + Curl_ssl_delsessionid(data, old_ssl_sessionid); + } + Curl_ssl_sessionid_unlock(data); + } + X509_free(backend->server_cert); backend->server_cert = NULL; return result; @@ -4608,10 +4627,10 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); else if(sockerr) Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); - else { - strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); - error_buffer[sizeof(error_buffer) - 1] = '\0'; - } + else + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); + failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d", error_buffer, sockerr); *curlcode = CURLE_SEND_ERROR; @@ -4704,10 +4723,9 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); else if(sockerr && err == SSL_ERROR_SYSCALL) Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); - else { - strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); - error_buffer[sizeof(error_buffer) - 1] = '\0'; - } + else + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d", error_buffer, sockerr); *curlcode = CURLE_RECV_ERROR; diff --git a/Utilities/cmcurl/lib/vtls/rustls.c b/Utilities/cmcurl/lib/vtls/rustls.c index 8751fd9..d589709 100644 --- a/Utilities/cmcurl/lib/vtls/rustls.c +++ b/Utilities/cmcurl/lib/vtls/rustls.c @@ -156,7 +156,7 @@ static ssize_t tls_recv_more(struct Curl_cfilter *cf, size_t errorlen; rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); failf(data, "rustls_connection_process_new_packets: %.*s", - errorlen, errorbuf); + (int)errorlen, errorbuf); *err = map_error(rresult); return -1; } @@ -225,7 +225,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char errorbuf[255]; size_t errorlen; rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_connection_read: %.*s", errorlen, errorbuf); + failf(data, "rustls_connection_read: %.*s", (int)errorlen, errorbuf); *err = CURLE_READ_ERROR; nread = -1; goto out; @@ -301,7 +301,7 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, &plainwritten); if(rresult != RUSTLS_RESULT_OK) { rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_connection_write: %.*s", errorlen, errorbuf); + failf(data, "rustls_connection_write: %.*s", (int)errorlen, errorbuf); *err = CURLE_WRITE_ERROR; return -1; } @@ -459,7 +459,7 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, } if(result != RUSTLS_RESULT_OK) { rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_client_connection_new: %.*s", errorlen, errorbuf); + failf(data, "rustls_client_connection_new: %.*s", (int)errorlen, errorbuf); return CURLE_COULDNT_CONNECT; } rustls_connection_set_userdata(rconn, backend); @@ -563,8 +563,8 @@ cr_connect_common(struct Curl_cfilter *cf, return CURLE_SSL_CONNECT_ERROR; } if(blocking && 0 == what) { - failf(data, "rustls connection timeout after %d ms", - socket_check_timeout); + failf(data, "rustls connection timeout after %" + CURL_FORMAT_TIMEDIFF_T " ms", socket_check_timeout); return CURLE_OPERATION_TIMEDOUT; } if(0 == what) { diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c index ae7f295..45c3373 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.c +++ b/Utilities/cmcurl/lib/vtls/schannel.c @@ -439,6 +439,12 @@ get_cert_location(TCHAR *path, DWORD *store_name, TCHAR **store_path, return CURLE_OK; } #endif + +static bool algo(const char *check, char *namep, size_t nlen) +{ + return (strlen(check) == nlen) && !strncmp(check, namep, nlen); +} + static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -660,7 +666,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, cert_showfilename_error); else failf(data, "schannel: Failed to import cert file %s, " - "last error is 0x%x", + "last error is 0x%lx", cert_showfilename_error, errorcode); return CURLE_SSL_CERTPROBLEM; } @@ -671,7 +677,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, if(!client_certs[0]) { failf(data, "schannel: Failed to get certificate from file %s" - ", last error is 0x%x", + ", last error is 0x%lx", cert_showfilename_error, GetLastError()); CertCloseStore(cert_store, 0); return CURLE_SSL_CERTPROBLEM; @@ -684,10 +690,15 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name, cert_store_path); if(!cert_store) { - failf(data, "schannel: Failed to open cert store %x %s, " - "last error is 0x%x", - cert_store_name, cert_store_path, GetLastError()); + char *path_utf8 = + curlx_convert_tchar_to_UTF8(cert_store_path); + failf(data, "schannel: Failed to open cert store %lx %s, " + "last error is 0x%lx", + cert_store_name, + (path_utf8 ? path_utf8 : "(unknown)"), + GetLastError()); free(cert_store_path); + curlx_unicodefree(path_utf8); curlx_unicodefree(cert_path); return CURLE_SSL_CERTPROBLEM; } @@ -790,9 +801,7 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, char *startCur = ciphers13; int algCount = 0; - char tmp[LONGEST_ALG_ID] = { 0 }; char *nameEnd; - size_t n; disable_aes_gcm_sha384 = TRUE; disable_aes_gcm_sha256 = TRUE; @@ -801,40 +810,34 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf, disable_aes_ccm_sha256 = TRUE; while(startCur && (0 != *startCur) && (algCount < remaining_ciphers)) { + size_t n; + char *namep; nameEnd = strchr(startCur, ':'); n = nameEnd ? (size_t)(nameEnd - startCur) : strlen(startCur); + namep = startCur; - /* reject too-long cipher names */ - if(n > (LONGEST_ALG_ID - 1)) { - failf(data, "schannel: Cipher name too long, not checked"); - return CURLE_SSL_CIPHER; - } - - strncpy(tmp, startCur, n); - tmp[n] = 0; - - if(disable_aes_gcm_sha384 - && !strcmp("TLS_AES_256_GCM_SHA384", tmp)) { + if(disable_aes_gcm_sha384 && + algo("TLS_AES_256_GCM_SHA384", namep, n)) { disable_aes_gcm_sha384 = FALSE; } else if(disable_aes_gcm_sha256 - && !strcmp("TLS_AES_128_GCM_SHA256", tmp)) { + && algo("TLS_AES_128_GCM_SHA256", namep, n)) { disable_aes_gcm_sha256 = FALSE; } else if(disable_chacha_poly - && !strcmp("TLS_CHACHA20_POLY1305_SHA256", tmp)) { + && algo("TLS_CHACHA20_POLY1305_SHA256", namep, n)) { disable_chacha_poly = FALSE; } else if(disable_aes_ccm_8_sha256 - && !strcmp("TLS_AES_128_CCM_8_SHA256", tmp)) { + && algo("TLS_AES_128_CCM_8_SHA256", namep, n)) { disable_aes_ccm_8_sha256 = FALSE; } else if(disable_aes_ccm_sha256 - && !strcmp("TLS_AES_128_CCM_SHA256", tmp)) { + && algo("TLS_AES_128_CCM_SHA256", namep, n)) { disable_aes_ccm_sha256 = FALSE; } else { - failf(data, "schannel: Unknown TLS 1.3 cipher: %s", tmp); + failf(data, "schannel: Unknown TLS 1.3 cipher: %.*s", (int)n, namep); return CURLE_SSL_CIPHER; } @@ -1195,9 +1198,8 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) cur += proto.len; *list_len = curlx_uitous(cur - list_start_index); - *extension_len = *list_len + - (unsigned short)sizeof(unsigned int) + - (unsigned short)sizeof(unsigned short); + *extension_len = (unsigned int)(*list_len + + sizeof(unsigned int) + sizeof(unsigned short)); InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); @@ -2332,10 +2334,10 @@ schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data, else { #ifndef CURL_DISABLE_VERBOSE_STRINGS char buffer[STRERROR_LEN]; -#endif - *err = CURLE_RECV_ERROR; infof(data, "schannel: failed to read data from server: %s", Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); +#endif + *err = CURLE_RECV_ERROR; goto cleanup; } } diff --git a/Utilities/cmcurl/lib/vtls/schannel_verify.c b/Utilities/cmcurl/lib/vtls/schannel_verify.c index e7c8bc6..24146d0 100644 --- a/Utilities/cmcurl/lib/vtls/schannel_verify.c +++ b/Utilities/cmcurl/lib/vtls/schannel_verify.c @@ -172,7 +172,7 @@ static CURLcode add_certs_data_to_store(HCERTSTORE trust_store, /* Sanity check that the cert_context object is the right type */ if(CERT_QUERY_CONTENT_CERT != actual_content_type) { failf(data, - "schannel: unexpected content type '%d' when extracting " + "schannel: unexpected content type '%lu' when extracting " "certificate from CA file '%s'", actual_content_type, ca_file_text); result = CURLE_SSL_CACERT_BADFILE; @@ -753,7 +753,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, failf(data, "schannel: CertGetCertificateChain trust error" " CERT_TRUST_REVOCATION_STATUS_UNKNOWN"); else - failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x", + failf(data, "schannel: CertGetCertificateChain error mask: 0x%08lx", dwTrustErrorMask); result = CURLE_PEER_FAILED_VERIFICATION; } diff --git a/Utilities/cmcurl/lib/vtls/sectransp.c b/Utilities/cmcurl/lib/vtls/sectransp.c index 0a22ff6..1f37305 100644 --- a/Utilities/cmcurl/lib/vtls/sectransp.c +++ b/Utilities/cmcurl/lib/vtls/sectransp.c @@ -906,7 +906,6 @@ static OSStatus sectransp_bio_cf_out_write(SSLConnectionRef connection, return rtn; } -#ifndef CURL_DISABLE_VERBOSE_STRINGS CF_INLINE const char *TLSCipherNameForNumber(SSLCipherSuite cipher) { /* The first ciphers in the ciphertable are continuous. Here we do small @@ -925,7 +924,6 @@ CF_INLINE const char *TLSCipherNameForNumber(SSLCipherSuite cipher) } return ciphertable[SSL_NULL_WITH_NULL_NULL].name; } -#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ #if CURL_BUILD_MAC CF_INLINE void GetDarwinVersionNumber(int *major, int *minor) @@ -2369,19 +2367,15 @@ static CURLcode verify_cert(struct Curl_cfilter *cf, const struct curl_blob *ca_info_blob, SSLContextRef ctx) { - int result; + CURLcode result; unsigned char *certbuf; size_t buflen; + bool free_certbuf = FALSE; if(ca_info_blob) { CURL_TRC_CF(data, cf, "verify_peer, CA from config blob"); - certbuf = (unsigned char *)malloc(ca_info_blob->len + 1); - if(!certbuf) { - return CURLE_OUT_OF_MEMORY; - } + certbuf = ca_info_blob->data; buflen = ca_info_blob->len; - memcpy(certbuf, ca_info_blob->data, ca_info_blob->len); - certbuf[ca_info_blob->len]='\0'; } else if(cafile) { CURL_TRC_CF(data, cf, "verify_peer, CA from file '%s'", cafile); @@ -2389,12 +2383,14 @@ static CURLcode verify_cert(struct Curl_cfilter *cf, failf(data, "SSL: failed to read or invalid CA certificate"); return CURLE_SSL_CACERT_BADFILE; } + free_certbuf = TRUE; } else return CURLE_SSL_CACERT_BADFILE; result = verify_cert_buf(cf, data, certbuf, buflen, ctx); - free(certbuf); + if(free_certbuf) + free(certbuf); return result; } diff --git a/Utilities/cmcurl/lib/vtls/vtls.c b/Utilities/cmcurl/lib/vtls/vtls.c index 34eda3e..e928ba5 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.c +++ b/Utilities/cmcurl/lib/vtls/vtls.c @@ -883,28 +883,21 @@ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, size_t valuelen) { struct curl_certinfo *ci = &data->info.certs; - char *output; struct curl_slist *nl; CURLcode result = CURLE_OK; - size_t labellen = strlen(label); - size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */ + struct dynbuf build; - output = malloc(outlen); - if(!output) - return CURLE_OUT_OF_MEMORY; - - /* sprintf the label and colon */ - msnprintf(output, outlen, "%s:", label); + Curl_dyn_init(&build, 10000); - /* memcpy the value (it might not be null-terminated) */ - memcpy(&output[labellen + 1], value, valuelen); - - /* null-terminate the output */ - output[labellen + 1 + valuelen] = 0; + if(Curl_dyn_add(&build, label) || + Curl_dyn_addn(&build, ":", 1) || + Curl_dyn_addn(&build, value, valuelen)) + return CURLE_OUT_OF_MEMORY; - nl = Curl_slist_append_nodup(ci->certinfo[certnum], output); + nl = Curl_slist_append_nodup(ci->certinfo[certnum], + Curl_dyn_ptr(&build)); if(!nl) { - free(output); + Curl_dyn_free(&build); curl_slist_free_all(ci->certinfo[certnum]); result = CURLE_OUT_OF_MEMORY; } @@ -1002,7 +995,7 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, /* only do this if pinnedpubkey starts with "sha256//", length 8 */ if(strncmp(pinnedpubkey, "sha256//", 8) == 0) { CURLcode encode; - size_t encodedlen = 0, pinkeylen; + size_t encodedlen = 0; char *encoded = NULL, *pinkeycopy, *begin_pos, *end_pos; unsigned char *sha256sumdigest; @@ -1030,13 +1023,11 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, infof(data, " public key hash: sha256//%s", encoded); /* it starts with sha256//, copy so we can modify it */ - pinkeylen = strlen(pinnedpubkey) + 1; - pinkeycopy = malloc(pinkeylen); + pinkeycopy = strdup(pinnedpubkey); if(!pinkeycopy) { Curl_safefree(encoded); return CURLE_OUT_OF_MEMORY; } - memcpy(pinkeycopy, pinnedpubkey, pinkeylen); /* point begin_pos to the copy, and start extracting keys */ begin_pos = pinkeycopy; do { @@ -1422,17 +1413,13 @@ static size_t multissl_version(char *buffer, size_t size) backends_len = p - backends; } - if(!size) - return 0; - - if(size <= backends_len) { - strncpy(buffer, backends, size - 1); - buffer[size - 1] = '\0'; - return size - 1; + if(size) { + if(backends_len < size) + strcpy(buffer, backends); + else + *buffer = 0; /* did not fit */ } - - strcpy(buffer, backends); - return backends_len; + return 0; } static int multissl_setup(const struct Curl_ssl *backend) @@ -1728,18 +1715,34 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf, { struct cf_call_data save; ssize_t nread; + size_t ntotal = 0; CF_DATA_SAVE(save, cf, data); *err = CURLE_OK; - nread = Curl_ssl->recv_plain(cf, data, buf, len, err); - if(nread > 0) { - DEBUGASSERT((size_t)nread <= len); - } - else if(nread == 0) { - /* eof */ + /* Do receive until we fill the buffer somehwhat or EGAIN, error or EOF */ + while(!ntotal || (len - ntotal) > (4*1024)) { *err = CURLE_OK; + nread = Curl_ssl->recv_plain(cf, data, buf + ntotal, len - ntotal, err); + if(nread < 0) { + if(*err == CURLE_AGAIN && ntotal > 0) { + /* we EAGAINed after having reed data, return the success amount */ + *err = CURLE_OK; + break; + } + /* we have a an error to report */ + goto out; + } + else if(nread == 0) { + /* eof */ + break; + } + ntotal += (size_t)nread; + DEBUGASSERT((size_t)ntotal <= len); } - CURL_TRC_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", len, nread, *err); + nread = (ssize_t)ntotal; +out: + CURL_TRC_CF(data, cf, "cf_recv(len=%zu) -> %zd, %d", len, + nread, *err); CF_DATA_RESTORE(cf, save); return nread; } @@ -1855,6 +1858,8 @@ struct Curl_cftype Curl_cft_ssl = { ssl_cf_query, }; +#ifndef CURL_DISABLE_PROXY + struct Curl_cftype Curl_cft_ssl_proxy = { "SSL-PROXY", CF_TYPE_SSL, @@ -1873,6 +1878,8 @@ struct Curl_cftype Curl_cft_ssl_proxy = { Curl_cf_def_query, }; +#endif /* !CURL_DISABLE_PROXY */ + static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, struct Curl_easy *data, struct connectdata *conn) @@ -1980,8 +1987,12 @@ bool Curl_ssl_supports(struct Curl_easy *data, int option) static struct Curl_cfilter *get_ssl_filter(struct Curl_cfilter *cf) { for(; cf; cf = cf->next) { - if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) + if(cf->cft == &Curl_cft_ssl) + return cf; +#ifndef CURL_DISABLE_PROXY + if(cf->cft == &Curl_cft_ssl_proxy) return cf; +#endif } return NULL; } @@ -2027,7 +2038,12 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) { +#ifndef CURL_DISABLE_PROXY return (cf->cft == &Curl_cft_ssl_proxy); +#else + (void)cf; + return FALSE; +#endif } struct ssl_config_data * diff --git a/Utilities/cmcurl/lib/vtls/vtls.h b/Utilities/cmcurl/lib/vtls/vtls.h index f1856bd..744bbf8 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.h +++ b/Utilities/cmcurl/lib/vtls/vtls.h @@ -228,7 +228,9 @@ struct ssl_primary_config * Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf); extern struct Curl_cftype Curl_cft_ssl; +#ifndef CURL_DISABLE_PROXY extern struct Curl_cftype Curl_cft_ssl_proxy; +#endif #else /* if not USE_SSL */ diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.c b/Utilities/cmcurl/lib/vtls/wolfssl.c index 5890bb6..a3c017c 100644 --- a/Utilities/cmcurl/lib/vtls/wolfssl.c +++ b/Utilities/cmcurl/lib/vtls/wolfssl.c @@ -583,12 +583,25 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) if(ssl_config->primary.clientcert && ssl_config->key) { int file_type = do_file_type(ssl_config->cert_type); - if(wolfSSL_CTX_use_certificate_file(backend->ctx, - ssl_config->primary.clientcert, - file_type) != 1) { - failf(data, "unable to use client certificate (no key or wrong pass" - " phrase?)"); - return CURLE_SSL_CONNECT_ERROR; + if(file_type == WOLFSSL_FILETYPE_PEM) { + if(wolfSSL_CTX_use_certificate_chain_file(backend->ctx, + ssl_config->primary.clientcert) + != 1) { + failf(data, "unable to use client certificate"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else if(file_type == WOLFSSL_FILETYPE_ASN1) { + if(wolfSSL_CTX_use_certificate_file(backend->ctx, + ssl_config->primary.clientcert, + file_type) != 1) { + failf(data, "unable to use client certificate"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { + failf(data, "unknown cert type"); + return CURLE_BAD_FUNCTION_ARGUMENT; } file_type = do_file_type(ssl_config->key_type); @@ -1084,9 +1097,7 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf, *curlcode = CURLE_OK; return 0; case SSL_ERROR_NONE: - /* FALLTHROUGH */ case SSL_ERROR_WANT_READ: - /* FALLTHROUGH */ case SSL_ERROR_WANT_WRITE: /* there's data pending, re-invoke wolfSSL_read() */ CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> AGAIN", blen); diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.c b/Utilities/cmcurl/lib/vtls/x509asn1.c index 8b1eed6..da07936 100644 --- a/Utilities/cmcurl/lib/vtls/x509asn1.c +++ b/Utilities/cmcurl/lib/vtls/x509asn1.c @@ -97,6 +97,11 @@ #define CURL_ASN1_CHARACTER_STRING 29 #define CURL_ASN1_BMP_STRING 30 +/* Max sixes */ + +#define MAX_X509_STR 10000 +#define MAX_X509_CERT 100000 + #ifdef WANT_EXTRACT_CERTINFO /* ASN.1 OID table entry. */ struct Curl_OID { @@ -255,61 +260,61 @@ static const struct Curl_OID *searchOID(const char *oid) } /* - * Convert an ASN.1 Boolean value into its string representation. Return the - * dynamically allocated string, or NULL if source is not an ASN.1 Boolean - * value. + * Convert an ASN.1 Boolean value into its string representation. + * + * Return error code. */ -static const char *bool2str(const char *beg, const char *end) +static CURLcode bool2str(struct dynbuf *store, + const char *beg, const char *end) { if(end - beg != 1) - return NULL; - return strdup(*beg? "TRUE": "FALSE"); + return CURLE_BAD_FUNCTION_ARGUMENT; + return Curl_dyn_add(store, *beg? "TRUE": "FALSE"); } /* * Convert an ASN.1 octet string to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * + * Return error code. */ -static const char *octet2str(const char *beg, const char *end) +static CURLcode octet2str(struct dynbuf *store, + const char *beg, const char *end) { - struct dynbuf buf; - CURLcode result; - - Curl_dyn_init(&buf, 3 * CURL_ASN1_MAX + 1); - result = Curl_dyn_addn(&buf, "", 0); + CURLcode result = CURLE_OK; while(!result && beg < end) - result = Curl_dyn_addf(&buf, "%02x:", (unsigned char) *beg++); + result = Curl_dyn_addf(store, "%02x:", (unsigned char) *beg++); - return Curl_dyn_ptr(&buf); + return result; } -static const char *bit2str(const char *beg, const char *end) +static CURLcode bit2str(struct dynbuf *store, + const char *beg, const char *end) { - /* Convert an ASN.1 bit string to a printable string. - Return the dynamically allocated string, or NULL if an error occurs. */ + /* Convert an ASN.1 bit string to a printable string. */ if(++beg > end) - return NULL; - return octet2str(beg, end); + return CURLE_BAD_FUNCTION_ARGUMENT; + return octet2str(store, beg, end); } /* * Convert an ASN.1 integer value into its string representation. - * Return the dynamically allocated string, or NULL if source is not an - * ASN.1 integer value. + * + * Returns error. */ -static const char *int2str(const char *beg, const char *end) +static CURLcode int2str(struct dynbuf *store, + const char *beg, const char *end) { unsigned int val = 0; size_t n = end - beg; if(!n) - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; if(n > 4) - return octet2str(beg, end); + return octet2str(store, beg, end); /* Represent integers <= 32-bit as a single value. */ if(*beg & 0x80) @@ -318,25 +323,24 @@ static const char *int2str(const char *beg, const char *end) do val = (val << 8) | *(const unsigned char *) beg++; while(beg < end); - return curl_maprintf("%s%x", val >= 10? "0x": "", val); + return Curl_dyn_addf(store, "%s%x", val >= 10? "0x": "", val); } /* - * Perform a lazy conversion from an ASN.1 typed string to UTF8. Allocate the - * destination buffer dynamically. The allocation size will normally be too - * large: this is to avoid buffer overflows. - * Terminate the string with a nul byte and return the converted - * string length. + * Convert from an ASN.1 typed string to UTF8. + * + * The result is stored in a dynbuf that is inited by the user of this + * function. + * + * Returns error. */ -static ssize_t -utf8asn1str(char **to, int type, const char *from, const char *end) +static CURLcode +utf8asn1str(struct dynbuf *to, int type, const char *from, const char *end) { size_t inlength = end - from; int size = 1; - size_t outlength; - char *buf; + CURLcode result = CURLE_OK; - *to = NULL; switch(type) { case CURL_ASN1_BMP_STRING: size = 2; @@ -352,133 +356,85 @@ utf8asn1str(char **to, int type, const char *from, const char *end) case CURL_ASN1_UTF8_STRING: break; default: - return -1; /* Conversion not supported. */ + return CURLE_BAD_FUNCTION_ARGUMENT; /* Conversion not supported. */ } if(inlength % size) - return -1; /* Length inconsistent with character size. */ - if(inlength / size > (SIZE_T_MAX - 1) / 4) - return -1; /* Too big. */ - buf = malloc(4 * (inlength / size) + 1); - if(!buf) - return -1; /* Not enough memory. */ + /* Length inconsistent with character size. */ + return CURLE_BAD_FUNCTION_ARGUMENT; if(type == CURL_ASN1_UTF8_STRING) { /* Just copy. */ - outlength = inlength; - if(outlength) - memcpy(buf, from, outlength); + if(inlength) + result = Curl_dyn_addn(to, from, inlength); } else { - for(outlength = 0; from < end;) { - int charsize; - unsigned int wc; + while(!result && (from < end)) { + char buf[4]; /* decode buffer */ + int charsize = 1; + unsigned int wc = 0; - wc = 0; switch(size) { case 4: wc = (wc << 8) | *(const unsigned char *) from++; wc = (wc << 8) | *(const unsigned char *) from++; - /* FALLTHROUGH */ + FALLTHROUGH(); case 2: wc = (wc << 8) | *(const unsigned char *) from++; - /* FALLTHROUGH */ + FALLTHROUGH(); default: /* case 1: */ wc = (wc << 8) | *(const unsigned char *) from++; } - charsize = 1; if(wc >= 0x00000080) { if(wc >= 0x00000800) { if(wc >= 0x00010000) { if(wc >= 0x00200000) { free(buf); - return -1; /* Invalid char. size for target encoding. */ + /* Invalid char. size for target encoding. */ + return CURLE_WEIRD_SERVER_REPLY; } - buf[outlength + 3] = (char) (0x80 | (wc & 0x3F)); + buf[3] = (char) (0x80 | (wc & 0x3F)); wc = (wc >> 6) | 0x00010000; charsize++; } - buf[outlength + 2] = (char) (0x80 | (wc & 0x3F)); + buf[2] = (char) (0x80 | (wc & 0x3F)); wc = (wc >> 6) | 0x00000800; charsize++; } - buf[outlength + 1] = (char) (0x80 | (wc & 0x3F)); + buf[1] = (char) (0x80 | (wc & 0x3F)); wc = (wc >> 6) | 0x000000C0; charsize++; } - buf[outlength] = (char) wc; - outlength += charsize; + buf[0] = (char) wc; + result = Curl_dyn_addn(to, buf, charsize); } } - buf[outlength] = '\0'; - *to = buf; - return outlength; -} - -/* - * Convert an ASN.1 String into its UTF-8 string representation. - * Return the dynamically allocated string, or NULL if an error occurs. - */ -static const char *string2str(int type, const char *beg, const char *end) -{ - char *buf; - if(utf8asn1str(&buf, type, beg, end) < 0) - return NULL; - return buf; -} - -/* - * Decimal ASCII encode unsigned integer `x' into the buflen sized buffer at - * buf. Return the total number of encoded digits, even if larger than - * `buflen'. - */ -static size_t encodeUint(char *buf, size_t buflen, unsigned int x) -{ - size_t i = 0; - unsigned int y = x / 10; - - if(y) { - i = encodeUint(buf, buflen, y); - x -= y * 10; - } - if(i < buflen) - buf[i] = (char) ('0' + x); - i++; - if(i < buflen) - buf[i] = '\0'; /* Store a terminator if possible. */ - return i; + return result; } /* * Convert an ASN.1 OID into its dotted string representation. - * Store the result in th `n'-byte buffer at `buf'. - * Return the converted string length, or 0 on errors. + * + * Return error code. */ -static size_t encodeOID(char *buf, size_t buflen, - const char *beg, const char *end) +static CURLcode encodeOID(struct dynbuf *store, + const char *beg, const char *end) { - size_t i; unsigned int x; unsigned int y; + CURLcode result = CURLE_OK; /* Process the first two numbers. */ y = *(const unsigned char *) beg++; x = y / 40; y -= x * 40; - i = encodeUint(buf, buflen, x); - if(i < buflen) - buf[i] = '.'; - i++; - if(i >= buflen) - i += encodeUint(NULL, 0, y); - else - i += encodeUint(buf + i, buflen - i, y); + + result = Curl_dyn_addf(store, "%u.%u", x, y); + if(result) + return result; /* Process the trailing numbers. */ while(beg < end) { - if(i < buflen) - buf[i] = '.'; - i++; x = 0; do { if(x & 0xFF000000) @@ -486,46 +442,42 @@ static size_t encodeOID(char *buf, size_t buflen, y = *(const unsigned char *) beg++; x = (x << 7) | (y & 0x7F); } while(y & 0x80); - if(i >= buflen) - i += encodeUint(NULL, 0, x); - else - i += encodeUint(buf + i, buflen - i, x); + result = Curl_dyn_addf(store, ".%u", x); } - if(i < buflen) - buf[i] = '\0'; - return i; + return result; } /* * Convert an ASN.1 OID into its dotted or symbolic string representation. - * Return the dynamically allocated string, or NULL if an error occurs. + * + * Return error code. */ -static const char *OID2str(const char *beg, const char *end, bool symbolic) +static CURLcode OID2str(struct dynbuf *store, + const char *beg, const char *end, bool symbolic) { - char *buf = NULL; + CURLcode result = CURLE_OK; if(beg < end) { - size_t buflen = encodeOID(NULL, 0, beg, end); - if(buflen) { - buf = malloc(buflen + 1); /* one extra for the zero byte */ - if(buf) { - encodeOID(buf, buflen, beg, end); - buf[buflen] = '\0'; - - if(symbolic) { - const struct Curl_OID *op = searchOID(buf); - if(op) { - free(buf); - buf = strdup(op->textoid); - } - } + if(symbolic) { + struct dynbuf buf; + Curl_dyn_init(&buf, MAX_X509_STR); + result = encodeOID(&buf, beg, end); + + if(!result) { + const struct Curl_OID *op = searchOID(Curl_dyn_ptr(&buf)); + if(op) + result = Curl_dyn_add(store, op->textoid); + Curl_dyn_free(&buf); } } + else + result = encodeOID(store, beg, end); } - return buf; + return result; } -static const char *GTime2str(const char *beg, const char *end) +static CURLcode GTime2str(struct dynbuf *store, + const char *beg, const char *end) { const char *tzp; const char *fracp; @@ -548,12 +500,12 @@ static const char *GTime2str(const char *beg, const char *end) break; case 2: sec1 = fracp[-2]; - /* FALLTHROUGH */ + FALLTHROUGH(); case 1: sec2 = fracp[-1]; break; default: - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; } /* Scan for timezone, measure fractional seconds. */ @@ -582,7 +534,8 @@ static const char *GTime2str(const char *beg, const char *end) } tzl = end - tzp; - return curl_maprintf("%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s", + return Curl_dyn_addf(store, + "%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s", beg, beg + 4, beg + 6, beg + 8, beg + 10, sec1, sec2, fracl? ".": "", (int)fracl, fracp, @@ -590,10 +543,12 @@ static const char *GTime2str(const char *beg, const char *end) } /* - * Convert an ASN.1 UTC time to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * Convert an ASN.1 UTC time to a printable string. + * + * Return error code. */ -static const char *UTime2str(const char *beg, const char *end) +static CURLcode UTime2str(struct dynbuf *store, + const char *beg, const char *end) { const char *tzp; size_t tzl; @@ -606,15 +561,16 @@ static const char *UTime2str(const char *beg, const char *end) switch(tzp - sec) { case 0: sec = "00"; + FALLTHROUGH(); case 2: break; default: - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; } /* Process timezone. */ if(tzp >= end) - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; if(*tzp == 'Z') { tzp = "GMT"; end = tzp + 3; @@ -623,7 +579,7 @@ static const char *UTime2str(const char *beg, const char *end) tzp++; tzl = end - tzp; - return curl_maprintf("%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s", + return Curl_dyn_addf(store, "%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s", 20 - (*beg >= '5'), beg, beg + 2, beg + 4, beg + 6, beg + 8, sec, (int)tzl, tzp); @@ -631,34 +587,45 @@ static const char *UTime2str(const char *beg, const char *end) /* * Convert an ASN.1 element to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * + * Return error */ -static const char *ASN1tostr(struct Curl_asn1Element *elem, int type) +static CURLcode ASN1tostr(struct dynbuf *store, + struct Curl_asn1Element *elem, int type) { + CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; if(elem->constructed) - return NULL; /* No conversion of structured elements. */ + return CURLE_OK; /* No conversion of structured elements. */ if(!type) type = elem->tag; /* Type not forced: use element tag as type. */ switch(type) { case CURL_ASN1_BOOLEAN: - return bool2str(elem->beg, elem->end); + result = bool2str(store, elem->beg, elem->end); + break; case CURL_ASN1_INTEGER: case CURL_ASN1_ENUMERATED: - return int2str(elem->beg, elem->end); + result = int2str(store, elem->beg, elem->end); + break; case CURL_ASN1_BIT_STRING: - return bit2str(elem->beg, elem->end); + result = bit2str(store, elem->beg, elem->end); + break; case CURL_ASN1_OCTET_STRING: - return octet2str(elem->beg, elem->end); + result = octet2str(store, elem->beg, elem->end); + break; case CURL_ASN1_NULL: - return strdup(""); + result = Curl_dyn_addn(store, "", 1); + break; case CURL_ASN1_OBJECT_IDENTIFIER: - return OID2str(elem->beg, elem->end, TRUE); + result = OID2str(store, elem->beg, elem->end, TRUE); + break; case CURL_ASN1_UTC_TIME: - return UTime2str(elem->beg, elem->end); + result = UTime2str(store, elem->beg, elem->end); + break; case CURL_ASN1_GENERALIZED_TIME: - return GTime2str(elem->beg, elem->end); + result = GTime2str(store, elem->beg, elem->end); + break; case CURL_ASN1_UTF8_STRING: case CURL_ASN1_NUMERIC_STRING: case CURL_ASN1_PRINTABLE_STRING: @@ -667,87 +634,96 @@ static const char *ASN1tostr(struct Curl_asn1Element *elem, int type) case CURL_ASN1_VISIBLE_STRING: case CURL_ASN1_UNIVERSAL_STRING: case CURL_ASN1_BMP_STRING: - return string2str(type, elem->beg, elem->end); + result = utf8asn1str(store, type, elem->beg, elem->end); + break; } - return NULL; /* Unsupported. */ + return result; } /* - * ASCII encode distinguished name at `dn' into the `buflen'-sized buffer at - * `buf'. + * ASCII encode distinguished name at `dn' into the store dynbuf. * - * Returns the total string length, even if larger than `buflen' or -1 on - * error. + * Returns error. */ -static ssize_t encodeDN(char *buf, size_t buflen, struct Curl_asn1Element *dn) +static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) { struct Curl_asn1Element rdn; struct Curl_asn1Element atv; struct Curl_asn1Element oid; struct Curl_asn1Element value; - size_t l = 0; const char *p1; const char *p2; const char *p3; const char *str; + CURLcode result = CURLE_OK; + bool added = FALSE; + struct dynbuf temp; + Curl_dyn_init(&temp, MAX_X509_STR); for(p1 = dn->beg; p1 < dn->end;) { p1 = getASN1Element(&rdn, p1, dn->end); - if(!p1) - return -1; + if(!p1) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } for(p2 = rdn.beg; p2 < rdn.end;) { p2 = getASN1Element(&atv, p2, rdn.end); - if(!p2) - return -1; + if(!p2) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } p3 = getASN1Element(&oid, atv.beg, atv.end); - if(!p3) - return -1; - if(!getASN1Element(&value, p3, atv.end)) - return -1; - str = ASN1tostr(&oid, 0); - if(!str) - return -1; + if(!p3) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + if(!getASN1Element(&value, p3, atv.end)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + Curl_dyn_reset(&temp); + result = ASN1tostr(&temp, &oid, 0); + if(result) + goto error; + + str = Curl_dyn_ptr(&temp); /* Encode delimiter. If attribute has a short uppercase name, delimiter is ", ". */ - if(l) { - for(p3 = str; ISUPPER(*p3); p3++) - ; - for(p3 = (*p3 || p3 - str > 2)? "/": ", "; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } + for(p3 = str; ISUPPER(*p3); p3++) + ; + if(added) { + if(p3 - str > 2) + result = Curl_dyn_addn(store, "/", 1); + else + result = Curl_dyn_addn(store, ", ", 2); + if(result) + goto error; } /* Encode attribute name. */ - for(p3 = str; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } - free((char *) str); + result = Curl_dyn_add(store, str); + if(result) + goto error; /* Generate equal sign. */ - if(l < buflen) - buf[l] = '='; - l++; + result = Curl_dyn_addn(store, "=", 1); + if(result) + goto error; /* Generate value. */ - str = ASN1tostr(&value, 0); - if(!str) - return -1; - for(p3 = str; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } - free((char *) str); + result = ASN1tostr(store, &value, 0); + if(result) + goto error; + Curl_dyn_reset(&temp); + added = TRUE; /* use separator for next */ } } +error: + Curl_dyn_free(&temp); - return l; + return result; } #endif /* WANT_EXTRACT_CERTINFO */ @@ -876,25 +852,9 @@ int Curl_parseX509(struct Curl_X509certificate *cert, #ifdef WANT_EXTRACT_CERTINFO -/* - * Copy at most 64-characters, terminate with a newline and returns the - * effective number of stored characters. - */ -static size_t copySubstring(char *to, const char *from) -{ - size_t i; - for(i = 0; i < 64; i++) { - to[i] = *from; - if(!*from++) - break; - } - - to[i++] = '\n'; - return i; -} - -static const char *dumpAlgo(struct Curl_asn1Element *param, - const char *beg, const char *end) +static CURLcode dumpAlgo(struct dynbuf *store, + struct Curl_asn1Element *param, + const char *beg, const char *end) { struct Curl_asn1Element oid; @@ -902,14 +862,16 @@ static const char *dumpAlgo(struct Curl_asn1Element *param, beg = getASN1Element(&oid, beg, end); if(!beg) - return NULL; + return CURLE_BAD_FUNCTION_ARGUMENT; param->header = NULL; param->tag = 0; param->beg = param->end = end; - if(beg < end) - if(!getASN1Element(param, beg, end)) - return NULL; - return OID2str(oid.beg, oid.end, TRUE); + if(beg < end) { + const char *p = getASN1Element(param, beg, end); + if(!p) + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return OID2str(store, oid.beg, oid.end, TRUE); } /* @@ -926,24 +888,47 @@ static CURLcode ssl_push_certinfo(struct Curl_easy *data, return Curl_ssl_push_certinfo_len(data, certnum, label, value, valuelen); } -/* return 0 on success, 1 on error */ -static int do_pubkey_field(struct Curl_easy *data, int certnum, - const char *label, struct Curl_asn1Element *elem) +/* + * This is a convenience function for push_certinfo_len that takes a + * dynbuf value. + * + * It also does the verbose output if !certnum. + */ +static CURLcode ssl_push_certinfo_dyn(struct Curl_easy *data, + int certnum, + const char *label, + struct dynbuf *ptr) { - const char *output; - CURLcode result = CURLE_OK; + size_t valuelen = Curl_dyn_len(ptr); + char *value = Curl_dyn_ptr(ptr); + + CURLcode result = Curl_ssl_push_certinfo_len(data, certnum, label, + value, valuelen); + + if(!certnum && !result) + infof(data, " %s: %s", label, value); + + return result; +} + +static CURLcode do_pubkey_field(struct Curl_easy *data, int certnum, + const char *label, + struct Curl_asn1Element *elem) +{ + CURLcode result; + struct dynbuf out; + + Curl_dyn_init(&out, MAX_X509_STR); /* Generate a certificate information record for the public key. */ - output = ASN1tostr(elem, 0); - if(output) { + result = ASN1tostr(&out, elem, 0); + if(!result) { if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, label, output); - if(!certnum && !result) - infof(data, " %s: %s", label, output); - free((char *) output); + result = ssl_push_certinfo_dyn(data, certnum, label, &out); + Curl_dyn_free(&out); } - return result ? 1 : 0; + return result; } /* return 0 on success, 1 on error */ @@ -964,7 +949,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, */ const size_t len = ((pubkey->end - pubkey->beg - 2) * 4); if(!certnum) - infof(data, " ECC Public Key (%lu bits)", len); + infof(data, " ECC Public Key (%zu bits)", len); if(data->set.ssl.certinfo) { char q[sizeof(len) * 8 / 3 + 1]; (void)msnprintf(q, sizeof(q), "%zu", len); @@ -998,7 +983,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, if(len > 32) elem.beg = q; /* Strip leading zero bytes. */ if(!certnum) - infof(data, " RSA Public Key (%lu bits)", len); + infof(data, " RSA Public Key (%zu bits)", len); if(data->set.ssl.certinfo) { char r[sizeof(len) * 8 / 3 + 1]; msnprintf(r, sizeof(r), "%zu", len); @@ -1049,24 +1034,12 @@ static int do_pubkey(struct Curl_easy *data, int certnum, /* * Convert an ASN.1 distinguished name into a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. + * Return error. */ -static const char *DNtostr(struct Curl_asn1Element *dn) +static CURLcode DNtostr(struct dynbuf *store, + struct Curl_asn1Element *dn) { - char *buf = NULL; - ssize_t buflen = encodeDN(NULL, 0, dn); - - if(buflen >= 0) { - buf = malloc(buflen + 1); - if(buf) { - if(encodeDN(buf, buflen + 1, dn) == -1) { - free(buf); - return NULL; - } - buf[buflen] = '\0'; - } - } - return buf; + return encodeDN(store, dn); } CURLcode Curl_extract_certinfo(struct Curl_easy *data, @@ -1076,19 +1049,19 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, { struct Curl_X509certificate cert; struct Curl_asn1Element param; - const char *ccp; - char *cp1; - size_t cl1; - char *cp2; + char *certptr; + size_t clen; + struct dynbuf out; CURLcode result = CURLE_OK; unsigned int version; - size_t i; - size_t j; + const char *ptr; + int rc; if(!data->set.ssl.certinfo) if(certnum) return CURLE_OK; + Curl_dyn_init(&out, MAX_X509_STR); /* Prepare the certificate information for curl_easy_getinfo(). */ /* Extract the certificate ASN.1 elements. */ @@ -1096,135 +1069,126 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, return CURLE_PEER_FAILED_VERIFICATION; /* Subject. */ - ccp = DNtostr(&cert.subject); - if(!ccp) - return CURLE_OUT_OF_MEMORY; + result = DNtostr(&out, &cert.subject); + if(result) + goto done; if(data->set.ssl.certinfo) { - result = ssl_push_certinfo(data, certnum, "Subject", ccp); + result = ssl_push_certinfo_dyn(data, certnum, "Subject", &out); if(result) - return result; + goto done; } - if(!certnum) - infof(data, "%2d Subject: %s", certnum, ccp); - free((char *) ccp); + Curl_dyn_reset(&out); /* Issuer. */ - ccp = DNtostr(&cert.issuer); - if(!ccp) - return CURLE_OUT_OF_MEMORY; + result = DNtostr(&out, &cert.issuer); + if(result) + goto done; if(data->set.ssl.certinfo) { - result = ssl_push_certinfo(data, certnum, "Issuer", ccp); + result = ssl_push_certinfo_dyn(data, certnum, "Issuer", &out); + if(result) + goto done; } - if(!certnum) - infof(data, " Issuer: %s", ccp); - free((char *) ccp); - if(result) - return result; + Curl_dyn_reset(&out); /* Version (always fits in less than 32 bits). */ version = 0; - for(ccp = cert.version.beg; ccp < cert.version.end; ccp++) - version = (version << 8) | *(const unsigned char *) ccp; + for(ptr = cert.version.beg; ptr < cert.version.end; ptr++) + version = (version << 8) | *(const unsigned char *) ptr; if(data->set.ssl.certinfo) { - ccp = curl_maprintf("%x", version); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - result = ssl_push_certinfo(data, certnum, "Version", ccp); - free((char *) ccp); + result = Curl_dyn_addf(&out, "%x", version); + if(result) + goto done; + result = ssl_push_certinfo_dyn(data, certnum, "Version", &out); if(result) - return result; + goto done; + Curl_dyn_reset(&out); } - if(!certnum) - infof(data, " Version: %u (0x%x)", version + 1, version); /* Serial number. */ - ccp = ASN1tostr(&cert.serialNumber, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Serial Number", ccp); - if(!certnum) - infof(data, " Serial Number: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.serialNumber, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Serial Number", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Signature algorithm .*/ - ccp = dumpAlgo(¶m, cert.signatureAlgorithm.beg, - cert.signatureAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Signature Algorithm", ccp); - if(!certnum) - infof(data, " Signature Algorithm: %s", ccp); - free((char *) ccp); + result = dumpAlgo(&out, ¶m, cert.signatureAlgorithm.beg, + cert.signatureAlgorithm.end); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Signature Algorithm", + &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Start Date. */ - ccp = ASN1tostr(&cert.notBefore, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Start Date", ccp); - if(!certnum) - infof(data, " Start Date: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.notBefore, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Start Date", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Expire Date. */ - ccp = ASN1tostr(&cert.notAfter, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Expire Date", ccp); - if(!certnum) - infof(data, " Expire Date: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.notAfter, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Expire Date", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Public Key Algorithm. */ - ccp = dumpAlgo(¶m, cert.subjectPublicKeyAlgorithm.beg, - cert.subjectPublicKeyAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Public Key Algorithm", - ccp); - if(!result) { - int ret; - if(!certnum) - infof(data, " Public Key Algorithm: %s", ccp); - ret = do_pubkey(data, certnum, ccp, ¶m, &cert.subjectPublicKey); - if(ret) - result = CURLE_OUT_OF_MEMORY; /* the most likely error */ - } - free((char *) ccp); + result = dumpAlgo(&out, ¶m, cert.subjectPublicKeyAlgorithm.beg, + cert.subjectPublicKeyAlgorithm.end); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Public Key Algorithm", + &out); + if(result) + goto done; + } + + rc = do_pubkey(data, certnum, Curl_dyn_ptr(&out), + ¶m, &cert.subjectPublicKey); + if(rc) { + result = CURLE_OUT_OF_MEMORY; /* the most likely error */ + goto done; + } + Curl_dyn_reset(&out); /* Signature. */ - ccp = ASN1tostr(&cert.signature, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Signature", ccp); - if(!certnum) - infof(data, " Signature: %s", ccp); - free((char *) ccp); + result = ASN1tostr(&out, &cert.signature, 0); if(result) - return result; + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Signature", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); /* Generate PEM certificate. */ result = Curl_base64_encode(cert.certificate.beg, cert.certificate.end - cert.certificate.beg, - &cp1, &cl1); + &certptr, &clen); if(result) - return result; - /* Compute the number of characters in final certificate string. Format is: + goto done; + + /* Generate the final output certificate string. Format is: -----BEGIN CERTIFICATE-----\n <max 64 base64 characters>\n . @@ -1232,207 +1196,34 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, . -----END CERTIFICATE-----\n */ - i = 28 + cl1 + (cl1 + 64 - 1) / 64 + 26; - cp2 = malloc(i + 1); - if(!cp2) { - free(cp1); - return CURLE_OUT_OF_MEMORY; - } - /* Build the certificate string. */ - i = copySubstring(cp2, "-----BEGIN CERTIFICATE-----"); - for(j = 0; j < cl1; j += 64) - i += copySubstring(cp2 + i, cp1 + j); - i += copySubstring(cp2 + i, "-----END CERTIFICATE-----"); - cp2[i] = '\0'; - free(cp1); - if(data->set.ssl.certinfo) - result = ssl_push_certinfo(data, certnum, "Cert", cp2); - if(!certnum) - infof(data, "%s", cp2); - free(cp2); - return result; -} -#endif /* WANT_EXTRACT_CERTINFO */ - -#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ + Curl_dyn_reset(&out); -#ifdef WANT_VERIFYHOST - -static const char *checkOID(const char *beg, const char *end, - const char *oid) -{ - struct Curl_asn1Element e; - const char *ccp; - const char *p; - bool matched; - - /* Check if first ASN.1 element at `beg' is the given OID. - Return a pointer in the source after the OID if found, else NULL. */ - - ccp = getASN1Element(&e, beg, end); - if(!ccp || e.tag != CURL_ASN1_OBJECT_IDENTIFIER) - return NULL; - - p = OID2str(e.beg, e.end, FALSE); - if(!p) - return NULL; - - matched = !strcmp(p, oid); - free((char *) p); - return matched? ccp: NULL; -} - -CURLcode Curl_verifyhost(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char *beg, const char *end) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct Curl_X509certificate cert; - struct Curl_asn1Element dn; - struct Curl_asn1Element elem; - struct Curl_asn1Element ext; - struct Curl_asn1Element name; - const char *p; - const char *q; - char *dnsname; - int matched = -1; - size_t addrlen = (size_t) -1; - ssize_t len; - size_t hostlen; - -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - - /* Verify that connection server matches info in X509 certificate at - `beg'..`end'. */ - - if(!conn_config->verifyhost) - return CURLE_OK; - - if(Curl_parseX509(&cert, beg, end)) - return CURLE_PEER_FAILED_VERIFICATION; - - hostlen = strlen(connssl->peer.hostname); - - /* Get the server IP address. */ -#ifdef ENABLE_IPV6 - if(cf->conn->bits.ipv6_ip && - Curl_inet_pton(AF_INET6, connssl->peer.hostname, &addr)) - addrlen = sizeof(struct in6_addr); - else -#endif - if(Curl_inet_pton(AF_INET, connssl->peer.hostname, &addr)) - addrlen = sizeof(struct in_addr); - - /* Process extensions. */ - for(p = cert.extensions.beg; p < cert.extensions.end && matched != 1;) { - p = getASN1Element(&ext, p, cert.extensions.end); - if(!p) - return CURLE_PEER_FAILED_VERIFICATION; - - /* Check if extension is a subjectAlternativeName. */ - ext.beg = checkOID(ext.beg, ext.end, sanOID); - if(ext.beg) { - ext.beg = getASN1Element(&elem, ext.beg, ext.end); - if(!ext.beg) - return CURLE_PEER_FAILED_VERIFICATION; - /* Skip critical if present. */ - if(elem.tag == CURL_ASN1_BOOLEAN) { - ext.beg = getASN1Element(&elem, ext.beg, ext.end); - if(!ext.beg) - return CURLE_PEER_FAILED_VERIFICATION; - } - /* Parse the octet string contents: is a single sequence. */ - if(!getASN1Element(&elem, elem.beg, elem.end)) - return CURLE_PEER_FAILED_VERIFICATION; - /* Check all GeneralNames. */ - for(q = elem.beg; matched != 1 && q < elem.end;) { - q = getASN1Element(&name, q, elem.end); - if(!q) - break; - switch(name.tag) { - case 2: /* DNS name. */ - len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING, - name.beg, name.end); - if(len > 0 && (size_t)len == strlen(dnsname)) - matched = Curl_cert_hostcheck(dnsname, (size_t)len, - connssl->peer.hostname, hostlen); - else - matched = 0; - free(dnsname); - break; - - case 7: /* IP address. */ - matched = (size_t)(name.end - name.beg) == addrlen && - !memcmp(&addr, name.beg, addrlen); - break; - } - } - } - } - - switch(matched) { - case 1: - /* an alternative name matched the server hostname */ - infof(data, " subjectAltName: %s matched", connssl->dispname); - return CURLE_OK; - case 0: - /* an alternative name field existed, but didn't match and then - we MUST fail */ - infof(data, " subjectAltName does not match %s", connssl->dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - - /* Process subject. */ - name.header = NULL; - name.beg = name.end = ""; - q = cert.subject.beg; - /* we have to look to the last occurrence of a commonName in the - distinguished one to get the most significant one. */ - while(q < cert.subject.end) { - q = getASN1Element(&dn, q, cert.subject.end); - if(!q) - break; - for(p = dn.beg; p < dn.end;) { - p = getASN1Element(&elem, p, dn.end); - if(!p) - return CURLE_PEER_FAILED_VERIFICATION; - /* We have a DN's AttributeTypeAndValue: check it in case it's a CN. */ - elem.beg = checkOID(elem.beg, elem.end, cnOID); - if(elem.beg) - name = elem; /* Latch CN. */ - } - } - - /* Check the CN if found. */ - if(!getASN1Element(&elem, name.beg, name.end)) - failf(data, "SSL: unable to obtain common name from peer certificate"); - else { - len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end); - if(len < 0) { - free(dnsname); - return CURLE_OUT_OF_MEMORY; - } - if(strlen(dnsname) != (size_t) len) /* Nul byte in string ? */ - failf(data, "SSL: illegal cert name field"); - else if(Curl_cert_hostcheck((const char *) dnsname, - len, connssl->peer.hostname, hostlen)) { - infof(data, " common name: %s (matched)", dnsname); - free(dnsname); - return CURLE_OK; + /* Build the certificate string. */ + result = Curl_dyn_add(&out, "-----BEGIN CERTIFICATE-----\n"); + if(!result) { + size_t j = 0; + + while(!result && (j < clen)) { + size_t chunksize = (clen - j) > 64 ? 64 : (clen - j); + result = Curl_dyn_addn(&out, &certptr[j], chunksize); + if(!result) + result = Curl_dyn_addn(&out, "\n", 1); + j += chunksize; } - else - failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", dnsname, connssl->dispname); - free(dnsname); + if(!result) + result = Curl_dyn_add(&out, "-----END CERTIFICATE-----\n"); } + free(certptr); + if(!result) + if(data->set.ssl.certinfo) + result = ssl_push_certinfo_dyn(data, certnum, "Cert", &out); - return CURLE_PEER_FAILED_VERIFICATION; +done: + Curl_dyn_free(&out); + return result; } -#endif /* WANT_VERIFYHOST */ +#endif /* WANT_EXTRACT_CERTINFO */ + +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ diff --git a/Utilities/cmcurl/lib/ws.c b/Utilities/cmcurl/lib/ws.c index adde531..d976518 100644 --- a/Utilities/cmcurl/lib/ws.c +++ b/Utilities/cmcurl/lib/ws.c @@ -24,7 +24,7 @@ #include "curl_setup.h" #include <curl/curl.h> -#ifdef USE_WEBSOCKETS +#if defined(USE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) #include "urldata.h" #include "bufq.h" @@ -225,6 +225,10 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, dec->payload_len = (dec->head[2] << 8) | dec->head[3]; break; case 10: + if(dec->head[2] > 127) { + failf(data, "WS: frame length longer than 64 signed not supported"); + return CURLE_RECV_ERROR; + } dec->payload_len = ((curl_off_t)dec->head[2] << 56) | (curl_off_t)dec->head[3] << 48 | (curl_off_t)dec->head[4] << 40 | @@ -296,7 +300,7 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, case WS_DEC_INIT: ws_dec_reset(dec); dec->state = WS_DEC_HEAD; - /* FALLTHROUGH */ + FALLTHROUGH(); case WS_DEC_HEAD: result = ws_dec_read_head(dec, data, inraw); if(result) { @@ -321,7 +325,7 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, dec->state = WS_DEC_INIT; break; } - /* FALLTHROUGH */ + FALLTHROUGH(); case WS_DEC_PAYLOAD: result = ws_dec_pass_payload(dec, data, inraw, write_payload, write_ctx); ws_dec_info(dec, data, "passing"); @@ -350,6 +354,136 @@ static void update_meta(struct websocket *ws, ws->frame.bytesleft = (payload_len - payload_offset - cur_len); } +/* WebSockets decoding client writer */ +struct ws_cw_ctx { + struct Curl_cwriter super; + struct bufq buf; +}; + +static CURLcode ws_cw_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct ws_cw_ctx *ctx = (struct ws_cw_ctx *)writer; + (void)data; + Curl_bufq_init2(&ctx->buf, WS_CHUNK_SIZE, 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer) +{ + struct ws_cw_ctx *ctx = (struct ws_cw_ctx *)writer; + (void) data; + Curl_bufq_free(&ctx->buf); +} + +struct ws_cw_dec_ctx { + struct Curl_easy *data; + struct websocket *ws; + struct Curl_cwriter *next_writer; + int cw_type; +}; + +static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen, + int frame_age, int frame_flags, + curl_off_t payload_offset, + curl_off_t payload_len, + void *user_data, + CURLcode *err) +{ + struct ws_cw_dec_ctx *ctx = user_data; + struct Curl_easy *data = ctx->data; + struct websocket *ws = ctx->ws; + curl_off_t remain = (payload_len - (payload_offset + buflen)); + + (void)frame_age; + if((frame_flags & CURLWS_PING) && !remain) { + /* auto-respond to PINGs, only works for single-frame payloads atm */ + size_t bytes; + infof(data, "WS: auto-respond to PING with a PONG"); + /* send back the exact same content as a PONG */ + *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG); + if(*err) + return -1; + } + else if(buflen || !remain) { + /* forward the decoded frame to the next client writer. */ + update_meta(ws, frame_age, frame_flags, payload_offset, + payload_len, buflen); + + *err = Curl_cwriter_write(data, ctx->next_writer, ctx->cw_type, + (const char *)buf, buflen); + if(*err) + return -1; + } + *err = CURLE_OK; + return (ssize_t)buflen; +} + +static CURLcode ws_cw_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + struct ws_cw_ctx *ctx = (struct ws_cw_ctx *)writer; + struct websocket *ws; + CURLcode result; + + if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + + ws = data->conn->proto.ws; + if(!ws) { + failf(data, "WS: not a websocket transfer"); + return CURLE_FAILED_INIT; + } + + if(nbytes) { + ssize_t nwritten; + nwritten = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf, + nbytes, &result); + if(nwritten < 0) { + infof(data, "WS: error adding data to buffer %d", result); + return result; + } + } + + while(!Curl_bufq_is_empty(&ctx->buf)) { + struct ws_cw_dec_ctx pass_ctx; + pass_ctx.data = data; + pass_ctx.ws = ws; + pass_ctx.next_writer = writer->next; + pass_ctx.cw_type = type; + result = ws_dec_pass(&ws->dec, data, &ctx->buf, + ws_cw_dec_next, &pass_ctx); + if(result == CURLE_AGAIN) + /* insufficient amount of data, keep it for later. + * we pretend to have written all since we have a copy */ + return CURLE_OK; + else if(result) { + infof(data, "WS: decode error %d", (int)result); + return result; + } + } + + if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) { + infof(data, "WS: decode ending with %zd frame bytes remaining", + Curl_bufq_len(&ctx->buf)); + return CURLE_RECV_ERROR; + } + + return CURLE_OK; +} + +/* WebSocket payload decoding client writer. */ +static const struct Curl_cwtype ws_cw_decode = { + "ws-decode", + NULL, + ws_cw_init, + ws_cw_write, + ws_cw_close, + sizeof(struct ws_cw_ctx) +}; + + static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data, const char *msg) { @@ -410,6 +544,13 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data, size_t hlen; ssize_t n; + if(payload_len < 0) { + failf(data, "WS: starting new frame with negative payload length %" + CURL_FORMAT_CURL_OFF_T, payload_len); + *err = CURLE_SEND_ERROR; + return -1; + } + if(enc->payload_remain > 0) { /* trying to write a new frame before the previous one is finished */ failf(data, "WS: starting new frame with %zd bytes from last one" @@ -607,6 +748,7 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, { struct SingleRequest *k = &data->req; struct websocket *ws; + struct Curl_cwriter *ws_dec_writer; CURLcode result; DEBUGASSERT(data->conn); @@ -616,7 +758,8 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, if(!ws) return CURLE_OUT_OF_MEMORY; data->conn->proto.ws = ws; - Curl_bufq_init(&ws->recvbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT); + Curl_bufq_init2(&ws->recvbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT, + BUFQ_OPT_SOFT_LIMIT); Curl_bufq_init2(&ws->sendbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT, BUFQ_OPT_SOFT_LIMIT); ws_dec_init(&ws->dec); @@ -655,6 +798,18 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x", ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]); + /* Install our client writer that decodes WS frames payload */ + result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode, + CURL_CW_CONTENT_DECODE); + if(result) + return result; + + result = Curl_cwriter_add(data, ws_dec_writer); + if(result) { + Curl_cwriter_free(data, ws_dec_writer); + return result; + } + if(data->set.connect_only) { ssize_t nwritten; /* In CONNECT_ONLY setup, the payloads from `mem` need to be received @@ -666,105 +821,15 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, return result; infof(data, "%zu bytes websocket payload", nread); } - k->upgr101 = UPGR101_RECEIVED; - - return result; -} - -static ssize_t ws_client_write(const unsigned char *buf, size_t buflen, - int frame_age, int frame_flags, - curl_off_t payload_offset, - curl_off_t payload_len, - void *userp, - CURLcode *err) -{ - struct Curl_easy *data = userp; - struct websocket *ws; - size_t wrote; - curl_off_t remain = (payload_len - (payload_offset + buflen)); - - (void)frame_age; - if(!data->conn || !data->conn->proto.ws) { - *err = CURLE_FAILED_INIT; - return -1; - } - ws = data->conn->proto.ws; - - if((frame_flags & CURLWS_PING) && !remain) { - /* auto-respond to PINGs, only works for single-frame payloads atm */ - size_t bytes; - infof(data, "WS: auto-respond to PING with a PONG"); - /* send back the exact same content as a PONG */ - *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG); - if(*err) - return -1; - } - else if(buflen || !remain) { - /* deliver the decoded frame to the user callback. The application - * may invoke curl_ws_meta() to access frame information. */ - update_meta(ws, frame_age, frame_flags, payload_offset, - payload_len, buflen); - Curl_set_in_callback(data, true); - wrote = data->set.fwrite_func((char *)buf, 1, - buflen, data->set.out); - Curl_set_in_callback(data, false); - if(wrote != buflen) { - *err = CURLE_RECV_ERROR; - return -1; + else { /* !connect_only */ + /* And pass any additional data to the writers */ + if(nread) { + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)mem, nread); } } - *err = CURLE_OK; - return (ssize_t)buflen; -} - -/* Curl_ws_writecb() is the write callback for websocket traffic. The - websocket data is provided to this raw, in chunks. This function should - handle/decode the data and call the "real" underlying callback accordingly. -*/ -size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */, - size_t nitems, void *userp) -{ - struct Curl_easy *data = userp; - - if(data->set.ws_raw_mode) - return data->set.fwrite_func(buffer, size, nitems, data->set.out); - else if(nitems) { - struct websocket *ws; - CURLcode result; - - if(!data->conn || !data->conn->proto.ws) { - failf(data, "WS: not a websocket transfer"); - return nitems - 1; - } - ws = data->conn->proto.ws; - - if(buffer) { - ssize_t nwritten; - - nwritten = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)buffer, - nitems, &result); - if(nwritten < 0) { - infof(data, "WS: error adding data to buffer %d", (int)result); - return nitems - 1; - } - buffer = NULL; - } - - while(!Curl_bufq_is_empty(&ws->recvbuf)) { + k->upgr101 = UPGR101_RECEIVED; - result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, - ws_client_write, data); - if(result == CURLE_AGAIN) - /* insufficient amount of data, keep it for later. - * we pretend to have written all since we have a copy */ - return nitems; - else if(result) { - infof(data, "WS: decode error %d", (int)result); - return nitems - 1; - } - } - } - return nitems; + return result; } struct ws_collect { @@ -997,8 +1062,11 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *data, const void *buffer, ws = data->conn->proto.ws; if(data->set.ws_raw_mode) { - if(fragsize || flags) + if(fragsize || flags) { + DEBUGF(infof(data, "ws_send: " + "fragsize and flags cannot be non-zero in raw mode")); return CURLE_BAD_FUNCTION_ARGUMENT; + } if(!buflen) /* nothing to do */ return CURLE_OK; @@ -1071,14 +1139,23 @@ static void ws_free(struct connectdata *conn) } } +static CURLcode ws_setup_conn(struct Curl_easy *data, + struct connectdata *conn) +{ + /* websockets is 1.1 only (for now) */ + data->state.httpwant = CURL_HTTP_VERSION_1_1; + return Curl_http_setup_conn(data, conn); +} + + void Curl_ws_done(struct Curl_easy *data) { (void)data; } -CURLcode Curl_ws_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) +static CURLcode ws_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) { (void)data; (void)dead_connection; @@ -1096,6 +1173,57 @@ CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) return NULL; } +const struct Curl_handler Curl_handler_ws = { + "WS", /* scheme */ + ws_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + Curl_http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ws_disconnect, /* disconnect */ + Curl_http_write_resp, /* write_resp */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTP, /* defport */ + CURLPROTO_WS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; + +#ifdef USE_SSL +const struct Curl_handler Curl_handler_wss = { + "WSS", /* scheme */ + ws_setup_conn, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + NULL, /* connecting */ + ZERO_NULL, /* doing */ + NULL, /* proto_getsock */ + Curl_http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ws_disconnect, /* disconnect */ + Curl_http_write_resp, /* write_resp */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTPS, /* defport */ + CURLPROTO_WSS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; +#endif + + #else CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, diff --git a/Utilities/cmcurl/lib/ws.h b/Utilities/cmcurl/lib/ws.h index 0308a42..5f40d45 100644 --- a/Utilities/cmcurl/lib/ws.h +++ b/Utilities/cmcurl/lib/ws.h @@ -25,7 +25,7 @@ ***************************************************************************/ #include "curl_setup.h" -#ifdef USE_WEBSOCKETS +#if defined(USE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) #ifdef USE_HYPER #define REQTYPE void @@ -75,11 +75,14 @@ struct websocket { CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req); CURLcode Curl_ws_accept(struct Curl_easy *data, const char *mem, size_t len); -size_t Curl_ws_writecb(char *buffer, size_t size, size_t nitems, void *userp); void Curl_ws_done(struct Curl_easy *data); -CURLcode Curl_ws_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection); + +extern const struct Curl_handler Curl_handler_ws; +#ifdef USE_SSL +extern const struct Curl_handler Curl_handler_wss; +#endif + + #else #define Curl_ws_request(x,y) CURLE_OK #define Curl_ws_done(x) Curl_nop_stmt |