diff options
Diffstat (limited to 'Utilities')
365 files changed, 14343 insertions, 9805 deletions
diff --git a/Utilities/Doxygen/CMakeLists.txt b/Utilities/Doxygen/CMakeLists.txt index b084dd5b98..fee21b65d6 100644 --- a/Utilities/Doxygen/CMakeLists.txt +++ b/Utilities/Doxygen/CMakeLists.txt @@ -3,7 +3,7 @@ if(NOT CMake_SOURCE_DIR) set(CMakeDeveloperReference_STANDALONE 1) - cmake_minimum_required(VERSION 3.13...3.24 FATAL_ERROR) + cmake_minimum_required(VERSION 3.13...3.25 FATAL_ERROR) get_filename_component(tmp "${CMAKE_CURRENT_SOURCE_DIR}" PATH) get_filename_component(CMake_SOURCE_DIR "${tmp}" PATH) include(${CMake_SOURCE_DIR}/Modules/CTestUseLaunchers.cmake) diff --git a/Utilities/Scripts/update-curl.bash b/Utilities/Scripts/update-curl.bash index 856ae8156e..791c8b5421 100755 --- a/Utilities/Scripts/update-curl.bash +++ b/Utilities/Scripts/update-curl.bash @@ -8,7 +8,7 @@ readonly name="curl" readonly ownership="Curl Upstream <curl-library@lists.haxx.se>" readonly subtree="Utilities/cmcurl" readonly repo="https://github.com/curl/curl.git" -readonly tag="curl-7_87_0" +readonly tag="curl-7_88_1" readonly shortlog=false readonly paths=" CMake/* diff --git a/Utilities/Sphinx/CMakeLists.txt b/Utilities/Sphinx/CMakeLists.txt index a9aa47d97a..bde6c6bf20 100644 --- a/Utilities/Sphinx/CMakeLists.txt +++ b/Utilities/Sphinx/CMakeLists.txt @@ -3,7 +3,7 @@ if(NOT CMake_SOURCE_DIR) set(CMakeHelp_STANDALONE 1) - cmake_minimum_required(VERSION 3.13...3.24 FATAL_ERROR) + cmake_minimum_required(VERSION 3.13...3.25 FATAL_ERROR) get_filename_component(tmp "${CMAKE_CURRENT_SOURCE_DIR}" PATH) get_filename_component(CMake_SOURCE_DIR "${tmp}" PATH) include(${CMake_SOURCE_DIR}/Modules/CTestUseLaunchers.cmake) diff --git a/Utilities/Sphinx/conf.py.in b/Utilities/Sphinx/conf.py.in index fc3ecb5fca..d4e40599ce 100644 --- a/Utilities/Sphinx/conf.py.in +++ b/Utilities/Sphinx/conf.py.in @@ -89,3 +89,11 @@ html_favicon = '@conf_path@/static/cmake-favicon.ico' # qthelp_qch_name = "CMake.qch" linkcheck_ignore = [r'about:|https://gitlab.kitware.com/cmake/community/-/wikis/doc/cpack'] + +linkcheck_allowed_redirects = { + r'https://cdash\.org': r'https://www\.cdash\.org/', + r'https://docs\.nvidia\.com/cuda/': r'https://docs\.nvidia\.com/cuda/index\.html', + r'https://learn\.microsoft\.com/en-us/cpp/c-language/parsing-c-command-line-arguments': r'https://learn\.microsoft\.com/en-us/cpp/c-language/parsing-c-command-line-arguments\?.*', + r'https://openjdk\.java\.net/jeps/313': r'https://openjdk\.org:443/jeps/313', + r'https://www\.sphinx-doc\.org': r'https://www\.sphinx-doc\.org/en/master/', +} diff --git a/Utilities/cmThirdPartyChecks.cmake b/Utilities/cmThirdPartyChecks.cmake index a38c10183c..0f2bdb4e7a 100644 --- a/Utilities/cmThirdPartyChecks.cmake +++ b/Utilities/cmThirdPartyChecks.cmake @@ -161,6 +161,7 @@ if(WIN32) set(HAVE_REGEX_H 0) set(HAVE_RSA_H 0) set(HAVE_SELECT 0) + set(HAVE_SENDMSG 0) set(HAVE_SETENV 0) set(HAVE_SETMODE 1) set(HAVE_SETRLIMIT 0) diff --git a/Utilities/cmcurl/CMake/CMakeConfigurableFile.in b/Utilities/cmcurl/CMake/CMakeConfigurableFile.in index b93e7539a1..a3d2bc4a28 100644 --- a/Utilities/cmcurl/CMake/CMakeConfigurableFile.in +++ b/Utilities/cmcurl/CMake/CMakeConfigurableFile.in @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake b/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake index 75215a122f..142e919449 100644 --- a/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake +++ b/Utilities/cmcurl/CMake/CurlSymbolHiding.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/CurlTests.c b/Utilities/cmcurl/CMake/CurlTests.c index 6a9fdea098..3dbba3cbb2 100644 --- a/Utilities/cmcurl/CMake/CurlTests.c +++ b/Utilities/cmcurl/CMake/CurlTests.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/CMake/FindBearSSL.cmake b/Utilities/cmcurl/CMake/FindBearSSL.cmake index 88d5e87ade..56a064eacd 100644 --- a/Utilities/cmcurl/CMake/FindBearSSL.cmake +++ b/Utilities/cmcurl/CMake/FindBearSSL.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindBrotli.cmake b/Utilities/cmcurl/CMake/FindBrotli.cmake index 833e1811ae..11ab7f8254 100644 --- a/Utilities/cmcurl/CMake/FindBrotli.cmake +++ b/Utilities/cmcurl/CMake/FindBrotli.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 @@ -28,7 +28,7 @@ find_path(BROTLI_INCLUDE_DIR "brotli/decode.h") find_library(BROTLICOMMON_LIBRARY NAMES brotlicommon) find_library(BROTLIDEC_LIBRARY NAMES brotlidec) -find_package_handle_standard_args(BROTLI +find_package_handle_standard_args(Brotli FOUND_VAR BROTLI_FOUND REQUIRED_VARS @@ -36,7 +36,7 @@ find_package_handle_standard_args(BROTLI BROTLICOMMON_LIBRARY BROTLI_INCLUDE_DIR FAIL_MESSAGE - "Could NOT find BROTLI" + "Could NOT find Brotli" ) set(BROTLI_INCLUDE_DIRS ${BROTLI_INCLUDE_DIR}) diff --git a/Utilities/cmcurl/CMake/FindCARES.cmake b/Utilities/cmcurl/CMake/FindCARES.cmake index 99cf31d913..fa75891189 100644 --- a/Utilities/cmcurl/CMake/FindCARES.cmake +++ b/Utilities/cmcurl/CMake/FindCARES.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindGSS.cmake b/Utilities/cmcurl/CMake/FindGSS.cmake index ec2bd57bae..b244e610ec 100644 --- a/Utilities/cmcurl/CMake/FindGSS.cmake +++ b/Utilities/cmcurl/CMake/FindGSS.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 @@ -181,14 +181,14 @@ if(NOT _GSS_FOUND) #not found by pkg-config. Let's take more traditional approac set(GSS_FLAVOUR "MIT") else() # prevent compiling the header - just check if we can include it - set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -D__ROKEN_H__") + list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__ROKEN_H__) check_include_file( "roken.h" _GSS_HAVE_ROKEN_H) check_include_file( "heimdal/roken.h" _GSS_HAVE_HEIMDAL_ROKEN_H) if(_GSS_HAVE_ROKEN_H OR _GSS_HAVE_HEIMDAL_ROKEN_H) set(GSS_FLAVOUR "Heimdal") endif() - set(CMAKE_REQUIRED_DEFINITIONS "") + list(REMOVE_ITEM CMAKE_REQUIRED_DEFINITIONS -D__ROKEN_H__) endif() else() # I'm not convinced if this is the right way but this is what autotools do at the moment diff --git a/Utilities/cmcurl/CMake/FindLibPSL.cmake b/Utilities/cmcurl/CMake/FindLibPSL.cmake index 66abdd79a3..e3bd68d1d7 100644 --- a/Utilities/cmcurl/CMake/FindLibPSL.cmake +++ b/Utilities/cmcurl/CMake/FindLibPSL.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindLibSSH2.cmake b/Utilities/cmcurl/CMake/FindLibSSH2.cmake index 0ec7f7e3b9..a0c251ae39 100644 --- a/Utilities/cmcurl/CMake/FindLibSSH2.cmake +++ b/Utilities/cmcurl/CMake/FindLibSSH2.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindMSH3.cmake b/Utilities/cmcurl/CMake/FindMSH3.cmake index 96477e2828..7d9c6b6544 100644 --- a/Utilities/cmcurl/CMake/FindMSH3.cmake +++ b/Utilities/cmcurl/CMake/FindMSH3.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindMbedTLS.cmake b/Utilities/cmcurl/CMake/FindMbedTLS.cmake index fcd6717f6f..814bd97da1 100644 --- a/Utilities/cmcurl/CMake/FindMbedTLS.cmake +++ b/Utilities/cmcurl/CMake/FindMbedTLS.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindNGHTTP2.cmake b/Utilities/cmcurl/CMake/FindNGHTTP2.cmake index 6d70c4a049..3957646c43 100644 --- a/Utilities/cmcurl/CMake/FindNGHTTP2.cmake +++ b/Utilities/cmcurl/CMake/FindNGHTTP2.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindNGHTTP3.cmake b/Utilities/cmcurl/CMake/FindNGHTTP3.cmake index 8d8ebc1b1e..9b13e6c6ff 100644 --- a/Utilities/cmcurl/CMake/FindNGHTTP3.cmake +++ b/Utilities/cmcurl/CMake/FindNGHTTP3.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindNGTCP2.cmake b/Utilities/cmcurl/CMake/FindNGTCP2.cmake index 61e54c2d67..9f4e9f233f 100644 --- a/Utilities/cmcurl/CMake/FindNGTCP2.cmake +++ b/Utilities/cmcurl/CMake/FindNGTCP2.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindNSS.cmake b/Utilities/cmcurl/CMake/FindNSS.cmake index 6742dda83f..ccddf422a5 100644 --- a/Utilities/cmcurl/CMake/FindNSS.cmake +++ b/Utilities/cmcurl/CMake/FindNSS.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindQUICHE.cmake b/Utilities/cmcurl/CMake/FindQUICHE.cmake index fc47027d74..0488463d55 100644 --- a/Utilities/cmcurl/CMake/FindQUICHE.cmake +++ b/Utilities/cmcurl/CMake/FindQUICHE.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindWolfSSL.cmake b/Utilities/cmcurl/CMake/FindWolfSSL.cmake index 986f01e00e..d67c0eb24d 100644 --- a/Utilities/cmcurl/CMake/FindWolfSSL.cmake +++ b/Utilities/cmcurl/CMake/FindWolfSSL.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/FindZstd.cmake b/Utilities/cmcurl/CMake/FindZstd.cmake index 2d6540443c..973e6ad4a9 100644 --- a/Utilities/cmcurl/CMake/FindZstd.cmake +++ b/Utilities/cmcurl/CMake/FindZstd.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/Macros.cmake b/Utilities/cmcurl/CMake/Macros.cmake index 4d7380eb5f..e12bf303f4 100644 --- a/Utilities/cmcurl/CMake/Macros.cmake +++ b/Utilities/cmcurl/CMake/Macros.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/OtherTests.cmake b/Utilities/cmcurl/CMake/OtherTests.cmake index ed8d28a216..fa1e458e61 100644 --- a/Utilities/cmcurl/CMake/OtherTests.cmake +++ b/Utilities/cmcurl/CMake/OtherTests.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake b/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake index 3cb4ffec3d..cef31b55fa 100644 --- a/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake +++ b/Utilities/cmcurl/CMake/Platforms/WindowsCache.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/Utilities.cmake b/Utilities/cmcurl/CMake/Utilities.cmake index 78bfd6ffe6..9ff38e3a0f 100644 --- a/Utilities/cmcurl/CMake/Utilities.cmake +++ b/Utilities/cmcurl/CMake/Utilities.cmake @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in b/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in index 2549d41f42..3e0742d1e4 100644 --- a/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in +++ b/Utilities/cmcurl/CMake/cmake_uninstall.cmake.in @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMake/curl-config.cmake.in b/Utilities/cmcurl/CMake/curl-config.cmake.in index 496a92d0e5..dbe4ed2102 100644 --- a/Utilities/cmcurl/CMake/curl-config.cmake.in +++ b/Utilities/cmcurl/CMake/curl-config.cmake.in @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 diff --git a/Utilities/cmcurl/CMakeLists.txt b/Utilities/cmcurl/CMakeLists.txt index 49016c8863..6fc316dda3 100644 --- a/Utilities/cmcurl/CMakeLists.txt +++ b/Utilities/cmcurl/CMakeLists.txt @@ -150,7 +150,7 @@ endif() # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 @@ -253,7 +253,7 @@ if(WIN32) set(CURL_TARGET_WINDOWS_VERSION "" CACHE STRING "Minimum target Windows version as hex string") if(CURL_TARGET_WINDOWS_VERSION) add_definitions(-D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}) - set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}") + list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}) set(CURL_TEST_DEFINES "${CURL_TEST_DEFINES} -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}") endif() endif() @@ -488,7 +488,7 @@ include(CheckCSourceCompiles) # On windows preload settings if(WIN32) - set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -D_WINSOCKAPI_=") + list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_WINSOCKAPI_=) include(${CMAKE_CURRENT_SOURCE_DIR}/CMake/Platforms/WindowsCache.cmake) endif() @@ -514,24 +514,6 @@ if(WIN32) endif() if(0) # This code not needed for building within CMake. -# This check below for use of deprecated symbols is only temporary and is to -# be removed again after a year's service. Remove after November 25, 2022. -set(CURL_RECONFIG_REQUIRED 0) -foreach(_LIB GSSAPI OPENLDAP LIBSSH LIBSSH2 BEARSSL MBEDTLS NSS OPENSSL - SCHANNEL SECTRANSP WOLFSSL) - if(CMAKE_USE_${_LIB}) - set(CURL_RECONFIG_REQUIRED 1) - message(SEND_ERROR "The option CMAKE_USE_${_LIB} was renamed to CURL_USE_${_LIB}.") - endif() -endforeach() -if(CMAKE_USE_WINSSL) - set(CURL_RECONFIG_REQUIRED 1) - message(SEND_ERROR "The option CMAKE_USE_WINSSL was renamed to CURL_USE_SCHANNEL.") -endif() -if(CURL_RECONFIG_REQUIRED) - message(FATAL_ERROR "Reconfig required") -endif() - # check SSL libraries # TODO support GnuTLS option(CURL_ENABLE_SSL "Enable SSL support" ON) @@ -577,7 +559,6 @@ if(CURL_USE_SCHANNEL) endif() if(CURL_WINDOWS_SSPI) set(USE_WINDOWS_SSPI ON) - set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -DSECURITY_WIN32") endif() if(CURL_USE_SECTRANSP) @@ -630,8 +611,6 @@ if(CURL_USE_OPENSSL) if(CURL_CA_PATH) add_definitions(-DCURL_CA_PATH="${CURL_CA_PATH}") endif() - - add_definitions(-DOPENSSL_SUPPRESS_DEPRECATED) endif() if(CURL_USE_MBEDTLS) @@ -826,7 +805,7 @@ if(NOT CURL_DISABLE_LDAP) return 0; }" ) - set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -DLDAP_DEPRECATED=1") + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DLDAP_DEPRECATED=1) list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_LDAP_LIB}) if(HAVE_LIBLBER) list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_LBER_LIB}) @@ -1204,6 +1183,7 @@ check_symbol_exists(socket "${CURL_INCLUDES}" HAVE_SOCKET) check_symbol_exists(socketpair "${CURL_INCLUDES}" HAVE_SOCKETPAIR) check_symbol_exists(recv "${CURL_INCLUDES}" HAVE_RECV) check_symbol_exists(send "${CURL_INCLUDES}" HAVE_SEND) +check_symbol_exists(sendmsg "${CURL_INCLUDES}" HAVE_SENDMSG) check_symbol_exists(select "${CURL_INCLUDES}" HAVE_SELECT) check_symbol_exists(strdup "${CURL_INCLUDES}" HAVE_STRDUP) check_symbol_exists(strtok_r "${CURL_INCLUDES}" HAVE_STRTOK_R) @@ -1248,7 +1228,7 @@ check_symbol_exists(setrlimit "${CURL_INCLUDES}" HAVE_SETRLIMIT) if(NOT MSVC OR (MSVC_VERSION GREATER_EQUAL 1900)) # earlier MSVC compilers had faulty snprintf implementations - check_symbol_exists(snprintf "${CURL_INCLUDES}" HAVE_SNPRINTF) + check_symbol_exists(snprintf "stdio.h" HAVE_SNPRINTF) endif() check_function_exists(mach_absolute_time HAVE_MACH_ABSOLUTE_TIME) check_symbol_exists(inet_ntop "${CURL_INCLUDES}" HAVE_INET_NTOP) @@ -1451,7 +1431,7 @@ if(WIN32) # Check if crypto functions in wincrypt.h are actually available if(HAVE_WINCRYPT_H) - check_symbol_exists(CryptAcquireContext "${CURL_INCLUDES}" USE_WINCRYPT) + check_symbol_exists(CryptAcquireContext "windows.h;wincrypt.h" USE_WINCRYPT) endif() if(USE_WINCRYPT) set(USE_WIN32_CRYPTO ON) diff --git a/Utilities/cmcurl/COPYING b/Utilities/cmcurl/COPYING index 90f05adf25..d1eab3eb93 100644 --- a/Utilities/cmcurl/COPYING +++ b/Utilities/cmcurl/COPYING @@ -1,6 +1,6 @@ COPYRIGHT AND PERMISSION NOTICE -Copyright (c) 1996 - 2022, Daniel Stenberg, <daniel@haxx.se>, and many +Copyright (c) 1996 - 2023, 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 b354757afc..c5ce0a59b5 100644 --- a/Utilities/cmcurl/include/curl/curl.h +++ b/Utilities/cmcurl/include/curl/curl.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -167,7 +167,7 @@ typedef enum { CURLSSLBACKEND_SECURETRANSPORT = 9, CURLSSLBACKEND_AXTLS CURL_DEPRECATED(7.61.0, "") = 10, CURLSSLBACKEND_MBEDTLS = 11, - CURLSSLBACKEND_MESALINK = 12, + CURLSSLBACKEND_MESALINK CURL_DEPRECATED(7.82.0, "") = 12, CURLSSLBACKEND_BEARSSL = 13, CURLSSLBACKEND_RUSTLS = 14 } curl_sslbackend; @@ -248,7 +248,7 @@ typedef int (*curl_xferinfo_callback)(void *clientp, #ifndef CURL_MAX_READ_SIZE /* The maximum receive buffer size configurable via CURLOPT_BUFFERSIZE. */ -#define CURL_MAX_READ_SIZE 524288 +#define CURL_MAX_READ_SIZE (10*1024*1024) #endif #ifndef CURL_MAX_WRITE_SIZE @@ -2259,8 +2259,13 @@ enum { CURL_HTTP_VERSION_2TLS, /* use version 2 for HTTPS, version 1.1 for HTTP */ CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE, /* please use HTTP 2 without HTTP/1.1 Upgrade */ - CURL_HTTP_VERSION_3 = 30, /* Makes use of explicit HTTP/3 without fallback. - Use CURLOPT_ALTSVC to enable HTTP/3 upgrade */ + CURL_HTTP_VERSION_3 = 30, /* Use HTTP/3, fallback to HTTP/2 or HTTP/1 if + needed. For HTTPS only. For HTTP, this option + makes libcurl return error. */ + CURL_HTTP_VERSION_3ONLY = 31, /* Use HTTP/3 without fallback. For HTTPS + only. For HTTP, this makes libcurl + return error. */ + CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */ }; @@ -2953,6 +2958,7 @@ typedef enum { CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_DATA_CONNECT, CURL_LOCK_DATA_PSL, + CURL_LOCK_DATA_HSTS, CURL_LOCK_DATA_LAST } curl_lock_data; diff --git a/Utilities/cmcurl/include/curl/curlver.h b/Utilities/cmcurl/include/curl/curlver.h index 94db4cde00..ce39cdc5b8 100644 --- a/Utilities/cmcurl/include/curl/curlver.h +++ b/Utilities/cmcurl/include/curl/curlver.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -28,17 +28,17 @@ a script at release-time. This was made its own header file in 7.11.2 */ /* This is the global package copyright */ -#define LIBCURL_COPYRIGHT "1996 - 2022 Daniel Stenberg, <daniel@haxx.se>." +#define LIBCURL_COPYRIGHT "Daniel Stenberg, <daniel@haxx.se>." /* This is the version number of the libcurl package from which this header file origins: */ -#define LIBCURL_VERSION "7.87.0" +#define LIBCURL_VERSION "7.88.1" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 7 -#define LIBCURL_VERSION_MINOR 87 -#define LIBCURL_VERSION_PATCH 0 +#define LIBCURL_VERSION_MINOR 88 +#define LIBCURL_VERSION_PATCH 1 /* This is the numeric version of the libcurl version number, meant for easier parsing and comparisons by programs. The LIBCURL_VERSION_NUM define will @@ -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 0x075700 +#define LIBCURL_VERSION_NUM 0x075801 /* * This is the date and time when the full source package was created. The diff --git a/Utilities/cmcurl/include/curl/easy.h b/Utilities/cmcurl/include/curl/easy.h index 98ee8888ed..394668a8fa 100644 --- a/Utilities/cmcurl/include/curl/easy.h +++ b/Utilities/cmcurl/include/curl/easy.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/include/curl/header.h b/Utilities/cmcurl/include/curl/header.h index 1598c6f113..8df11e1e42 100644 --- a/Utilities/cmcurl/include/curl/header.h +++ b/Utilities/cmcurl/include/curl/header.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/include/curl/mprintf.h b/Utilities/cmcurl/include/curl/mprintf.h index 06ef5c6d8c..e652a6520e 100644 --- a/Utilities/cmcurl/include/curl/mprintf.h +++ b/Utilities/cmcurl/include/curl/mprintf.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/include/curl/multi.h b/Utilities/cmcurl/include/curl/multi.h index c956d28e8f..30a3d93017 100644 --- a/Utilities/cmcurl/include/curl/multi.h +++ b/Utilities/cmcurl/include/curl/multi.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/include/curl/options.h b/Utilities/cmcurl/include/curl/options.h index a792687cf4..1ed76a95c6 100644 --- a/Utilities/cmcurl/include/curl/options.h +++ b/Utilities/cmcurl/include/curl/options.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/include/curl/stdcheaders.h b/Utilities/cmcurl/include/curl/stdcheaders.h index 82e1b5fef6..7451aa3052 100644 --- a/Utilities/cmcurl/include/curl/stdcheaders.h +++ b/Utilities/cmcurl/include/curl/stdcheaders.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/include/curl/system.h b/Utilities/cmcurl/include/curl/system.h index 11db51ecf3..def7739242 100644 --- a/Utilities/cmcurl/include/curl/system.h +++ b/Utilities/cmcurl/include/curl/system.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -227,16 +227,14 @@ # define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int #elif defined(__OS400__) -# if defined(__ILEC400__) -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t -# define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_SYS_SOCKET_H 1 -# endif +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 #elif defined(__MVS__) # if defined(__IBMC__) || defined(__IBMCPP__) diff --git a/Utilities/cmcurl/include/curl/typecheck-gcc.h b/Utilities/cmcurl/include/curl/typecheck-gcc.h index bf655bb632..bc8d7a78ce 100644 --- a/Utilities/cmcurl/include/curl/typecheck-gcc.h +++ b/Utilities/cmcurl/include/curl/typecheck-gcc.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -42,9 +42,8 @@ */ #define curl_easy_setopt(handle, option, value) \ __extension__({ \ - CURL_IGNORE_DEPRECATION(__typeof__(option) _curl_opt = option;) \ + CURLoption _curl_opt = (option); \ if(__builtin_constant_p(_curl_opt)) { \ - (void) option; \ CURL_IGNORE_DEPRECATION( \ if(curlcheck_long_option(_curl_opt)) \ if(!curlcheck_long(value)) \ @@ -120,9 +119,8 @@ /* wraps curl_easy_getinfo() with typechecking */ #define curl_easy_getinfo(handle, info, arg) \ __extension__({ \ - CURL_IGNORE_DEPRECATION(__typeof__(info) _curl_info = info;) \ + CURLINFO _curl_info = (info); \ if(__builtin_constant_p(_curl_info)) { \ - (void) info; \ CURL_IGNORE_DEPRECATION( \ if(curlcheck_string_info(_curl_info)) \ if(!curlcheck_arr((arg), char *)) \ diff --git a/Utilities/cmcurl/include/curl/urlapi.h b/Utilities/cmcurl/include/curl/urlapi.h index e15c213ccd..b97b53475a 100644 --- a/Utilities/cmcurl/include/curl/urlapi.h +++ b/Utilities/cmcurl/include/curl/urlapi.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -62,6 +62,7 @@ typedef enum { CURLUE_BAD_SCHEME, /* 27 */ CURLUE_BAD_SLASHES, /* 28 */ CURLUE_BAD_USER, /* 29 */ + CURLUE_LACKS_IDN, /* 30 */ CURLUE_LAST } CURLUcode; @@ -95,6 +96,7 @@ typedef enum { #define CURLU_NO_AUTHORITY (1<<10) /* Allow empty authority when the scheme is unknown. */ #define CURLU_ALLOW_SPACE (1<<11) /* Allow spaces in the URL */ +#define CURLU_PUNYCODE (1<<12) /* get the host name in pynycode */ typedef struct Curl_URL CURLU; diff --git a/Utilities/cmcurl/include/curl/websockets.h b/Utilities/cmcurl/include/curl/websockets.h index 4d57f91e56..fd6a916547 100644 --- a/Utilities/cmcurl/include/curl/websockets.h +++ b/Utilities/cmcurl/include/curl/websockets.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -33,6 +33,7 @@ struct curl_ws_frame { int flags; /* See the CURLWS_* defines */ curl_off_t offset; /* the offset of this data into the frame */ curl_off_t bytesleft; /* number of pending bytes left of the payload */ + size_t len; /* size of the current data chunk */ }; /* flag bits */ diff --git a/Utilities/cmcurl/lib/CMakeLists.txt b/Utilities/cmcurl/lib/CMakeLists.txt index 074ab9dac7..15853ff72b 100644 --- a/Utilities/cmcurl/lib/CMakeLists.txt +++ b/Utilities/cmcurl/lib/CMakeLists.txt @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 @@ -136,11 +136,12 @@ set_target_properties(${LIB_NAME} PROPERTIES if(0) # This code not needed for building within CMake. if(CMAKE_SYSTEM_NAME STREQUAL "AIX" OR CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "GNU/kFreeBSD" OR # FreeBSD comes with the a.out and elf flavours # but a.out was supported up to version 3.x and - # elf from 3.x. I cannot imagine someone runnig + # elf from 3.x. I cannot imagine someone running # CMake on those ancient systems CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR diff --git a/Utilities/cmcurl/lib/Makefile.inc b/Utilities/cmcurl/lib/Makefile.inc index 9eafa93b9a..c28b4756b0 100644 --- a/Utilities/cmcurl/lib/Makefile.inc +++ b/Utilities/cmcurl/lib/Makefile.inc @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. +# 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 @@ -79,16 +79,17 @@ LIB_VTLS_HFILES = \ vtls/x509asn1.h LIB_VQUIC_CFILES = \ - vquic/msh3.c \ - vquic/ngtcp2.c \ - vquic/quiche.c \ + vquic/curl_msh3.c \ + vquic/curl_ngtcp2.c \ + vquic/curl_quiche.c \ vquic/vquic.c LIB_VQUIC_HFILES = \ - vquic/msh3.h \ - vquic/ngtcp2.h \ - vquic/quiche.h \ - vquic/vquic.h + vquic/curl_msh3.h \ + vquic/curl_ngtcp2.h \ + vquic/curl_quiche.h \ + vquic/vquic.h \ + vquic/vquic_int.h LIB_VSSH_CFILES = \ vssh/libssh.c \ @@ -106,6 +107,8 @@ LIB_CFILES = \ base64.c \ bufref.c \ c-hyper.c \ + cf-http.c \ + cf-socket.c \ cfilters.c \ conncache.c \ connect.c \ @@ -118,6 +121,7 @@ LIB_CFILES = \ curl_get_line.c \ curl_gethostname.c \ curl_gssapi.c \ + curl_log.c \ curl_memrchr.c \ curl_multibyte.c \ curl_ntlm_core.c \ @@ -229,6 +233,8 @@ LIB_HFILES = \ asyn.h \ bufref.h \ c-hyper.h \ + cf-http.h \ + cf-socket.h \ cfilters.h \ conncache.h \ connect.h \ @@ -246,6 +252,7 @@ LIB_HFILES = \ curl_hmac.h \ curl_krb5.h \ curl_ldap.h \ + curl_log.h \ curl_md4.h \ curl_md5.h \ curl_memory.h \ @@ -312,7 +319,6 @@ LIB_HFILES = \ pop3.h \ progress.h \ psl.h \ - quic.h \ rand.h \ rename.h \ rtsp.h \ diff --git a/Utilities/cmcurl/lib/altsvc.c b/Utilities/cmcurl/lib/altsvc.c index ec18e381c8..31a7abcc5d 100644 --- a/Utilities/cmcurl/lib/altsvc.c +++ b/Utilities/cmcurl/lib/altsvc.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/altsvc.h b/Utilities/cmcurl/lib/altsvc.h index 2751d272a1..7fea1434a5 100644 --- a/Utilities/cmcurl/lib/altsvc.h +++ b/Utilities/cmcurl/lib/altsvc.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/amigaos.c b/Utilities/cmcurl/lib/amigaos.c index e8c2fc02e0..b0a9500267 100644 --- a/Utilities/cmcurl/lib/amigaos.c +++ b/Utilities/cmcurl/lib/amigaos.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/amigaos.h b/Utilities/cmcurl/lib/amigaos.h index 9abfb59eac..7997ede80e 100644 --- a/Utilities/cmcurl/lib/amigaos.h +++ b/Utilities/cmcurl/lib/amigaos.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/arpa_telnet.h b/Utilities/cmcurl/lib/arpa_telnet.h index 523f7f51e7..de1373800c 100644 --- a/Utilities/cmcurl/lib/arpa_telnet.h +++ b/Utilities/cmcurl/lib/arpa_telnet.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/asyn-ares.c b/Utilities/cmcurl/lib/asyn-ares.c index 4436da3c4d..19fe8536b0 100644 --- a/Utilities/cmcurl/lib/asyn-ares.c +++ b/Utilities/cmcurl/lib/asyn-ares.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/asyn-thread.c b/Utilities/cmcurl/lib/asyn-thread.c index 705f0f612a..4d7f8605f7 100644 --- a/Utilities/cmcurl/lib/asyn-thread.c +++ b/Utilities/cmcurl/lib/asyn-thread.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/asyn.h b/Utilities/cmcurl/lib/asyn.h index 1aab21aaa7..7e207c4f56 100644 --- a/Utilities/cmcurl/lib/asyn.h +++ b/Utilities/cmcurl/lib/asyn.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/base64.c b/Utilities/cmcurl/lib/base64.c index bacd627cf4..e1b7b7287a 100644 --- a/Utilities/cmcurl/lib/base64.c +++ b/Utilities/cmcurl/lib/base64.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/bufref.c b/Utilities/cmcurl/lib/bufref.c index 91b037431d..ce686b6f37 100644 --- a/Utilities/cmcurl/lib/bufref.c +++ b/Utilities/cmcurl/lib/bufref.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2021 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/bufref.h b/Utilities/cmcurl/lib/bufref.h index 96b818b537..dd424f18f5 100644 --- a/Utilities/cmcurl/lib/bufref.h +++ b/Utilities/cmcurl/lib/bufref.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2021 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/c-hyper.c b/Utilities/cmcurl/lib/c-hyper.c index 65f5581aee..9c7632d354 100644 --- a/Utilities/cmcurl/lib/c-hyper.c +++ b/Utilities/cmcurl/lib/c-hyper.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -485,7 +485,7 @@ CURLcode Curl_hyper_stream(struct Curl_easy *data, if(k->upgr101 == UPGR101_WS) { if(http_status == 101) { /* verify the response */ - result = Curl_ws_accept(data); + result = Curl_ws_accept(data, NULL, 0); if(result) return result; } @@ -1128,6 +1128,16 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) goto error; } +#ifdef HAVE_LIBZ + /* we only consider transfer-encoding magic if libz support is built-in */ + result = Curl_transferencode(data); + if(result) + goto error; + result = Curl_hyper_header(data, headers, data->state.aptr.te); + if(result) + goto error; +#endif + if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && data->set.str[STRING_ENCODING]) { Curl_safefree(data->state.aptr.accept_encoding); @@ -1144,16 +1154,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) else Curl_safefree(data->state.aptr.accept_encoding); -#ifdef HAVE_LIBZ - /* we only consider transfer-encoding magic if libz support is built-in */ - result = Curl_transferencode(data); - if(result) - goto error; - result = Curl_hyper_header(data, headers, data->state.aptr.te); - if(result) - goto error; -#endif - result = cookies(data, conn, headers); if(result) goto error; diff --git a/Utilities/cmcurl/lib/c-hyper.h b/Utilities/cmcurl/lib/c-hyper.h index 70507ad2a4..4218cda75f 100644 --- a/Utilities/cmcurl/lib/c-hyper.h +++ b/Utilities/cmcurl/lib/c-hyper.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/cf-http.c b/Utilities/cmcurl/lib/cf-http.c new file mode 100644 index 0000000000..2ee3d4da60 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-http.c @@ -0,0 +1,518 @@ +/*************************************************************************** + * _ _ ____ _ + * 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(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + +#include "urldata.h" +#include <curl/curl.h> +#include "curl_log.h" +#include "cfilters.h" +#include "connect.h" +#include "multiif.h" +#include "cf-http.h" +#include "http2.h" +#include "vquic/vquic.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +typedef enum { + CF_HC_INIT, + CF_HC_CONNECT, + CF_HC_SUCCESS, + CF_HC_FAILURE +} cf_hc_state; + +struct cf_hc_baller { + const char *name; + struct Curl_cfilter *cf; + CURLcode result; + struct curltime started; + int reply_ms; + bool enabled; +}; + +static void cf_hc_baller_reset(struct cf_hc_baller *b, + struct Curl_easy *data) +{ + if(b->cf) { + Curl_conn_cf_close(b->cf, data); + Curl_conn_cf_discard_chain(&b->cf, data); + b->cf = NULL; + } + b->result = CURLE_OK; + b->reply_ms = -1; +} + +static bool cf_hc_baller_is_active(struct cf_hc_baller *b) +{ + return b->enabled && b->cf && !b->result; +} + +static bool cf_hc_baller_has_started(struct cf_hc_baller *b) +{ + return !!b->cf; +} + +static int cf_hc_baller_reply_ms(struct cf_hc_baller *b, + struct Curl_easy *data) +{ + if(b->reply_ms < 0) + b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS, + &b->reply_ms, NULL); + return b->reply_ms; +} + +static bool cf_hc_baller_data_pending(struct cf_hc_baller *b, + const struct Curl_easy *data) +{ + return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data); +} + +struct cf_hc_ctx { + cf_hc_state state; + const struct Curl_dns_entry *remotehost; + struct curltime started; /* when connect started */ + CURLcode result; /* overall result */ + struct cf_hc_baller h3_baller; + struct cf_hc_baller h21_baller; + int soft_eyeballs_timeout_ms; + int hard_eyeballs_timeout_ms; +}; + +static void cf_hc_baller_init(struct cf_hc_baller *b, + struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *name, + int transport) +{ + struct cf_hc_ctx *ctx = cf->ctx; + struct Curl_cfilter *save = cf->next; + + b->name = name; + cf->next = NULL; + b->started = Curl_now(); + b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost, + transport, CURL_CF_SSL_ENABLE); + b->cf = cf->next; + cf->next = save; +} + +static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b, + struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + struct Curl_cfilter *save = cf->next; + + cf->next = b->cf; + b->result = Curl_conn_cf_connect(cf->next, data, FALSE, done); + b->cf = cf->next; /* it might mutate */ + cf->next = save; + return b->result; +} + +static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_hc_ctx *ctx = cf->ctx; + + if(ctx) { + cf_hc_baller_reset(&ctx->h3_baller, data); + cf_hc_baller_reset(&ctx->h21_baller, data); + ctx->state = CF_HC_INIT; + ctx->result = CURLE_OK; + ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout; + ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2; + } +} + +static CURLcode baller_connected(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_hc_baller *winner) +{ + struct cf_hc_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + DEBUGASSERT(winner->cf); + if(winner != &ctx->h3_baller) + cf_hc_baller_reset(&ctx->h3_baller, data); + if(winner != &ctx->h21_baller) + cf_hc_baller_reset(&ctx->h21_baller, data); + + DEBUGF(LOG_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms", + winner->name, (int)Curl_timediff(Curl_now(), winner->started), + cf_hc_baller_reply_ms(winner, data))); + cf->next = winner->cf; + winner->cf = NULL; + + switch(cf->conn->alpn) { + case CURL_HTTP_VERSION_3: + infof(data, "using HTTP/3"); + break; + case CURL_HTTP_VERSION_2: +#ifdef USE_NGHTTP2 + /* Using nghttp2, we add the filter "below" us, so when the conn + * closes, we tear it down for a fresh reconnect */ + result = Curl_http2_switch_at(cf, data); + if(result) { + ctx->state = CF_HC_FAILURE; + ctx->result = result; + return result; + } +#endif + infof(data, "using HTTP/2"); + break; + case CURL_HTTP_VERSION_1_1: + infof(data, "using HTTP/1.1"); + break; + default: + infof(data, "using HTTP/1.x"); + break; + } + ctx->state = CF_HC_SUCCESS; + cf->connected = TRUE; + Curl_conn_cf_cntrl(cf->next, data, TRUE, + CF_CTRL_CONN_INFO_UPDATE, 0, NULL); + return result; +} + + +static bool time_to_start_h21(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct curltime now) +{ + struct cf_hc_ctx *ctx = cf->ctx; + timediff_t elapsed_ms; + + if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller)) + return FALSE; + + if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller)) + return TRUE; + + elapsed_ms = Curl_timediff(now, ctx->started); + if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) { + DEBUGF(LOG_CF(data, cf, "hard timeout of %dms reached, starting h21", + ctx->hard_eyeballs_timeout_ms)); + return TRUE; + } + + if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) { + if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) { + DEBUGF(LOG_CF(data, cf, "soft timeout of %dms reached, h3 has not " + "seen any data, starting h21", + ctx->soft_eyeballs_timeout_ms)); + return TRUE; + } + /* set the effective hard timeout again */ + Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms, + EXPIRE_ALPN_EYEBALLS); + } + return FALSE; +} + +static CURLcode cf_hc_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_hc_ctx *ctx = cf->ctx; + struct curltime now; + CURLcode result = CURLE_OK; + + (void)blocking; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + now = Curl_now(); + switch(ctx->state) { + case CF_HC_INIT: + DEBUGASSERT(!ctx->h3_baller.cf); + DEBUGASSERT(!ctx->h21_baller.cf); + DEBUGASSERT(!cf->next); + DEBUGF(LOG_CF(data, cf, "connect, init")); + ctx->started = now; + if(ctx->h3_baller.enabled) { + cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC); + if(ctx->h21_baller.enabled) + Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); + } + else if(ctx->h21_baller.enabled) + cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", TRNSPRT_TCP); + ctx->state = CF_HC_CONNECT; + /* FALLTHROUGH */ + + case CF_HC_CONNECT: + if(cf_hc_baller_is_active(&ctx->h3_baller)) { + result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done); + if(!result && *done) { + result = baller_connected(cf, data, &ctx->h3_baller); + goto out; + } + } + + if(time_to_start_h21(cf, data, now)) { + cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", TRNSPRT_TCP); + } + + if(cf_hc_baller_is_active(&ctx->h21_baller)) { + DEBUGF(LOG_CF(data, cf, "connect, check h21")); + result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done); + if(!result && *done) { + result = baller_connected(cf, data, &ctx->h21_baller); + goto out; + } + } + + if((!ctx->h3_baller.enabled || ctx->h3_baller.result) && + (!ctx->h21_baller.enabled || ctx->h21_baller.result)) { + /* both failed or disabled. we give up */ + DEBUGF(LOG_CF(data, cf, "connect, all failed")); + result = ctx->result = ctx->h3_baller.enabled? + ctx->h3_baller.result : ctx->h21_baller.result; + ctx->state = CF_HC_FAILURE; + goto out; + } + result = CURLE_OK; + *done = FALSE; + break; + + case CF_HC_FAILURE: + result = ctx->result; + cf->connected = FALSE; + *done = FALSE; + break; + + case CF_HC_SUCCESS: + result = CURLE_OK; + cf->connected = TRUE; + *done = TRUE; + break; + } + +out: + DEBUGF(LOG_CF(data, cf, "connect -> %d, done=%d", result, *done)); + return result; +} + +static int cf_hc_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *socks) +{ + struct cf_hc_ctx *ctx = cf->ctx; + size_t i, j, s; + int brc, rc = GETSOCK_BLANK; + curl_socket_t bsocks[MAX_SOCKSPEREASYHANDLE]; + struct cf_hc_baller *ballers[2]; + + if(cf->connected) + return cf->next->cft->get_select_socks(cf->next, data, socks); + + ballers[0] = &ctx->h3_baller; + ballers[1] = &ctx->h21_baller; + for(i = s = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { + struct cf_hc_baller *b = ballers[i]; + if(!cf_hc_baller_is_active(b)) + continue; + brc = Curl_conn_cf_get_select_socks(b->cf, data, bsocks); + DEBUGF(LOG_CF(data, cf, "get_selected_socks(%s) -> %x", b->name, brc)); + if(!brc) + continue; + for(j = 0; j < MAX_SOCKSPEREASYHANDLE && s < MAX_SOCKSPEREASYHANDLE; ++j) { + if((brc & GETSOCK_WRITESOCK(j)) || (brc & GETSOCK_READSOCK(j))) { + socks[s] = bsocks[j]; + if(brc & GETSOCK_WRITESOCK(j)) + rc |= GETSOCK_WRITESOCK(s); + if(brc & GETSOCK_READSOCK(j)) + rc |= GETSOCK_READSOCK(s); + s++; + } + } + } + DEBUGF(LOG_CF(data, cf, "get_selected_socks -> %x", rc)); + return rc; +} + +static bool cf_hc_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_hc_ctx *ctx = cf->ctx; + + if(cf->connected) + return cf->next->cft->has_data_pending(cf->next, data); + + DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data_pending")); + return cf_hc_baller_data_pending(&ctx->h3_baller, data) + || cf_hc_baller_data_pending(&ctx->h21_baller, data); +} + +static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + DEBUGF(LOG_CF(data, cf, "close")); + cf_hc_reset(cf, data); + cf->connected = FALSE; + + if(cf->next) { + cf->next->cft->close(cf->next, data); + Curl_conn_cf_discard_chain(&cf->next, data); + } +} + +static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_hc_ctx *ctx = cf->ctx; + + (void)data; + DEBUGF(LOG_CF(data, cf, "destroy")); + cf_hc_reset(cf, data); + Curl_safefree(ctx); +} + +struct Curl_cftype Curl_cft_http_connect = { + "HTTPS-CONNECT", + 0, + CURL_LOG_DEFAULT, + cf_hc_destroy, + cf_hc_connect, + cf_hc_close, + Curl_cf_def_get_host, + cf_hc_get_select_socks, + cf_hc_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode cf_hc_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + bool try_h3, bool try_h21) +{ + struct Curl_cfilter *cf = NULL; + struct cf_hc_ctx *ctx; + CURLcode result = CURLE_OK; + + (void)data; + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->remotehost = remotehost; + ctx->h3_baller.enabled = try_h3; + ctx->h21_baller.enabled = try_h21; + + result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx); + if(result) + goto out; + ctx = NULL; + cf_hc_reset(cf, data); + +out: + *pcf = result? NULL : cf; + free(ctx); + return result; +} + +CURLcode Curl_cf_http_connect_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + bool try_h3, bool try_h21) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); +out: + return result; +} + +CURLcode +Curl_cf_http_connect_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + bool try_h3, bool try_h21) +{ + struct Curl_cfilter *cf; + CURLcode result; + + DEBUGASSERT(data); + result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); +out: + return result; +} + +CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost) +{ + bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */ + CURLcode result = CURLE_OK; + + (void)sockindex; + (void)remotehost; + + if(!conn->bits.tls_enable_alpn) + goto out; + + if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { + result = Curl_conn_may_http3(data, conn); + if(result) /* can't do it */ + goto out; + try_h3 = TRUE; + try_h21 = FALSE; + } + else if(data->state.httpwant >= CURL_HTTP_VERSION_3) { + /* We assume that silently not even trying H3 is ok here */ + /* TODO: should we fail instead? */ + try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK); + try_h21 = TRUE; + } + + result = Curl_cf_http_connect_add(data, conn, sockindex, remotehost, + try_h3, try_h21); +out: + return result; +} + +#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ diff --git a/Utilities/cmcurl/lib/cf-http.h b/Utilities/cmcurl/lib/cf-http.h new file mode 100644 index 0000000000..6a39527317 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-http.h @@ -0,0 +1,58 @@ +#ifndef HEADER_CURL_CF_HTTP_H +#define HEADER_CURL_CF_HTTP_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(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + +struct Curl_cfilter; +struct Curl_easy; +struct connectdata; +struct Curl_cftype; +struct Curl_dns_entry; + +extern struct Curl_cftype Curl_cft_http_connect; + +CURLcode Curl_cf_http_connect_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + bool try_h3, bool try_h21); + +CURLcode +Curl_cf_http_connect_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + bool try_h3, bool try_h21); + + +CURLcode Curl_cf_https_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost); + + +#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ +#endif /* HEADER_CURL_CF_HTTP_H */ diff --git a/Utilities/cmcurl/lib/cf-socket.c b/Utilities/cmcurl/lib/cf-socket.c new file mode 100644 index 0000000000..2549f3456b --- /dev/null +++ b/Utilities/cmcurl/lib/cf-socket.c @@ -0,0 +1,1908 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> /* <netinet/tcp.h> may need it */ +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> /* for sockaddr_un */ +#endif +#ifdef HAVE_LINUX_TCP_H +#include <linux/tcp.h> +#elif defined(HAVE_NETINET_TCP_H) +#include <netinet/tcp.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#ifdef __VMS +#include <in.h> +#include <inet.h> +#endif + +#include "urldata.h" +#include "sendf.h" +#include "if2ip.h" +#include "strerror.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "connect.h" +#include "select.h" +#include "url.h" /* for Curl_safefree() */ +#include "multiif.h" +#include "sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "inet_ntop.h" +#include "inet_pton.h" +#include "progress.h" +#include "warnless.h" +#include "conncache.h" +#include "multihandle.h" +#include "share.h" +#include "version_win32.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) +{ +#if defined(TCP_NODELAY) + curl_socklen_t onoff = (curl_socklen_t) 1; + int level = IPPROTO_TCP; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + char buffer[STRERROR_LEN]; +#else + (void) data; +#endif + + if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, + sizeof(onoff)) < 0) + infof(data, "Could not set TCP_NODELAY: %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); +#else + (void)data; + (void)sockfd; +#endif +} + +#ifdef SO_NOSIGPIPE +/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when + sending data to a dead peer (instead of relying on the 4th argument to send + being MSG_NOSIGNAL). Possibly also existing and in use on other BSD + systems? */ +static void nosigpipe(struct Curl_easy *data, + curl_socket_t sockfd) +{ + int onoff = 1; + if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, + sizeof(onoff)) < 0) { +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + char buffer[STRERROR_LEN]; + infof(data, "Could not set SO_NOSIGPIPE: %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); +#endif + } +} +#else +#define nosigpipe(x,y) Curl_nop_stmt +#endif + +#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H) +/* 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) +#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4) + +struct tcp_keepalive { + u_long onoff; + u_long keepalivetime; + u_long keepaliveinterval; +}; +#endif + +static void +tcpkeepalive(struct Curl_easy *data, + curl_socket_t sockfd) +{ + int optval = data->set.tcp_keepalive?1:0; + + /* only set IDLE and INTVL if setting KEEPALIVE is successful */ + if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd); + } + else { +#if defined(SIO_KEEPALIVE_VALS) + struct tcp_keepalive vals; + DWORD dummy; + vals.onoff = 1; + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + vals.keepalivetime = optval; + optval = curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + 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()); + } +#else +#ifdef TCP_KEEPIDLE + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd); + } +#elif defined(TCP_KEEPALIVE) + /* Mac OS X style */ + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd); + } +#endif +#ifdef TCP_KEEPINTVL + optval = curlx_sltosi(data->set.tcp_keepintvl); + 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); + } +#endif +#endif + } +} + +void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, + const struct Curl_addrinfo *ai, + int transport) +{ + /* + * The Curl_sockaddr_ex structure is basically libcurl's external API + * curl_sockaddr structure with enough space available to directly hold + * any protocol-specific address structures. The variable declared here + * will be used to pass / receive data to/from the fopensocket callback + * if this has been set, before that, it is initialized from parameters. + */ + dest->family = ai->ai_family; + switch(transport) { + case TRNSPRT_TCP: + dest->socktype = SOCK_STREAM; + dest->protocol = IPPROTO_TCP; + break; + case TRNSPRT_UNIX: + dest->socktype = SOCK_STREAM; + dest->protocol = IPPROTO_IP; + break; + default: /* UDP and QUIC */ + dest->socktype = SOCK_DGRAM; + dest->protocol = IPPROTO_UDP; + break; + } + dest->addrlen = ai->ai_addrlen; + + if(dest->addrlen > sizeof(struct Curl_sockaddr_storage)) + dest->addrlen = sizeof(struct Curl_sockaddr_storage); + memcpy(&dest->sa_addr, ai->ai_addr, dest->addrlen); +} + +static CURLcode socket_open(struct Curl_easy *data, + struct Curl_sockaddr_ex *addr, + curl_socket_t *sockfd) +{ + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + if(data->set.fopensocket) { + /* + * If the opensocket callback is set, all the destination address + * information is passed to the callback. Depending on this information the + * callback may opt to abort the connection, this is indicated returning + * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When + * the callback returns a valid socket the destination address information + * might have been changed and this 'new' address will actually be used + * here to connect. + */ + Curl_set_in_callback(data, true); + *sockfd = data->set.fopensocket(data->set.opensocket_client, + CURLSOCKTYPE_IPCXN, + (struct curl_sockaddr *)addr); + Curl_set_in_callback(data, false); + } + else { + /* opensocket callback not set, so simply create the socket now */ + *sockfd = socket(addr->family, addr->socktype, addr->protocol); + if(!*sockfd && addr->socktype == SOCK_DGRAM) { + /* This is icky and seems, at least, to happen on macOS: + * we get sockfd == 0 and if called again, we get a valid one > 0. + * If we close the 0, we sometimes get failures in multi poll, as + * 0 seems also be the fd for the sockpair used for WAKEUP polling. + * Very strange. Maybe this code should be ifdef'ed for macOS, but + * on "real" OS, fd 0 is stdin and we never see that. So... + */ + fake_sclose(*sockfd); + *sockfd = socket(addr->family, addr->socktype, addr->protocol); + DEBUGF(infof(data, "QUIRK: UDP socket() gave handle 0, 2nd attempt %d", + (int)*sockfd)); + } + } + + if(*sockfd == CURL_SOCKET_BAD) + /* no socket, no connection */ + return CURLE_COULDNT_CONNECT; + +#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + if(data->conn->scope_id && (addr->family == AF_INET6)) { + struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; + sa6->sin6_scope_id = data->conn->scope_id; + } +#endif + return CURLE_OK; +} + +/* + * Create a socket based on info from 'conn' and 'ai'. + * + * 'addr' should be a pointer to the correct struct to get data back, or NULL. + * 'sockfd' must be a pointer to a socket descriptor. + * + * If the open socket callback is set, used that! + * + */ +CURLcode Curl_socket_open(struct Curl_easy *data, + const struct Curl_addrinfo *ai, + struct Curl_sockaddr_ex *addr, + int transport, + curl_socket_t *sockfd) +{ + struct Curl_sockaddr_ex dummy; + + if(!addr) + /* if the caller doesn't want info back, use a local temp copy */ + addr = &dummy; + + Curl_sock_assign_addr(addr, ai, transport); + return socket_open(data, addr, sockfd); +} + +static int socket_close(struct Curl_easy *data, struct connectdata *conn, + int use_callback, curl_socket_t sock) +{ + if(use_callback && conn && conn->fclosesocket) { + int rc; + Curl_multi_closed(data, sock); + Curl_set_in_callback(data, true); + rc = conn->fclosesocket(conn->closesocket_client, sock); + Curl_set_in_callback(data, false); + return rc; + } + + if(conn) + /* tell the multi-socket code about this */ + Curl_multi_closed(data, sock); + + sclose(sock); + + return 0; +} + +/* + * Close a socket. + * + * 'conn' can be NULL, beware! + */ +int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t sock) +{ + return socket_close(data, conn, FALSE, sock); +} + +bool Curl_socket_is_dead(curl_socket_t sock) +{ + int sval; + bool ret_val = TRUE; + + sval = SOCKET_READABLE(sock, 0); + if(sval == 0) + /* timeout */ + ret_val = FALSE; + + return ret_val; +} + + +#ifdef USE_WINSOCK +/* When you run a program that uses the Windows Sockets API, you may + experience slow performance when you copy data to a TCP server. + + https://support.microsoft.com/kb/823764 + + Work-around: Make the Socket Send Buffer Size Larger Than the Program Send + Buffer Size + + The problem described in this knowledge-base is applied only to pre-Vista + Windows. Following function trying to detect OS version and skips + SO_SNDBUF adjustment for Windows Vista and above. +*/ +#define DETECT_OS_NONE 0 +#define DETECT_OS_PREVISTA 1 +#define DETECT_OS_VISTA_OR_LATER 2 + +void Curl_sndbufset(curl_socket_t sockfd) +{ + int val = CURL_MAX_WRITE_SIZE + 32; + int curval = 0; + int curlen = sizeof(curval); + + static int detectOsState = DETECT_OS_NONE; + + if(detectOsState == DETECT_OS_NONE) { + if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + detectOsState = DETECT_OS_VISTA_OR_LATER; + else + detectOsState = DETECT_OS_PREVISTA; + } + + if(detectOsState == DETECT_OS_VISTA_OR_LATER) + return; + + if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0) + if(curval > val) + return; + + setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val)); +} +#endif + +static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t sockfd, int af, unsigned int scope) +{ + struct Curl_sockaddr_storage sa; + struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ + curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ + struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; +#endif + + struct Curl_dns_entry *h = NULL; + unsigned short port = data->set.localport; /* use this port number, 0 for + "random" */ + /* how many port numbers to try to bind to, increasing one at a time */ + int portnum = data->set.localportrange; + const char *dev = data->set.str[STRING_DEVICE]; + int error; +#ifdef IP_BIND_ADDRESS_NO_PORT + int on = 1; +#endif +#ifndef ENABLE_IPV6 + (void)scope; +#endif + + /************************************************************* + * Select device to bind socket to + *************************************************************/ + if(!dev && !port) + /* no local kind of binding was requested */ + return CURLE_OK; + + memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); + + if(dev && (strlen(dev)<255) ) { + char myhost[256] = ""; + int done = 0; /* -1 for error, 1 for address found */ + bool is_interface = FALSE; + bool is_host = FALSE; + static const char *if_prefix = "if!"; + static const char *host_prefix = "host!"; + + if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) { + dev += strlen(if_prefix); + is_interface = TRUE; + } + else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) { + dev += strlen(host_prefix); + is_host = TRUE; + } + + /* interface */ + if(!is_host) { +#ifdef SO_BINDTODEVICE + /* I am not sure any other OSs than Linux that provide this feature, + * and at the least I cannot test. --Ben + * + * This feature allows one to tightly bind the local socket to a + * particular interface. This will force even requests to other + * local interfaces to go out the external interface. + * + * + * Only bind to the interface when specified as interface, not just + * as a hostname or ip address. + * + * interface might be a VRF, eg: vrf-blue, which means it cannot be + * converted to an IP address and would fail Curl_if2ip. Simply try + * to use it straight away. + */ + if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, + dev, (curl_socklen_t)strlen(dev) + 1) == 0) { + /* This is typically "errno 1, error: Operation not permitted" if + * you're not running as root or another suitable privileged + * user. + * If it succeeds it means the parameter was a valid interface and + * not an IP address. Return immediately. + */ + return CURLE_OK; + } +#endif + + switch(Curl_if2ip(af, +#ifdef ENABLE_IPV6 + scope, conn->scope_id, +#endif + dev, myhost, sizeof(myhost))) { + case IF2IP_NOT_FOUND: + if(is_interface) { + /* Do not fall back to treating it as a host name */ + failf(data, "Couldn't bind to interface '%s'", dev); + return CURLE_INTERFACE_FAILED; + } + break; + case IF2IP_AF_NOT_SUPPORTED: + /* Signal the caller to try another address family if available */ + return CURLE_UNSUPPORTED_PROTOCOL; + case IF2IP_FOUND: + is_interface = TRUE; + /* + * We now have the numerical IP address in the 'myhost' buffer + */ + infof(data, "Local Interface %s is ip %s using address family %i", + dev, myhost, af); + done = 1; + break; + } + } + if(!is_interface) { + /* + * This was not an interface, resolve the name as a host name + * or IP number + * + * Temporarily force name resolution to use only the address type + * of the connection. The resolve functions should really be changed + * to take a type parameter instead. + */ + unsigned char ipver = conn->ip_version; + int rc; + + if(af == AF_INET) + conn->ip_version = CURL_IPRESOLVE_V4; +#ifdef ENABLE_IPV6 + else if(af == AF_INET6) + conn->ip_version = CURL_IPRESOLVE_V6; +#endif + + rc = Curl_resolv(data, dev, 0, FALSE, &h); + if(rc == CURLRESOLV_PENDING) + (void)Curl_resolver_wait_resolv(data, &h); + conn->ip_version = ipver; + + if(h) { + /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ + Curl_printable_address(h->addr, myhost, sizeof(myhost)); + infof(data, "Name '%s' family %i resolved to '%s' family %i", + dev, af, myhost, h->addr->ai_family); + Curl_resolv_unlock(data, h); + if(af != h->addr->ai_family) { + /* bad IP version combo, signal the caller to try another address + family if available */ + return CURLE_UNSUPPORTED_PROTOCOL; + } + done = 1; + } + else { + /* + * provided dev was no interface (or interfaces are not supported + * e.g. solaris) no ip address and no domain we fail here + */ + done = -1; + } + } + + if(done > 0) { +#ifdef ENABLE_IPV6 + /* IPv6 address */ + if(af == AF_INET6) { +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + char *scope_ptr = strchr(myhost, '%'); + if(scope_ptr) + *(scope_ptr++) = '\0'; +#endif + if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) { + si6->sin6_family = AF_INET6; + si6->sin6_port = htons(port); +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + if(scope_ptr) { + /* The "myhost" string either comes from Curl_if2ip or from + Curl_printable_address. The latter returns only numeric scope + IDs and the former returns none at all. So the scope ID, if + present, is known to be numeric */ + unsigned long scope_id = strtoul(scope_ptr, NULL, 10); + if(scope_id > UINT_MAX) + return CURLE_UNSUPPORTED_PROTOCOL; + + si6->sin6_scope_id = (unsigned int)scope_id; + } +#endif + } + sizeof_sa = sizeof(struct sockaddr_in6); + } + else +#endif + /* IPv4 address */ + if((af == AF_INET) && + (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { + si4->sin_family = AF_INET; + si4->sin_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in); + } + } + + if(done < 1) { + /* errorbuf is set false so failf will overwrite any message already in + the error buffer, so the user receives this error message instead of a + generic resolve error. */ + data->state.errorbuf = FALSE; + failf(data, "Couldn't bind to '%s'", dev); + return CURLE_INTERFACE_FAILED; + } + } + else { + /* no device was given, prepare sa to match af's needs */ +#ifdef ENABLE_IPV6 + if(af == AF_INET6) { + si6->sin6_family = AF_INET6; + si6->sin6_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in6); + } + else +#endif + if(af == AF_INET) { + si4->sin_family = AF_INET; + si4->sin_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in); + } + } +#ifdef IP_BIND_ADDRESS_NO_PORT + (void)setsockopt(sockfd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &on, sizeof(on)); +#endif + for(;;) { + if(bind(sockfd, sock, sizeof_sa) >= 0) { + /* we succeeded to bind */ + struct Curl_sockaddr_storage add; + curl_socklen_t size = sizeof(add); + memset(&add, 0, sizeof(struct Curl_sockaddr_storage)); + if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) { + char buffer[STRERROR_LEN]; + data->state.os_errno = error = SOCKERRNO; + failf(data, "getsockname() failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + return CURLE_INTERFACE_FAILED; + } + infof(data, "Local port: %hu", port); + conn->bits.bound = TRUE; + return CURLE_OK; + } + + if(--portnum > 0) { + port++; /* try next port */ + if(port == 0) + break; + infof(data, "Bind to local port %hu failed, trying next", port - 1); + /* We re-use/clobber the port variable here below */ + if(sock->sa_family == AF_INET) + si4->sin_port = ntohs(port); +#ifdef ENABLE_IPV6 + else + si6->sin6_port = ntohs(port); +#endif + } + else + break; + } + { + char buffer[STRERROR_LEN]; + data->state.os_errno = error = SOCKERRNO; + failf(data, "bind failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + } + + return CURLE_INTERFACE_FAILED; +} + +/* + * verifyconnect() returns TRUE if the connect really has happened. + */ +static bool verifyconnect(curl_socket_t sockfd, int *error) +{ + bool rc = TRUE; +#ifdef SO_ERROR + int err = 0; + curl_socklen_t errSize = sizeof(err); + +#ifdef WIN32 + /* + * In October 2003 we effectively nullified this function on Windows due to + * problems with it using all CPU in multi-threaded cases. + * + * In May 2004, we bring it back to offer more info back on connect failures. + * Gisle Vanem could reproduce the former problems with this function, but + * could avoid them by adding this SleepEx() call below: + * + * "I don't have Rational Quantify, but the hint from his post was + * ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe + * just Sleep(0) would be enough?) would release whatever + * mutex/critical-section the ntdll call is waiting on. + * + * Someone got to verify this on Win-NT 4.0, 2000." + */ + +#ifdef _WIN32_WCE + Sleep(0); +#else + SleepEx(0, FALSE); +#endif + +#endif + + if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize)) + err = SOCKERRNO; +#ifdef _WIN32_WCE + /* Old WinCE versions don't support SO_ERROR */ + if(WSAENOPROTOOPT == err) { + SET_SOCKERRNO(0); + err = 0; + } +#endif +#if defined(EBADIOCTL) && defined(__minix) + /* Minix 3.1.x doesn't support getsockopt on UDP sockets */ + if(EBADIOCTL == err) { + SET_SOCKERRNO(0); + err = 0; + } +#endif + if((0 == err) || (EISCONN == err)) + /* we are connected, awesome! */ + rc = TRUE; + else + /* This wasn't a successful connect */ + rc = FALSE; + if(error) + *error = err; +#else + (void)sockfd; + if(error) + *error = SOCKERRNO; +#endif + return rc; +} + +CURLcode Curl_socket_connect_result(struct Curl_easy *data, + const char *ipaddress, int error) +{ + char buffer[STRERROR_LEN]; + + switch(error) { + case EINPROGRESS: + case EWOULDBLOCK: +#if defined(EAGAIN) +#if (EAGAIN) != (EWOULDBLOCK) + /* On some platforms EAGAIN and EWOULDBLOCK are the + * same value, and on others they are different, hence + * the odd #if + */ + case EAGAIN: +#endif +#endif + return CURLE_OK; + + default: + /* unknown error, fallthrough and try another address! */ + infof(data, "Immediate connect fail for %s: %s", + ipaddress, Curl_strerror(error, buffer, sizeof(buffer))); + data->state.os_errno = error; + /* connect failed */ + return CURLE_COULDNT_CONNECT; + } +} + +#ifdef USE_RECV_BEFORE_SEND_WORKAROUND +struct io_buffer { + char *bufr; + size_t allc; /* size of the current allocation */ + size_t head; /* bufr index for next read */ + size_t tail; /* bufr index for next write */ +}; + +static void io_buffer_reset(struct io_buffer *iob) +{ + if(iob->bufr) + free(iob->bufr); + memset(iob, 0, sizeof(*iob)); +} +#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */ + +struct cf_socket_ctx { + int transport; + struct Curl_sockaddr_ex addr; /* address to connect to */ + curl_socket_t sock; /* current attempt socket */ +#ifdef USE_RECV_BEFORE_SEND_WORKAROUND + struct io_buffer recv_buffer; +#endif + char r_ip[MAX_IPADR_LEN]; /* remote IP as string */ + int r_port; /* remote port number */ + char l_ip[MAX_IPADR_LEN]; /* local IP as string */ + int l_port; /* local port number */ + struct curltime started_at; /* when socket was created */ + struct curltime connected_at; /* when socket connected/got first byte */ + struct curltime first_byte_at; /* when first byte was recvd */ + int error; /* errno of last failure or 0 */ + BIT(got_first_byte); /* if first byte was received */ + BIT(accepted); /* socket was accepted, not connected */ + BIT(active); +}; + +static void cf_socket_ctx_init(struct cf_socket_ctx *ctx, + const struct Curl_addrinfo *ai, + int transport) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->sock = CURL_SOCKET_BAD; + ctx->transport = transport; + Curl_sock_assign_addr(&ctx->addr, ai, transport); +} + +static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + if(ctx && CURL_SOCKET_BAD != ctx->sock) { + if(ctx->active) { + /* We share our socket at cf->conn->sock[cf->sockindex] when active. + * If it is no longer there, someone has stolen (and hopefully + * closed it) and we just forget about it. + */ + if(ctx->sock == cf->conn->sock[cf->sockindex]) { + DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d, active)", + (int)ctx->sock)); + socket_close(data, cf->conn, !ctx->accepted, ctx->sock); + cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; + } + else { + DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d) no longer at " + "conn->sock[], discarding", (int)ctx->sock)); + /* TODO: we do not want this to happen. Need to check which + * code is messing with conn->sock[cf->sockindex] */ + } + ctx->sock = CURL_SOCKET_BAD; + if(cf->sockindex == FIRSTSOCKET) + cf->conn->remote_addr = NULL; + } + else { + /* this is our local socket, we did never publish it */ + DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d, not active)", + (int)ctx->sock)); + sclose(ctx->sock); + ctx->sock = CURL_SOCKET_BAD; + } +#ifdef USE_RECV_BEFORE_SEND_WORKAROUND + io_buffer_reset(&ctx->recv_buffer); +#endif + ctx->active = FALSE; + memset(&ctx->started_at, 0, sizeof(ctx->started_at)); + memset(&ctx->connected_at, 0, sizeof(ctx->connected_at)); + } + + cf->connected = FALSE; +} + +static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + cf_socket_close(cf, data); + DEBUGF(LOG_CF(data, cf, "destroy")); + free(ctx); + cf->ctx = NULL; +} + +static CURLcode set_local_ip(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + +#ifdef HAVE_GETSOCKNAME + char buffer[STRERROR_LEN]; + struct Curl_sockaddr_storage ssloc; + curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage); + + memset(&ssloc, 0, sizeof(ssloc)); + if(getsockname(ctx->sock, (struct sockaddr*) &ssloc, &slen)) { + int error = SOCKERRNO; + failf(data, "getsockname() failed with errno %d: %s", + error, Curl_strerror(error, buffer, sizeof(buffer))); + return CURLE_FAILED_INIT; + } + if(!Curl_addr2string((struct sockaddr*)&ssloc, slen, + ctx->l_ip, &ctx->l_port)) { + failf(data, "ssloc inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return CURLE_FAILED_INIT; + } +#else + (void)data; + ctx->l_ip[0] = 0; + ctx->l_port = -1; +#endif + return CURLE_OK; +} + +static CURLcode set_remote_ip(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + /* store remote address and port used in this connection attempt */ + if(!Curl_addr2string(&ctx->addr.sa_addr, ctx->addr.addrlen, + ctx->r_ip, &ctx->r_port)) { + char buffer[STRERROR_LEN]; + + ctx->error = errno; + /* malformed address or bug in inet_ntop, try next address */ + failf(data, "sa_addr inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return CURLE_FAILED_INIT; + } + return CURLE_OK; +} + +static CURLcode cf_socket_open(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + int error = 0; + bool isconnected = FALSE; + CURLcode result = CURLE_COULDNT_CONNECT; + bool is_tcp; + const char *ipmsg; + + (void)data; + DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD); + ctx->started_at = Curl_now(); + result = socket_open(data, &ctx->addr, &ctx->sock); + if(result) + goto out; + + result = set_remote_ip(cf, data); + if(result) + goto out; + +#ifdef ENABLE_IPV6 + if(ctx->addr.family == AF_INET6) + ipmsg = " Trying [%s]:%d..."; + else +#endif + ipmsg = " Trying %s:%d..."; + infof(data, ipmsg, ctx->r_ip, ctx->r_port); + +#ifdef ENABLE_IPV6 + is_tcp = (ctx->addr.family == AF_INET + || ctx->addr.family == AF_INET6) && + ctx->addr.socktype == SOCK_STREAM; +#else + is_tcp = (ctx->addr.family == AF_INET) && + ctx->addr.socktype == SOCK_STREAM; +#endif + if(is_tcp && data->set.tcp_nodelay) + tcpnodelay(data, ctx->sock); + + nosigpipe(data, ctx->sock); + + Curl_sndbufset(ctx->sock); + + if(is_tcp && data->set.tcp_keepalive) + tcpkeepalive(data, ctx->sock); + + if(data->set.fsockopt) { + /* activate callback for setting socket options */ + Curl_set_in_callback(data, true); + error = data->set.fsockopt(data->set.sockopt_client, + ctx->sock, + CURLSOCKTYPE_IPCXN); + Curl_set_in_callback(data, false); + + if(error == CURL_SOCKOPT_ALREADY_CONNECTED) + isconnected = TRUE; + else if(error) { + result = CURLE_ABORTED_BY_CALLBACK; + goto out; + } + } + + /* possibly bind the local end to an IP, interface or port */ + if(ctx->addr.family == AF_INET +#ifdef ENABLE_IPV6 + || ctx->addr.family == AF_INET6 +#endif + ) { + result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family, + Curl_ipv6_scope(&ctx->addr.sa_addr)); + if(result) { + if(result == CURLE_UNSUPPORTED_PROTOCOL) { + /* The address family is not supported on this interface. + We can continue trying addresses */ + result = CURLE_COULDNT_CONNECT; + } + goto out; + } + } + + /* set socket non-blocking */ + (void)curlx_nonblock(ctx->sock, TRUE); + +out: + if(result) { + if(ctx->sock != CURL_SOCKET_BAD) { + socket_close(data, cf->conn, TRUE, ctx->sock); + ctx->sock = CURL_SOCKET_BAD; + } + } + else if(isconnected) { + set_local_ip(cf, data); + ctx->connected_at = Curl_now(); + cf->connected = TRUE; + } + DEBUGF(LOG_CF(data, cf, "cf_socket_open() -> %d, fd=%d", result, ctx->sock)); + return result; +} + +static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, + bool is_tcp_fastopen) +{ + struct cf_socket_ctx *ctx = cf->ctx; +#ifdef TCP_FASTOPEN_CONNECT + int optval = 1; +#endif + int rc = -1; + + (void)data; + if(is_tcp_fastopen) { +#if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */ +# if defined(HAVE_BUILTIN_AVAILABLE) + /* while connectx function is available since macOS 10.11 / iOS 9, + it did not have the interface declared correctly until + Xcode 9 / macOS SDK 10.13 */ + if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) { + sa_endpoints_t endpoints; + endpoints.sae_srcif = 0; + endpoints.sae_srcaddr = NULL; + endpoints.sae_srcaddrlen = 0; + endpoints.sae_dstaddr = &ctx->addr.sa_addr; + endpoints.sae_dstaddrlen = ctx->addr.addrlen; + + rc = connectx(ctx->sock, &endpoints, SAE_ASSOCID_ANY, + CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT, + NULL, 0, NULL, NULL); + } + else { + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + } +# else + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); +# endif /* HAVE_BUILTIN_AVAILABLE */ +#elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */ + if(setsockopt(ctx->sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, + (void *)&optval, sizeof(optval)) < 0) + infof(data, "Failed to enable TCP Fast Open on fd %d", ctx->sock); + + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); +#elif defined(MSG_FASTOPEN) /* old Linux */ + if(cf->conn->given->flags & PROTOPT_SSL) + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + else + rc = 0; /* Do nothing */ +#endif + } + else { + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + } + return rc; +} + +static CURLcode cf_tcp_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_socket_ctx *ctx = cf->ctx; + CURLcode result = CURLE_COULDNT_CONNECT; + int rc = 0; + + (void)data; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* TODO: need to support blocking connect? */ + if(blocking) + return CURLE_UNSUPPORTED_PROTOCOL; + + *done = FALSE; /* a very negative world view is best */ + if(ctx->sock == CURL_SOCKET_BAD) { + + result = cf_socket_open(cf, data); + if(result) + goto out; + + /* Connect TCP socket */ + rc = do_connect(cf, data, cf->conn->bits.tcp_fastopen); + if(-1 == rc) { + result = Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO); + goto out; + } + } + +#ifdef mpeix + /* Call this function once now, and ignore the results. We do this to + "clear" the error state on the socket so that we can later read it + reliably. This is reported necessary on the MPE/iX operating + system. */ + (void)verifyconnect(ctx->sock, NULL); +#endif + /* check socket for connect */ + rc = SOCKET_WRITABLE(ctx->sock, 0); + + if(rc == 0) { /* no connection yet */ + DEBUGF(LOG_CF(data, cf, "not connected yet")); + return CURLE_OK; + } + else if(rc == CURL_CSELECT_OUT || cf->conn->bits.tcp_fastopen) { + if(verifyconnect(ctx->sock, &ctx->error)) { + /* we are connected with TCP, awesome! */ + ctx->connected_at = Curl_now(); + set_local_ip(cf, data); + *done = TRUE; + cf->connected = TRUE; + DEBUGF(LOG_CF(data, cf, "connected")); + return CURLE_OK; + } + } + else if(rc & CURL_CSELECT_ERR) { + (void)verifyconnect(ctx->sock, &ctx->error); + result = CURLE_COULDNT_CONNECT; + } + +out: + if(result) { + if(ctx->error) { + data->state.os_errno = ctx->error; + SET_SOCKERRNO(ctx->error); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + { + char buffer[STRERROR_LEN]; + infof(data, "connect to %s port %u failed: %s", + ctx->r_ip, ctx->r_port, + Curl_strerror(ctx->error, buffer, sizeof(buffer))); + } +#endif + } + if(ctx->sock != CURL_SOCKET_BAD) { + socket_close(data, cf->conn, TRUE, ctx->sock); + ctx->sock = CURL_SOCKET_BAD; + } + *done = FALSE; + } + return result; +} + +static void cf_socket_get_host(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char **phost, + const char **pdisplay_host, + int *pport) +{ + (void)data; + *phost = cf->conn->host.name; + *pdisplay_host = cf->conn->host.dispname; + *pport = cf->conn->port; +} + +static int cf_socket_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *socks) +{ + struct cf_socket_ctx *ctx = cf->ctx; + int rc = GETSOCK_BLANK; + + (void)data; + if(!cf->connected && ctx->sock != CURL_SOCKET_BAD) { + socks[0] = ctx->sock; + rc |= GETSOCK_WRITESOCK(0); + } + + return rc; +} + +#ifdef USE_RECV_BEFORE_SEND_WORKAROUND + +static CURLcode pre_receive_plain(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + struct io_buffer * const iob = &ctx->recv_buffer; + + /* WinSock will destroy unread received data if send() is + failed. + To avoid lossage of received data, recv() must be + performed before every send() if any incoming data is + available. However, skip this, if buffer is already full. */ + if((cf->conn->handler->protocol&PROTO_FAMILY_HTTP) != 0 && + cf->conn->recv[cf->sockindex] == Curl_conn_recv && + (!iob->bufr || (iob->allc > iob->tail))) { + const int readymask = Curl_socket_check(ctx->sock, CURL_SOCKET_BAD, + CURL_SOCKET_BAD, 0); + if(readymask != -1 && (readymask & CURL_CSELECT_IN) != 0) { + size_t bytestorecv = iob->allc - iob->tail; + ssize_t nread; + /* Have some incoming data */ + if(!iob->bufr) { + /* Use buffer double default size for intermediate buffer */ + iob->allc = 2 * data->set.buffer_size; + iob->bufr = malloc(iob->allc); + if(!iob->bufr) + return CURLE_OUT_OF_MEMORY; + iob->tail = 0; + iob->head = 0; + bytestorecv = iob->allc; + } + + nread = sread(ctx->sock, iob->bufr + iob->tail, bytestorecv); + if(nread > 0) + iob->tail += (size_t)nread; + } + } + return CURLE_OK; +} + +static ssize_t get_pre_recved(struct Curl_cfilter *cf, char *buf, size_t len) +{ + struct cf_socket_ctx *ctx = cf->ctx; + struct io_buffer * const iob = &ctx->recv_buffer; + size_t copysize; + if(!iob->bufr) + return 0; + + DEBUGASSERT(iob->allc > 0); + DEBUGASSERT(iob->tail <= iob->allc); + DEBUGASSERT(iob->head <= iob->tail); + /* Check and process data that already received and storied in internal + intermediate buffer */ + if(iob->tail > iob->head) { + copysize = CURLMIN(len, iob->tail - iob->head); + memcpy(buf, iob->bufr + iob->head, copysize); + iob->head += copysize; + } + else + copysize = 0; /* buffer was allocated, but nothing was received */ + + /* Free intermediate buffer if it has no unprocessed data */ + if(iob->head == iob->tail) + io_buffer_reset(iob); + + return (ssize_t)copysize; +} +#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */ + +static bool cf_socket_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + int readable; + +#ifdef USE_RECV_BEFORE_SEND_WORKAROUND + if(ctx->recv_buffer.bufr && ctx->recv_buffer.allc && + ctx->recv_buffer.tail > ctx->recv_buffer.head) + return TRUE; +#endif + + (void)data; + readable = SOCKET_READABLE(ctx->sock, 0); + return (readable > 0 && (readable & CURL_CSELECT_IN)); +} + +static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_socket_ctx *ctx = cf->ctx; + curl_socket_t fdsave; + ssize_t nwritten; + + *err = CURLE_OK; + +#ifdef USE_RECV_BEFORE_SEND_WORKAROUND + /* WinSock will destroy unread received data if send() is + failed. + To avoid lossage of received data, recv() must be + performed before every send() if any incoming data is + available. */ + if(pre_receive_plain(cf, data)) { + *err = CURLE_OUT_OF_MEMORY; + return -1; + } +#endif + + fdsave = cf->conn->sock[cf->sockindex]; + cf->conn->sock[cf->sockindex] = ctx->sock; + +#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT) /* Linux */ + if(cf->conn->bits.tcp_fastopen) { + nwritten = sendto(ctx->sock, buf, len, MSG_FASTOPEN, + &cf->conn->remote_addr->sa_addr, + cf->conn->remote_addr->addrlen); + cf->conn->bits.tcp_fastopen = FALSE; + } + else +#endif + nwritten = swrite(ctx->sock, buf, len); + + if(-1 == nwritten) { + int sockerr = SOCKERRNO; + + if( +#ifdef WSAEWOULDBLOCK + /* This is how Windows does it */ + (WSAEWOULDBLOCK == sockerr) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned + due to its inability to send off data without blocking. We therefore + treat both error codes the same here */ + (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) || + (EINPROGRESS == sockerr) +#endif + ) { + /* this is just a case of EWOULDBLOCK */ + *err = CURLE_AGAIN; + } + else { + char buffer[STRERROR_LEN]; + failf(data, "Send failure: %s", + Curl_strerror(sockerr, buffer, sizeof(buffer))); + data->state.os_errno = sockerr; + *err = CURLE_SEND_ERROR; + } + } + + DEBUGF(LOG_CF(data, cf, "send(len=%zu) -> %d, err=%d", + len, (int)nwritten, *err)); + cf->conn->sock[cf->sockindex] = fdsave; + return nwritten; +} + +static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_socket_ctx *ctx = cf->ctx; + curl_socket_t fdsave; + ssize_t nread; + + *err = CURLE_OK; + +#ifdef USE_RECV_BEFORE_SEND_WORKAROUND + /* Check and return data that already received and storied in internal + intermediate buffer */ + nread = get_pre_recved(cf, buf, len); + if(nread > 0) { + *err = CURLE_OK; + return nread; + } +#endif + + fdsave = cf->conn->sock[cf->sockindex]; + cf->conn->sock[cf->sockindex] = ctx->sock; + + nread = sread(ctx->sock, buf, len); + + if(-1 == nread) { + int sockerr = SOCKERRNO; + + if( +#ifdef WSAEWOULDBLOCK + /* This is how Windows does it */ + (WSAEWOULDBLOCK == sockerr) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned + due to its inability to send off data without blocking. We therefore + treat both error codes the same here */ + (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) +#endif + ) { + /* this is just a case of EWOULDBLOCK */ + *err = CURLE_AGAIN; + } + else { + char buffer[STRERROR_LEN]; + failf(data, "Recv failure: %s", + Curl_strerror(sockerr, buffer, sizeof(buffer))); + data->state.os_errno = sockerr; + *err = CURLE_RECV_ERROR; + } + } + + DEBUGF(LOG_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread, + *err)); + if(nread > 0 && !ctx->got_first_byte) { + ctx->first_byte_at = Curl_now(); + ctx->got_first_byte = TRUE; + } + cf->conn->sock[cf->sockindex] = fdsave; + return nread; +} + +static void conn_set_primary_ip(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; +#ifdef HAVE_GETPEERNAME + 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 +} + +static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + /* use this socket from now on */ + cf->conn->sock[cf->sockindex] = ctx->sock; + /* the first socket info gets set at conn and data */ + if(cf->sockindex == FIRSTSOCKET) { + cf->conn->remote_addr = &ctx->addr; + #ifdef ENABLE_IPV6 + cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE; + #endif + conn_set_primary_ip(cf, data); + set_local_ip(cf, data); + Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); + } + ctx->active = TRUE; +} + +static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_CONN_INFO_UPDATE: + cf_socket_active(cf, data); + break; + case CF_CTRL_CONN_REPORT_STATS: + switch(ctx->transport) { + case TRNSPRT_UDP: + case TRNSPRT_QUIC: + /* Since UDP connected sockets work different from TCP, we use the + * time of the first byte from the peer as the "connect" time. */ + if(ctx->got_first_byte) { + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->first_byte_at); + break; + } + /* FALLTHROUGH */ + default: + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->connected_at); + break; + } + break; + case CF_CTRL_DATA_SETUP: + Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); + break; + } + return CURLE_OK; +} + +static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + int sval; + + (void)data; + if(!ctx || ctx->sock == CURL_SOCKET_BAD) + return FALSE; + + sval = SOCKET_READABLE(ctx->sock, 0); + if(sval == 0) { + /* timeout */ + return TRUE; + } + else if(sval & CURL_CSELECT_ERR) { + /* socket is in an error state */ + return FALSE; + } + else if(sval & CURL_CSELECT_IN) { + /* readable with no error. could still be closed */ +/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */ +#ifdef MSG_PEEK + /* use the socket */ + char buf; + if(recv((RECV_TYPE_ARG1)ctx->sock, (RECV_TYPE_ARG2)&buf, + (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) { + return FALSE; /* FIN received */ + } +#endif + return TRUE; + } + + return TRUE; +} + +static CURLcode cf_socket_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_SOCKET: + DEBUGASSERT(pres2); + *((curl_socket_t *)pres2) = ctx->sock; + 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; + default: + break; + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +struct Curl_cftype Curl_cft_tcp = { + "TCP", + CF_TYPE_IP_CONNECT, + CURL_LOG_DEFAULT, + cf_socket_destroy, + cf_tcp_connect, + cf_socket_close, + cf_socket_get_host, + cf_socket_get_select_socks, + cf_socket_data_pending, + cf_socket_send, + cf_socket_recv, + cf_socket_cntrl, + cf_socket_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_socket_query, +}; + +CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport) +{ + struct cf_socket_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + DEBUGASSERT(transport == TRNSPRT_TCP); + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_socket_ctx_init(ctx, ai, transport); + + result = Curl_cf_create(&cf, &Curl_cft_tcp, ctx); + +out: + *pcf = (!result)? cf : NULL; + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + int rc; + + /* QUIC needs a connected socket, nonblocking */ + DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD); + + rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen); + if(-1 == rc) { + return Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO); + } + set_local_ip(cf, data); + DEBUGF(LOG_CF(data, cf, "%s socket %d connected: [%s:%d] -> [%s:%d]", + (ctx->transport == TRNSPRT_QUIC)? "QUIC" : "UDP", + ctx->sock, ctx->l_ip, ctx->l_port, ctx->r_ip, ctx->r_port)); + + (void)curlx_nonblock(ctx->sock, TRUE); + switch(ctx->addr.family) { +#if defined(__linux__) && defined(IP_MTU_DISCOVER) + case AF_INET: { + int val = IP_PMTUDISC_DO; + (void)setsockopt(ctx->sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, + sizeof(val)); + break; + } +#endif +#if defined(__linux__) && defined(IPV6_MTU_DISCOVER) + case AF_INET6: { + int val = IPV6_PMTUDISC_DO; + (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val, + sizeof(val)); + break; + } +#endif + } + return CURLE_OK; +} + +static CURLcode cf_udp_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_socket_ctx *ctx = cf->ctx; + CURLcode result = CURLE_COULDNT_CONNECT; + + (void)blocking; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + *done = FALSE; + if(ctx->sock == CURL_SOCKET_BAD) { + result = cf_socket_open(cf, data); + if(result) { + DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), open failed -> %d", result)); + if(ctx->sock != CURL_SOCKET_BAD) { + socket_close(data, cf->conn, TRUE, ctx->sock); + ctx->sock = CURL_SOCKET_BAD; + } + goto out; + } + + if(ctx->transport == TRNSPRT_QUIC) { + result = cf_udp_setup_quic(cf, data); + if(result) + goto out; + DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d (%s:%d)", + ctx->sock, ctx->l_ip, ctx->l_port)); + } + else { + DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d " + "(unconnected)", ctx->sock)); + } + *done = TRUE; + cf->connected = TRUE; + } +out: + return result; +} + +struct Curl_cftype Curl_cft_udp = { + "UDP", + CF_TYPE_IP_CONNECT, + CURL_LOG_DEFAULT, + cf_socket_destroy, + cf_udp_connect, + cf_socket_close, + cf_socket_get_host, + cf_socket_get_select_socks, + cf_socket_data_pending, + cf_socket_send, + cf_socket_recv, + cf_socket_cntrl, + cf_socket_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_socket_query, +}; + +CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport) +{ + struct cf_socket_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + DEBUGASSERT(transport == TRNSPRT_UDP || transport == TRNSPRT_QUIC); + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_socket_ctx_init(ctx, ai, transport); + + result = Curl_cf_create(&cf, &Curl_cft_udp, ctx); + +out: + *pcf = (!result)? cf : NULL; + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +/* this is the TCP filter which can also handle this case */ +struct Curl_cftype Curl_cft_unix = { + "UNIX", + CF_TYPE_IP_CONNECT, + CURL_LOG_DEFAULT, + cf_socket_destroy, + cf_tcp_connect, + cf_socket_close, + cf_socket_get_host, + cf_socket_get_select_socks, + cf_socket_data_pending, + cf_socket_send, + cf_socket_recv, + cf_socket_cntrl, + cf_socket_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_socket_query, +}; + +CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport) +{ + struct cf_socket_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + DEBUGASSERT(transport == TRNSPRT_UNIX); + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_socket_ctx_init(ctx, ai, transport); + + result = Curl_cf_create(&cf, &Curl_cft_unix, ctx); + +out: + *pcf = (!result)? cf : NULL; + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + /* we start accepted, if we ever close, we cannot go on */ + (void)data; + (void)blocking; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + return CURLE_FAILED_INIT; +} + +struct Curl_cftype Curl_cft_tcp_accept = { + "TCP-ACCEPT", + CF_TYPE_IP_CONNECT, + CURL_LOG_DEFAULT, + cf_socket_destroy, + cf_tcp_accept_connect, + cf_socket_close, + cf_socket_get_host, /* TODO: not accurate */ + cf_socket_get_select_socks, + cf_socket_data_pending, + cf_socket_send, + cf_socket_recv, + cf_socket_cntrl, + cf_socket_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_socket_query, +}; + +CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, curl_socket_t *s) +{ + CURLcode result; + struct Curl_cfilter *cf = NULL; + struct cf_socket_ctx *ctx = NULL; + + /* replace any existing */ + Curl_conn_cf_discard_all(data, conn, sockindex); + DEBUGASSERT(conn->sock[sockindex] == CURL_SOCKET_BAD); + + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->transport = conn->transport; + ctx->sock = *s; + ctx->accepted = FALSE; + result = Curl_cf_create(&cf, &Curl_cft_tcp_accept, ctx); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); + + conn->sock[sockindex] = ctx->sock; + set_remote_ip(cf, data); + set_local_ip(cf, data); + ctx->active = TRUE; + ctx->connected_at = Curl_now(); + cf->connected = TRUE; + DEBUGF(LOG_CF(data, cf, "Curl_conn_tcp_listen_set(%d)", (int)ctx->sock)); + +out: + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + return result; +} + +CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, curl_socket_t *s) +{ + struct Curl_cfilter *cf = NULL; + struct cf_socket_ctx *ctx = NULL; + + cf = conn->cfilter[sockindex]; + if(!cf || cf->cft != &Curl_cft_tcp_accept) + return CURLE_FAILED_INIT; + + ctx = cf->ctx; + /* discard the listen socket */ + socket_close(data, conn, TRUE, ctx->sock); + ctx->sock = *s; + conn->sock[sockindex] = ctx->sock; + set_remote_ip(cf, data); + set_local_ip(cf, data); + ctx->active = TRUE; + ctx->accepted = TRUE; + ctx->connected_at = Curl_now(); + cf->connected = TRUE; + DEBUGF(LOG_CF(data, cf, "Curl_conn_tcp_accepted_set(%d)", (int)ctx->sock)); + + return CURLE_OK; +} + +bool Curl_cf_is_socket(struct Curl_cfilter *cf) +{ + return cf && (cf->cft == &Curl_cft_tcp || + cf->cft == &Curl_cft_udp || + cf->cft == &Curl_cft_unix || + cf->cft == &Curl_cft_tcp_accept); +} + +CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *psock, + const struct Curl_sockaddr_ex **paddr, + const char **pr_ip_str, int *pr_port, + const char **pl_ip_str, int *pl_port) +{ + if(Curl_cf_is_socket(cf) && cf->ctx) { + struct cf_socket_ctx *ctx = cf->ctx; + + if(psock) + *psock = ctx->sock; + if(paddr) + *paddr = &ctx->addr; + if(pr_ip_str) + *pr_ip_str = ctx->r_ip; + if(pr_port) + *pr_port = ctx->r_port; + if(pl_port ||pl_ip_str) { + set_local_ip(cf, data); + if(pl_ip_str) + *pl_ip_str = ctx->l_ip; + if(pl_port) + *pl_port = ctx->l_port; + } + return CURLE_OK; + } + return CURLE_FAILED_INIT; +} + diff --git a/Utilities/cmcurl/lib/cf-socket.h b/Utilities/cmcurl/lib/cf-socket.h new file mode 100644 index 0000000000..f6eb810a52 --- /dev/null +++ b/Utilities/cmcurl/lib/cf-socket.h @@ -0,0 +1,192 @@ +#ifndef HEADER_CURL_CF_SOCKET_H +#define HEADER_CURL_CF_SOCKET_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 "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */ +#include "sockaddr.h" + +struct Curl_addrinfo; +struct Curl_cfilter; +struct Curl_easy; +struct connectdata; +struct Curl_sockaddr_ex; + +/* + * The Curl_sockaddr_ex structure is basically libcurl's external API + * curl_sockaddr structure with enough space available to directly hold any + * protocol-specific address structures. The variable declared here will be + * used to pass / receive data to/from the fopensocket callback if this has + * been set, before that, it is initialized from parameters. + */ +struct Curl_sockaddr_ex { + int family; + int socktype; + int protocol; + unsigned int addrlen; + union { + struct sockaddr addr; + struct Curl_sockaddr_storage buff; + } _sa_ex_u; +}; +#define sa_addr _sa_ex_u.addr + + +/* + * Create a socket based on info from 'conn' and 'ai'. + * + * Fill in 'addr' and 'sockfd' accordingly if OK is returned. If the open + * socket callback is set, used that! + * + */ +CURLcode Curl_socket_open(struct Curl_easy *data, + const struct Curl_addrinfo *ai, + struct Curl_sockaddr_ex *addr, + int transport, + curl_socket_t *sockfd); + +int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t sock); + +/* + * This function should return TRUE if the socket is to be assumed to + * be dead. Most commonly this happens when the server has closed the + * connection due to inactivity. + */ +bool Curl_socket_is_dead(curl_socket_t sock); + +/** + * Determine the curl code for a socket connect() == -1 with errno. + */ +CURLcode Curl_socket_connect_result(struct Curl_easy *data, + const char *ipaddress, int error); + +#ifdef USE_WINSOCK +/* When you run a program that uses the Windows Sockets API, you may + experience slow performance when you copy data to a TCP server. + + https://support.microsoft.com/kb/823764 + + Work-around: Make the Socket Send Buffer Size Larger Than the Program Send + Buffer Size + +*/ +void Curl_sndbufset(curl_socket_t sockfd); +#else +#define Curl_sndbufset(y) Curl_nop_stmt +#endif + +/** + * Assign the address `ai` to the Curl_sockaddr_ex `dest` and + * set the transport used. + */ +void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest, + const struct Curl_addrinfo *ai, + int transport); + +/** + * Creates a cfilter that opens a TCP socket to the given address + * when calling its `connect` implementation. + * The filter will not touch any connection/data flags and can be + * used in happy eyeballing. Once selected for use, its `_active()` + * method needs to be called. + */ +CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +/** + * Creates a cfilter that opens a UDP socket to the given address + * when calling its `connect` implementation. + * The filter will not touch any connection/data flags and can be + * used in happy eyeballing. Once selected for use, its `_active()` + * method needs to be called. + */ +CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +/** + * Creates a cfilter that opens a UNIX socket to the given address + * when calling its `connect` implementation. + * The filter will not touch any connection/data flags and can be + * used in happy eyeballing. Once selected for use, its `_active()` + * method needs to be called. + */ +CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +/** + * Creates a cfilter that keeps a listening socket. + */ +CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + curl_socket_t *s); + +/** + * Replace the listen socket with the accept()ed one. + */ +CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + curl_socket_t *s); + +/** + * Return TRUE iff `cf` is a socket filter. + */ +bool Curl_cf_is_socket(struct Curl_cfilter *cf); + +/** + * Peek at the socket and remote ip/port the socket filter is using. + * The filter owns all returned values. + * @param psock pointer to hold socket descriptor or NULL + * @param paddr pointer to hold addr reference or NULL + * @param pr_ip_str pointer to hold remote addr as string or NULL + * @param pr_port pointer to hold remote port number or NULL + * @param pl_ip_str pointer to hold local addr as string or NULL + * @param pl_port pointer to hold local port number or NULL + * Returns error if the filter is of invalid type. + */ +CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *psock, + const struct Curl_sockaddr_ex **paddr, + const char **pr_ip_str, int *pr_port, + const char **pl_ip_str, int *pl_port); + +extern struct Curl_cftype Curl_cft_tcp; +extern struct Curl_cftype Curl_cft_udp; +extern struct Curl_cftype Curl_cft_unix; +extern struct Curl_cftype Curl_cft_tcp_accept; + +#endif /* HEADER_CURL_CF_SOCKET_H */ diff --git a/Utilities/cmcurl/lib/cfilters.c b/Utilities/cmcurl/lib/cfilters.c index bcb33da771..2af0dd8288 100644 --- a/Utilities/cmcurl/lib/cfilters.c +++ b/Utilities/cmcurl/lib/cfilters.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -34,9 +34,6 @@ #include "multiif.h" #include "progress.h" #include "warnless.h" -#include "http_proxy.h" -#include "socks.h" -#include "vtls/vtls.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -54,89 +51,116 @@ void Curl_cf_def_destroy_this(struct Curl_cfilter *cf, struct Curl_easy *data) (void)data; } -CURLcode Curl_cf_def_setup(struct Curl_cfilter *cf, - struct Curl_easy *data, - const struct Curl_dns_entry *remotehost) -{ - DEBUGASSERT(cf->next); - return cf->next->cft->setup(cf->next, data, remotehost); -} - -void Curl_cf_def_attach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - (void)cf; - (void)data; -} - -void Curl_cf_def_detach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - (void)cf; - (void)data; -} - void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - DEBUGASSERT(cf->next); cf->connected = FALSE; - cf->next->cft->close(cf->next, data); + if(cf->next) + cf->next->cft->close(cf->next, data); } CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool blocking, bool *done) { - DEBUGASSERT(cf->next); - return cf->next->cft->connect(cf->next, data, blocking, done); + CURLcode result; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + if(cf->next) { + result = cf->next->cft->connect(cf->next, data, blocking, done); + if(!result && *done) { + cf->connected = TRUE; + } + return result; + } + *done = FALSE; + return CURLE_FAILED_INIT; } void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, const char **phost, const char **pdisplay_host, int *pport) { - DEBUGASSERT(cf->next); - cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport); + if(cf->next) + cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport); + else { + *phost = cf->conn->host.name; + *pdisplay_host = cf->conn->host.dispname; + *pport = cf->conn->port; + } } int Curl_cf_def_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *socks) { - DEBUGASSERT(cf->next); - return cf->next->cft->get_select_socks(cf->next, data, socks); + return cf->next? + cf->next->cft->get_select_socks(cf->next, data, socks) : 0; } bool Curl_cf_def_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { - DEBUGASSERT(cf->next); - return cf->next->cft->has_data_pending(cf->next, data); + return cf->next? + cf->next->cft->has_data_pending(cf->next, data) : FALSE; } ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err) { - DEBUGASSERT(cf->next); - return cf->next->cft->do_send(cf->next, data, buf, len, err); + return cf->next? + cf->next->cft->do_send(cf->next, data, buf, len, err) : + CURLE_RECV_ERROR; } ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { - DEBUGASSERT(cf->next); - return cf->next->cft->do_recv(cf->next, data, buf, len, err); + return cf->next? + cf->next->cft->do_recv(cf->next, data, buf, len, err) : + CURLE_SEND_ERROR; } -void Curl_conn_cf_discard_all(struct Curl_easy *data, - struct connectdata *conn, int index) +bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct Curl_cfilter *cfn, *cf = conn->cfilter[index]; + return cf->next? + cf->next->cft->is_alive(cf->next, data) : + FALSE; /* pessimistic in absence of data */ +} + +CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + return cf->next? + cf->next->cft->keep_alive(cf->next, data) : + CURLE_OK; +} + +CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, + struct Curl_easy *data) +{ + struct Curl_cfilter *cfn, *cf = *pcf; if(cf) { - conn->cfilter[index] = NULL; + *pcf = NULL; while(cf) { cfn = cf->next; + /* prevent destroying filter to mess with its sub-chain, since + * we have the reference now and will call destroy on it. + */ + cf->next = NULL; cf->cft->destroy(cf, data); free(cf); cf = cfn; @@ -144,6 +168,12 @@ void Curl_conn_cf_discard_all(struct Curl_easy *data, } } +void Curl_conn_cf_discard_all(struct Curl_easy *data, + struct connectdata *conn, int index) +{ + Curl_conn_cf_discard_chain(&conn->cfilter[index], data); +} + void Curl_conn_close(struct Curl_easy *data, int index) { struct Curl_cfilter *cf; @@ -160,7 +190,6 @@ ssize_t Curl_conn_recv(struct Curl_easy *data, int num, char *buf, size_t len, CURLcode *code) { struct Curl_cfilter *cf; - ssize_t nread; DEBUGASSERT(data); DEBUGASSERT(data->conn); @@ -169,13 +198,9 @@ ssize_t Curl_conn_recv(struct Curl_easy *data, int num, char *buf, cf = cf->next; } if(cf) { - nread = cf->cft->do_recv(cf, data, buf, len, code); - /* DEBUGF(infof(data, "Curl_conn_recv(handle=%p, index=%d)" - "-> %ld, err=%d", data, num, nread, *code));*/ - return nread; + return cf->cft->do_recv(cf, data, buf, len, code); } - failf(data, "no filter connected, conn=%ld, sockindex=%d", - data->conn->connection_id, num); + failf(data, CMSGI(data->conn, num, "recv: no filter connected")); *code = CURLE_FAILED_INIT; return -1; } @@ -184,7 +209,6 @@ ssize_t Curl_conn_send(struct Curl_easy *data, int num, const void *mem, size_t len, CURLcode *code) { struct Curl_cfilter *cf; - ssize_t nwritten; DEBUGASSERT(data); DEBUGASSERT(data->conn); @@ -193,13 +217,10 @@ ssize_t Curl_conn_send(struct Curl_easy *data, int num, cf = cf->next; } if(cf) { - nwritten = cf->cft->do_send(cf, data, mem, len, code); - /* DEBUGF(infof(data, "Curl_conn_send(handle=%p, index=%d, len=%ld)" - " -> %ld, err=%d", data, num, len, nwritten, *code));*/ - return nwritten; + return cf->cft->do_send(cf, data, mem, len, code); } - failf(data, "no filter connected, conn=%ld, sockindex=%d", - data->conn->connection_id, num); + failf(data, CMSGI(data->conn, num, "send: no filter connected")); + DEBUGASSERT(0); *code = CURLE_FAILED_INIT; return -1; } @@ -234,12 +255,31 @@ void Curl_conn_cf_add(struct Curl_easy *data, DEBUGASSERT(!cf->conn); DEBUGASSERT(!cf->next); - DEBUGF(infof(data, CMSGI(conn, index, "cf_add(filter=%s)"), - cf->cft->name)); cf->next = conn->cfilter[index]; cf->conn = conn; cf->sockindex = index; conn->cfilter[index] = cf; + DEBUGF(LOG_CF(data, cf, "added")); +} + +void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at, + struct Curl_cfilter *cf_new) +{ + struct Curl_cfilter *tail, **pnext; + + DEBUGASSERT(cf_at); + DEBUGASSERT(cf_new); + DEBUGASSERT(!cf_new->conn); + + tail = cf_at->next; + cf_at->next = cf_new; + do { + cf_new->conn = cf_at->conn; + cf_new->sockindex = cf_at->sockindex; + pnext = &cf_new->next; + cf_new = cf_new->next; + } while(cf_new); + *pnext = tail; } void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -259,97 +299,54 @@ void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data) free(cf); } -ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) +CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) { - return cf->cft->do_send(cf, data, buf, len, err); + if(cf) + return cf->cft->connect(cf, data, blocking, done); + return CURLE_FAILED_INIT; } -ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, - char *buf, size_t len, CURLcode *err) +void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - return cf->cft->do_recv(cf, data, buf, len, err); + if(cf) + cf->cft->close(cf, data); } -CURLcode Curl_conn_setup(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - const struct Curl_dns_entry *remotehost, - int ssl_mode) +int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *socks) { - struct Curl_cfilter *cf; - CURLcode result; - - DEBUGASSERT(data); - /* If no filter is set, we have the "default" setup of connection filters. - * The filter chain from botton to top will be: - * - SOCKET socket filter for outgoing connection to remotehost - * if http_proxy tunneling is engaged: - * - SSL if proxytype is CURLPROXY_HTTPS - * - HTTP_PROXY_TUNNEL - * otherwise, if socks_proxy is engaged: - * - SOCKS_PROXY_TUNNEL - * - SSL if conn->handler has PROTOPT_SSL - */ - if(!conn->cfilter[sockindex]) { - DEBUGF(infof(data, DMSGI(data, sockindex, "setup, init filter chain"))); - result = Curl_conn_socket_set(data, conn, sockindex); - if(result) - goto out; - -#ifndef CURL_DISABLE_PROXY - if(conn->bits.socksproxy) { - result = Curl_conn_socks_proxy_add(data, conn, sockindex); - if(result) - goto out; - } + if(cf) + return cf->cft->get_select_socks(cf, data, socks); + return 0; +} - if(conn->bits.httpproxy) { -#ifdef USE_SSL - if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) { - result = Curl_ssl_cfilter_proxy_add(data, conn, sockindex); - if(result) - goto out; - } -#endif /* USE_SSL */ - -#if !defined(CURL_DISABLE_HTTP) - if(conn->bits.tunnel_proxy) { - result = Curl_conn_http_proxy_add(data, conn, sockindex); - if(result) - goto out; - } -#endif /* !CURL_DISABLE_HTTP */ - } -#endif /* !CURL_DISABLE_PROXY */ - -#ifdef USE_SSL - if(ssl_mode == CURL_CF_SSL_ENABLE - || (ssl_mode != CURL_CF_SSL_DISABLE - && conn->handler->flags & PROTOPT_SSL)) { - result = Curl_ssl_cfilter_add(data, conn, sockindex); - if(result) - goto out; - } -#else - (void)ssl_mode; -#endif /* USE_SSL */ - -#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) - if(data->set.haproxyprotocol) { - result = Curl_conn_haproxy_add(data, conn, sockindex); - if(result) - goto out; - } -#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ +bool Curl_conn_cf_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + if(cf) + return cf->cft->has_data_pending(cf, data); + return FALSE; +} - } - DEBUGASSERT(conn->cfilter[sockindex]); - cf = data->conn->cfilter[sockindex]; - result = cf->cft->setup(cf, data, remotehost); +ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + if(cf) + return cf->cft->do_send(cf, data, buf, len, err); + *err = CURLE_SEND_ERROR; + return -1; +} -out: - return result; +ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + if(cf) + return cf->cft->do_recv(cf, data, buf, len, err); + *err = CURLE_RECV_ERROR; + return -1; } CURLcode Curl_conn_connect(struct Curl_easy *data, @@ -358,16 +355,26 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, bool *done) { struct Curl_cfilter *cf; - CURLcode result; + CURLcode result = CURLE_OK; DEBUGASSERT(data); + DEBUGASSERT(data->conn); cf = data->conn->cfilter[sockindex]; DEBUGASSERT(cf); - result = cf->cft->connect(cf, data, blocking, done); + if(!cf) + return CURLE_FAILED_INIT; + + *done = cf->connected; + if(!*done) { + result = cf->cft->connect(cf, data, blocking, done); + if(!result && *done) { + Curl_conn_ev_update_info(data, data->conn); + Curl_conn_ev_report_stats(data, data->conn); + data->conn->keepalive = Curl_now(); + } + } - DEBUGF(infof(data, DMSGI(data, sockindex, "connect(block=%d)-> %d, done=%d"), - blocking, result, *done)); return result; } @@ -394,11 +401,10 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex) return FALSE; } -bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex) +bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex) { - struct Curl_cfilter *cf = data->conn? data->conn->cfilter[sockindex] : NULL; + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; - (void)data; for(; cf; cf = cf->next) { if(cf->cft->flags & CF_TYPE_SSL) return TRUE; @@ -408,6 +414,19 @@ bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex) return FALSE; } +bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex) +{ + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + + for(; cf; cf = cf->next) { + if(cf->cft->flags & CF_TYPE_MULTIPLEX) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT + || cf->cft->flags & CF_TYPE_SSL) + return FALSE; + } + return FALSE; +} bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex) { @@ -416,8 +435,6 @@ bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex) (void)data; DEBUGASSERT(data); DEBUGASSERT(data->conn); - if(Curl_recv_has_postponed_data(data->conn, sockindex)) - return TRUE; cf = data->conn->cfilter[sockindex]; while(cf && !cf->connected) { @@ -437,46 +454,16 @@ int Curl_conn_get_select_socks(struct Curl_easy *data, int sockindex, DEBUGASSERT(data); DEBUGASSERT(data->conn); cf = data->conn->cfilter[sockindex]; + + /* if the next one is not yet connected, that's the one we want */ + while(cf && cf->next && !cf->next->connected) + cf = cf->next; if(cf) { return cf->cft->get_select_socks(cf, data, socks); } return GETSOCK_BLANK; } -void Curl_conn_attach_data(struct connectdata *conn, - struct Curl_easy *data) -{ - size_t i; - struct Curl_cfilter *cf; - - for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) { - cf = conn->cfilter[i]; - if(cf) { - while(cf) { - cf->cft->attach_data(cf, data); - cf = cf->next; - } - } - } -} - -void Curl_conn_detach_data(struct connectdata *conn, - struct Curl_easy *data) -{ - size_t i; - struct Curl_cfilter *cf; - - for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) { - cf = conn->cfilter[i]; - if(cf) { - while(cf) { - cf->cft->detach_data(cf, data); - cf = cf->next; - } - } - } -} - void Curl_conn_get_host(struct Curl_easy *data, int sockindex, const char **phost, const char **pdisplay_host, int *pport) @@ -491,7 +478,7 @@ void Curl_conn_get_host(struct Curl_easy *data, int sockindex, else { /* Some filter ask during shutdown for this, mainly for debugging * purposes. We hand out the defaults, however this is not always - * accurate, as the connction might be tunneled, etc. But all that + * accurate, as the connection might be tunneled, etc. But all that * state is already gone here. */ *phost = data->conn->host.name; *pdisplay_host = data->conn->host.dispname; @@ -499,4 +486,158 @@ void Curl_conn_get_host(struct Curl_easy *data, int sockindex, } } +CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + (void)cf; + (void)data; + (void)event; + (void)arg1; + (void)arg2; + return CURLE_OK; +} + +CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool ignore_result, + int event, int arg1, void *arg2) +{ + CURLcode result = CURLE_OK; + + for(; cf; cf = cf->next) { + if(Curl_cf_def_cntrl == cf->cft->cntrl) + continue; + result = cf->cft->cntrl(cf, data, event, arg1, arg2); + if(!ignore_result && result) + break; + } + return result; +} + +curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + curl_socket_t sock; + if(cf && !cf->cft->query(cf, data, CF_QUERY_SOCKET, NULL, &sock)) + return sock; + return CURL_SOCKET_BAD; +} + +curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex) +{ + struct Curl_cfilter *cf; + + cf = data->conn? data->conn->cfilter[sockindex] : NULL; + /* if the top filter has not connected, ask it (and its sub-filters) + * for the socket. Otherwise conn->sock[sockindex] should have it. + */ + if(cf && !cf->connected) + return Curl_conn_cf_get_socket(cf, data); + return data->conn? data->conn->sock[sockindex] : CURL_SOCKET_BAD; +} + +static CURLcode cf_cntrl_all(struct connectdata *conn, + struct Curl_easy *data, + bool ignore_result, + int event, int arg1, void *arg2) +{ + CURLcode result = CURLE_OK; + size_t i; + + for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) { + result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result, + event, arg1, arg2); + if(!ignore_result && result) + break; + } + return result; +} + +void Curl_conn_ev_data_attach(struct connectdata *conn, + struct Curl_easy *data) +{ + cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_ATTACH, 0, NULL); +} + +void Curl_conn_ev_data_detach(struct connectdata *conn, + struct Curl_easy *data) +{ + cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_DETACH, 0, NULL); +} + +CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data) +{ + return cf_cntrl_all(data->conn, data, FALSE, + CF_CTRL_DATA_SETUP, 0, NULL); +} + +CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data) +{ + return cf_cntrl_all(data->conn, data, FALSE, + CF_CTRL_DATA_IDLE, 0, NULL); +} + +/** + * Notify connection filters that the transfer represented by `data` + * is donw with sending data (e.g. has uploaded everything). + */ +void Curl_conn_ev_data_done_send(struct Curl_easy *data) +{ + cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE_SEND, 0, NULL); +} + +/** + * Notify connection filters that the transfer represented by `data` + * is finished - eventually premature, e.g. before being complete. + */ +void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature) +{ + cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE, premature, NULL); +} + +CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause) +{ + return cf_cntrl_all(data->conn, data, FALSE, + CF_CTRL_DATA_PAUSE, do_pause, NULL); +} + +void Curl_conn_ev_update_info(struct Curl_easy *data, + struct connectdata *conn) +{ + cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL); +} + +void Curl_conn_ev_report_stats(struct Curl_easy *data, + struct connectdata *conn) +{ + cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_REPORT_STATS, 0, NULL); +} + +bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn) +{ + struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET]; + return cf && !cf->conn->bits.close && cf->cft->is_alive(cf, data); +} + +CURLcode Curl_conn_keep_alive(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + return cf? cf->cft->keep_alive(cf, data) : CURLE_OK; +} + +size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + CURLcode result; + int n = 0; + + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + result = cf? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT, + &n, NULL) : CURLE_UNKNOWN_OPTION; + return (result || n <= 0)? 1 : (size_t)n; +} diff --git a/Utilities/cmcurl/lib/cfilters.h b/Utilities/cmcurl/lib/cfilters.h index 4b81b42e6b..94dc53f813 100644 --- a/Utilities/cmcurl/lib/cfilters.h +++ b/Utilities/cmcurl/lib/cfilters.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -36,11 +36,6 @@ struct connectdata; typedef void Curl_cft_destroy_this(struct Curl_cfilter *cf, struct Curl_easy *data); -/* Setup the connection for `data`, using destination `remotehost`. - */ -typedef CURLcode Curl_cft_setup(struct Curl_cfilter *cf, - struct Curl_easy *data, - const struct Curl_dns_entry *remotehost); typedef void Curl_cft_close(struct Curl_cfilter *cf, struct Curl_easy *data); @@ -89,29 +84,89 @@ typedef ssize_t Curl_cft_recv(struct Curl_cfilter *cf, size_t len, /* amount to read */ CURLcode *err); /* error to return */ -typedef void Curl_cft_attach_data(struct Curl_cfilter *cf, - struct Curl_easy *data); -typedef void Curl_cft_detach_data(struct Curl_cfilter *cf, - struct Curl_easy *data); +typedef bool Curl_cft_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data); + +typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data); /** - * The easy handle `data` is being detached (no longer served) - * by connection `conn`. All filters are informed to release any resources - * related to `data`. - * Note: there may be several `data` attached to a connection at the same - * time. + * Events/controls for connection filters, their arguments and + * return code handling. Filter callbacks are invoked "top down". + * Return code handling: + * "first fail" meaning that the first filter returning != CURLE_OK, will + * abort further event distribution and determine the result. + * "ignored" meaning return values are ignored and the event is distributed + * to all filters in the chain. Overall result is always CURLE_OK. */ -void Curl_conn_detach(struct connectdata *conn, struct Curl_easy *data); +/* data event arg1 arg2 return */ +#define CF_CTRL_DATA_ATTACH 1 /* 0 NULL ignored */ +#define CF_CTRL_DATA_DETACH 2 /* 0 NULL ignored */ +#define CF_CTRL_DATA_SETUP 4 /* 0 NULL first fail */ +#define CF_CTRL_DATA_IDLE 5 /* 0 NULL first fail */ +#define CF_CTRL_DATA_PAUSE 6 /* on/off NULL first fail */ +#define CF_CTRL_DATA_DONE 7 /* premature NULL ignored */ +#define CF_CTRL_DATA_DONE_SEND 8 /* 0 NULL ignored */ +/* update conn info at connection and data */ +#define CF_CTRL_CONN_INFO_UPDATE (256+0) /* 0 NULL ignored */ +/* report conn statistics (timers) for connection and data */ +#define CF_CTRL_CONN_REPORT_STATS (256+1) /* 0 NULL ignored */ +/** + * Handle event/control for the filter. + * Implementations MUST NOT chain calls to cf->next. + */ +typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2); + + +/** + * Queries to ask via a `Curl_cft_query *query` method on a cfilter chain. + * - MAX_CONCURRENT: the maximum number of parallel transfers the filter + * chain expects to handle at the same time. + * default: 1 if no filter overrides. + * - CONNECT_REPLY_MS: milliseconds until the first indication of a server + * response was received on a connect. For TCP, this + * reflects the time until the socket connected. On UDP + * this gives the time the first bytes from the server + * were received. + * -1 if not determined yet. + * - CF_QUERY_SOCKET: the socket used by the filter chain + */ +/* query res1 res2 */ +#define CF_QUERY_MAX_CONCURRENT 1 /* number - */ +#define CF_QUERY_CONNECT_REPLY_MS 2 /* number - */ +#define CF_QUERY_SOCKET 3 /* - curl_socket_t */ + +/** + * Query the cfilter for properties. Filters ignorant of a query will + * pass it "down" the filter chain. + */ +typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2); + +/** + * Type flags for connection filters. A filter can have none, one or + * many of those. Use to evaluate state/capabilities of a filter chain. + * + * CF_TYPE_IP_CONNECT: provides an IP connection or sth equivalent, like + * a CONNECT tunnel, a UNIX domain socket, a QUIC + * connection, etc. + * CF_TYPE_SSL: provide SSL/TLS + * CF_TYPE_MULTIPLEX: provides multiplexing of easy handles + */ #define CF_TYPE_IP_CONNECT (1 << 0) #define CF_TYPE_SSL (1 << 1) +#define CF_TYPE_MULTIPLEX (1 << 2) /* A connection filter type, e.g. specific implementation. */ struct Curl_cftype { const char *name; /* name of the filter type */ - long flags; /* flags of filter type */ + int flags; /* flags of filter type */ + int log_level; /* log level for such filters */ Curl_cft_destroy_this *destroy; /* destroy resources of this cf */ - Curl_cft_setup *setup; /* setup for a connection */ Curl_cft_connect *connect; /* establish connection */ Curl_cft_close *close; /* close conn */ Curl_cft_get_host *get_host; /* host filter talks to */ @@ -119,8 +174,10 @@ struct Curl_cftype { Curl_cft_data_pending *has_data_pending;/* conn has data pending */ Curl_cft_send *do_send; /* send data */ Curl_cft_recv *do_recv; /* receive data */ - Curl_cft_attach_data *attach_data; /* data is being handled here */ - Curl_cft_detach_data *detach_data; /* data is no longer handled here */ + Curl_cft_cntrl *cntrl; /* events/control */ + Curl_cft_conn_is_alive *is_alive; /* FALSE if conn is dead, Jim! */ + Curl_cft_conn_keep_alive *keep_alive; /* try to keep it alive */ + Curl_cft_query *query; /* query filter chain */ }; /* A connection filter instance, e.g. registered at a connection */ @@ -129,7 +186,7 @@ struct Curl_cfilter { struct Curl_cfilter *next; /* next filter in chain */ void *ctx; /* filter type specific settings */ struct connectdata *conn; /* the connection this filter belongs to */ - int sockindex; /* TODO: like to get rid off this */ + int sockindex; /* the index the filter is installed at */ BIT(connected); /* != 0 iff this filter is connected */ }; @@ -139,9 +196,6 @@ void Curl_cf_def_destroy_this(struct Curl_cfilter *cf, /* Default implementations for the type functions, implementing pass-through * the filter chain. */ -CURLcode Curl_cf_def_setup(struct Curl_cfilter *cf, - struct Curl_easy *data, - const struct Curl_dns_entry *remotehost); void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data); CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf, struct Curl_easy *data, @@ -158,16 +212,22 @@ ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err); ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err); -void Curl_cf_def_attach_data(struct Curl_cfilter *cf, - struct Curl_easy *data); -void Curl_cf_def_detach_data(struct Curl_cfilter *cf, - struct Curl_easy *data); +CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2); +bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data); +CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data); +CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2); /** * Create a new filter instance, unattached to the filter chain. * Use Curl_conn_cf_add() to add it to the chain. * @param pcf on success holds the created instance - * @parm cft the filter type + * @param cft the filter type * @param ctx the type specific context to use */ CURLcode Curl_cf_create(struct Curl_cfilter **pcf, @@ -176,7 +236,7 @@ CURLcode Curl_cf_create(struct Curl_cfilter **pcf, /** * Add a filter instance to the `sockindex` filter chain at connection - * `data->conn`. The filter must not already be attached. It is inserted at + * `conn`. The filter must not already be attached. It is inserted at * the start of the chain (top). */ void Curl_conn_cf_add(struct Curl_easy *data, @@ -185,11 +245,11 @@ void Curl_conn_cf_add(struct Curl_easy *data, struct Curl_cfilter *cf); /** - * Remove and destroy all filters at chain `sockindex` on connection `conn`. + * Insert a filter (chain) after `cf_at`. + * `cf_new` must not already be attached. */ -void Curl_conn_cf_discard_all(struct Curl_easy *data, - struct connectdata *conn, - int sockindex); +void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at, + struct Curl_cfilter *cf_new); /** * Discard, e.g. remove and destroy a specific filter instance. @@ -198,29 +258,51 @@ void Curl_conn_cf_discard_all(struct Curl_easy *data, */ void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data); +/** + * Discard all cfilters starting with `*pcf` and clearing it afterwards. + */ +void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, + struct Curl_easy *data); + +/** + * Remove and destroy all filters at chain `sockindex` on connection `conn`. + */ +void Curl_conn_cf_discard_all(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + +CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done); +void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data); +int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *socks); +bool Curl_conn_cf_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data); ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err); ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err); +CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool ignore_result, + int event, int arg1, void *arg2); + +/** + * Get the socket used by the filter chain starting at `cf`. + * Returns CURL_SOCKET_BAD if not available. + */ +curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf, + struct Curl_easy *data); + #define CURL_CF_SSL_DEFAULT -1 #define CURL_CF_SSL_DISABLE 0 #define CURL_CF_SSL_ENABLE 1 /** - * Setup the filter chain at `sockindex` in connection `conn`, invoking - * the instance `setup(remotehost)` methods. If no filter chain is - * installed yet, inspects the configuration in `data` to install a - * suitable filter chain. - */ -CURLcode Curl_conn_setup(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - const struct Curl_dns_entry *remotehost, - int ssl_mode); - -/** * Bring the filter chain at `sockindex` for connection `data->conn` into * connected state. Which will set `*done` to TRUE. * This can be called on an already connected chain with no side effects. @@ -248,7 +330,12 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex); * (or will be once connected). This will return FALSE, if SSL * is only used in proxying and not for the tunnel itself. */ -bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex); +bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex); + +/** + * Connection provides multiplexing of easy handles at `socketindex`. + */ +bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex); /** * Close the filter chain at `sockindex` for connection `data->conn`. @@ -264,6 +351,12 @@ bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex); /** + * Return the socket used on data's connection for the index. + * Returns CURL_SOCKET_BAD if not available. + */ +curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex); + +/** * Get any select fd flags and the socket filters at chain `sockindex` * at connection `conn` might be waiting for. */ @@ -289,13 +382,12 @@ ssize_t Curl_conn_send(struct Curl_easy *data, int sockindex, const void *buf, size_t len, CURLcode *code); /** - * The easy handle `data` is being attached (served) by connection `conn`. - * All filters are informed to adapt to handling `data`. - * Note: there may be several `data` attached to a connection at the same - * time. + * The easy handle `data` is being attached to `conn`. This does + * not mean that data will actually do a transfer. Attachment is + * also used for temporary actions on the connection. */ -void Curl_conn_attach_data(struct connectdata *conn, - struct Curl_easy *data); +void Curl_conn_ev_data_attach(struct connectdata *conn, + struct Curl_easy *data); /** * The easy handle `data` is being detached (no longer served) @@ -304,12 +396,141 @@ void Curl_conn_attach_data(struct connectdata *conn, * Note: there may be several `data` attached to a connection at the same * time. */ -void Curl_conn_detach_data(struct connectdata *conn, - struct Curl_easy *data); +void Curl_conn_ev_data_detach(struct connectdata *conn, + struct Curl_easy *data); + +/** + * Notify connection filters that they need to setup data for + * a transfer. + */ +CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data); + +/** + * Notify connection filters that now would be a good time to + * perform any idle, e.g. time related, actions. + */ +CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data); + +/** + * Notify connection filters that the transfer represented by `data` + * is donw with sending data (e.g. has uploaded everything). + */ +void Curl_conn_ev_data_done_send(struct Curl_easy *data); + +/** + * Notify connection filters that the transfer represented by `data` + * is finished - eventually premature, e.g. before being complete. + */ +void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature); + +/** + * Notify connection filters that the transfer of data is paused/unpaused. + */ +CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause); + +/** + * Inform connection filters to update their info in `conn`. + */ +void Curl_conn_ev_update_info(struct Curl_easy *data, + struct connectdata *conn); + +/** + * Inform connection filters to report statistics. + */ +void Curl_conn_ev_report_stats(struct Curl_easy *data, + struct connectdata *conn); + +/** + * Check if FIRSTSOCKET's cfilter chain deems connection alive. + */ +bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn); + +/** + * Try to upkeep the connection filters at sockindex. + */ +CURLcode Curl_conn_keep_alive(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); void Curl_conn_get_host(struct Curl_easy *data, int sockindex, const char **phost, const char **pdisplay_host, int *pport); +/** + * Get the maximum number of parallel transfers the connection + * expects to be able to handle at `sockindex`. + */ +size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + + +/** + * Types and macros used to keep the current easy handle in filter calls, + * allowing for nested invocations. See #10336. + * + * `cf_call_data` is intended to be a member of the cfilter's `ctx` type. + * A filter defines the macro `CF_CTX_CALL_DATA` to give access to that. + * + * With all values 0, the default, this indicates that there is no cfilter + * call with `data` ongoing. + * Macro `CF_DATA_SAVE` preserves the current `cf_call_data` in a local + * variable and sets the `data` given, incrementing the `depth` counter. + * + * Macro `CF_DATA_RESTORE` restores the old values from the local variable, + * while checking that `depth` values are as expected (debug build), catching + * cases where a "lower" RESTORE was not called. + * + * Finally, macro `CF_DATA_CURRENT` gives the easy handle of the current + * invocation. + */ +struct cf_call_data { + struct Curl_easy *data; +#ifdef DEBUGBUILD + int depth; +#endif +}; + +/** + * define to access the `struct cf_call_data for a cfilter. Normally + * a member in the cfilter's `ctx`. + * + * #define CF_CTX_CALL_DATA(cf) -> struct cf_call_data instance +*/ + +#ifdef DEBUGBUILD + +#define CF_DATA_SAVE(save, cf, data) \ + do { \ + (save) = CF_CTX_CALL_DATA(cf); \ + DEBUGASSERT((save).data == NULL || (save).depth > 0); \ + CF_CTX_CALL_DATA(cf).depth++; \ + CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \ + } while(0) + +#define CF_DATA_RESTORE(cf, save) \ + do { \ + DEBUGASSERT(CF_CTX_CALL_DATA(cf).depth == (save).depth + 1); \ + DEBUGASSERT((save).data == NULL || (save).depth > 0); \ + CF_CTX_CALL_DATA(cf) = (save); \ + } while(0) + +#else /* DEBUGBUILD */ + +#define CF_DATA_SAVE(save, cf, data) \ + do { \ + (save) = CF_CTX_CALL_DATA(cf); \ + CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \ + } while(0) + +#define CF_DATA_RESTORE(cf, save) \ + do { \ + CF_CTX_CALL_DATA(cf) = (save); \ + } while(0) + +#endif /* !DEBUGBUILD */ + +#define CF_DATA_CURRENT(cf) \ + ((cf)? (CF_CTX_CALL_DATA(cf).data) : NULL) #endif /* HEADER_CURL_CFILTERS_H */ diff --git a/Utilities/cmcurl/lib/conncache.c b/Utilities/cmcurl/lib/conncache.c index a557ac6dc9..c21b96ca83 100644 --- a/Utilities/cmcurl/lib/conncache.c +++ b/Utilities/cmcurl/lib/conncache.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2016, Linus Nielsen Feltzing, <linus@haxx.se> - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se> + * 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 diff --git a/Utilities/cmcurl/lib/conncache.h b/Utilities/cmcurl/lib/conncache.h index 94664bc357..959767d596 100644 --- a/Utilities/cmcurl/lib/conncache.h +++ b/Utilities/cmcurl/lib/conncache.h @@ -7,8 +7,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2015 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2012 - 2014, Linus Nielsen Feltzing, <linus@haxx.se> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/connect.c b/Utilities/cmcurl/lib/connect.c index af0413836b..993a7f919b 100644 --- a/Utilities/cmcurl/lib/connect.c +++ b/Utilities/cmcurl/lib/connect.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -59,106 +59,30 @@ #include "strerror.h" #include "cfilters.h" #include "connect.h" +#include "cf-http.h" +#include "cf-socket.h" #include "select.h" #include "url.h" /* for Curl_safefree() */ #include "multiif.h" #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "inet_ntop.h" #include "inet_pton.h" -#include "vtls/vtls.h" /* for Curl_ssl_check_cxn() */ +#include "vtls/vtls.h" /* for vtsl cfilters */ #include "progress.h" #include "warnless.h" #include "conncache.h" #include "multihandle.h" #include "share.h" #include "version_win32.h" -#include "quic.h" +#include "vquic/vquic.h" /* for quic cfilters */ +#include "http_proxy.h" +#include "socks.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" -static bool verifyconnect(curl_socket_t sockfd, int *error); - -#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H) -/* 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) -#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR,4) - -struct tcp_keepalive { - u_long onoff; - u_long keepalivetime; - u_long keepaliveinterval; -}; -#endif - -static void -tcpkeepalive(struct Curl_easy *data, - curl_socket_t sockfd) -{ - int optval = data->set.tcp_keepalive?1:0; - - /* only set IDLE and INTVL if setting KEEPALIVE is successful */ - if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd); - } - else { -#if defined(SIO_KEEPALIVE_VALS) - struct tcp_keepalive vals; - DWORD dummy; - vals.onoff = 1; - optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); - vals.keepalivetime = optval; - optval = curlx_sltosi(data->set.tcp_keepintvl); - KEEPALIVE_FACTOR(optval); - 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()); - } -#else -#ifdef TCP_KEEPIDLE - optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); - if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd); - } -#elif defined(TCP_KEEPALIVE) - /* Mac OS X style */ - optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); - if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd); - } -#endif -#ifdef TCP_KEEPINTVL - optval = curlx_sltosi(data->set.tcp_keepintvl); - 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); - } -#endif -#endif - } -} - -static CURLcode -singleipconnect(struct Curl_easy *data, - struct connectdata *conn, - const struct Curl_addrinfo *ai, /* start connecting to this */ - int tempindex); /* 0 or 1 among the temp ones */ /* * Curl_timeleft() returns the amount of milliseconds left allowed for the @@ -230,387 +154,6 @@ timediff_t Curl_timeleft(struct Curl_easy *data, return timeout_ms; } -static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t sockfd, int af, unsigned int scope) -{ - struct Curl_sockaddr_storage sa; - struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ - curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ - struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; -#ifdef ENABLE_IPV6 - struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; -#endif - - struct Curl_dns_entry *h = NULL; - unsigned short port = data->set.localport; /* use this port number, 0 for - "random" */ - /* how many port numbers to try to bind to, increasing one at a time */ - int portnum = data->set.localportrange; - const char *dev = data->set.str[STRING_DEVICE]; - int error; -#ifdef IP_BIND_ADDRESS_NO_PORT - int on = 1; -#endif -#ifndef ENABLE_IPV6 - (void)scope; -#endif - - /************************************************************* - * Select device to bind socket to - *************************************************************/ - if(!dev && !port) - /* no local kind of binding was requested */ - return CURLE_OK; - - memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); - - if(dev && (strlen(dev)<255) ) { - char myhost[256] = ""; - int done = 0; /* -1 for error, 1 for address found */ - bool is_interface = FALSE; - bool is_host = FALSE; - static const char *if_prefix = "if!"; - static const char *host_prefix = "host!"; - - if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) { - dev += strlen(if_prefix); - is_interface = TRUE; - } - else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) { - dev += strlen(host_prefix); - is_host = TRUE; - } - - /* interface */ - if(!is_host) { -#ifdef SO_BINDTODEVICE - /* I am not sure any other OSs than Linux that provide this feature, - * and at the least I cannot test. --Ben - * - * This feature allows one to tightly bind the local socket to a - * particular interface. This will force even requests to other - * local interfaces to go out the external interface. - * - * - * Only bind to the interface when specified as interface, not just - * as a hostname or ip address. - * - * interface might be a VRF, eg: vrf-blue, which means it cannot be - * converted to an IP address and would fail Curl_if2ip. Simply try - * to use it straight away. - */ - if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, - dev, (curl_socklen_t)strlen(dev) + 1) == 0) { - /* This is typically "errno 1, error: Operation not permitted" if - * you're not running as root or another suitable privileged - * user. - * If it succeeds it means the parameter was a valid interface and - * not an IP address. Return immediately. - */ - return CURLE_OK; - } -#endif - - switch(Curl_if2ip(af, -#ifdef ENABLE_IPV6 - scope, conn->scope_id, -#endif - dev, myhost, sizeof(myhost))) { - case IF2IP_NOT_FOUND: - if(is_interface) { - /* Do not fall back to treating it as a host name */ - failf(data, "Couldn't bind to interface '%s'", dev); - return CURLE_INTERFACE_FAILED; - } - break; - case IF2IP_AF_NOT_SUPPORTED: - /* Signal the caller to try another address family if available */ - return CURLE_UNSUPPORTED_PROTOCOL; - case IF2IP_FOUND: - is_interface = TRUE; - /* - * We now have the numerical IP address in the 'myhost' buffer - */ - infof(data, "Local Interface %s is ip %s using address family %i", - dev, myhost, af); - done = 1; - break; - } - } - if(!is_interface) { - /* - * This was not an interface, resolve the name as a host name - * or IP number - * - * Temporarily force name resolution to use only the address type - * of the connection. The resolve functions should really be changed - * to take a type parameter instead. - */ - unsigned char ipver = conn->ip_version; - int rc; - - if(af == AF_INET) - conn->ip_version = CURL_IPRESOLVE_V4; -#ifdef ENABLE_IPV6 - else if(af == AF_INET6) - conn->ip_version = CURL_IPRESOLVE_V6; -#endif - - rc = Curl_resolv(data, dev, 0, FALSE, &h); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(data, &h); - conn->ip_version = ipver; - - if(h) { - /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ - Curl_printable_address(h->addr, myhost, sizeof(myhost)); - infof(data, "Name '%s' family %i resolved to '%s' family %i", - dev, af, myhost, h->addr->ai_family); - Curl_resolv_unlock(data, h); - if(af != h->addr->ai_family) { - /* bad IP version combo, signal the caller to try another address - family if available */ - return CURLE_UNSUPPORTED_PROTOCOL; - } - done = 1; - } - else { - /* - * provided dev was no interface (or interfaces are not supported - * e.g. solaris) no ip address and no domain we fail here - */ - done = -1; - } - } - - if(done > 0) { -#ifdef ENABLE_IPV6 - /* IPv6 address */ - if(af == AF_INET6) { -#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID - char *scope_ptr = strchr(myhost, '%'); - if(scope_ptr) - *(scope_ptr++) = '\0'; -#endif - if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) { - si6->sin6_family = AF_INET6; - si6->sin6_port = htons(port); -#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID - if(scope_ptr) { - /* The "myhost" string either comes from Curl_if2ip or from - Curl_printable_address. The latter returns only numeric scope - IDs and the former returns none at all. So the scope ID, if - present, is known to be numeric */ - unsigned long scope_id = strtoul(scope_ptr, NULL, 10); - if(scope_id > UINT_MAX) - return CURLE_UNSUPPORTED_PROTOCOL; - - si6->sin6_scope_id = (unsigned int)scope_id; - } -#endif - } - sizeof_sa = sizeof(struct sockaddr_in6); - } - else -#endif - /* IPv4 address */ - if((af == AF_INET) && - (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { - si4->sin_family = AF_INET; - si4->sin_port = htons(port); - sizeof_sa = sizeof(struct sockaddr_in); - } - } - - if(done < 1) { - /* errorbuf is set false so failf will overwrite any message already in - the error buffer, so the user receives this error message instead of a - generic resolve error. */ - data->state.errorbuf = FALSE; - failf(data, "Couldn't bind to '%s'", dev); - return CURLE_INTERFACE_FAILED; - } - } - else { - /* no device was given, prepare sa to match af's needs */ -#ifdef ENABLE_IPV6 - if(af == AF_INET6) { - si6->sin6_family = AF_INET6; - si6->sin6_port = htons(port); - sizeof_sa = sizeof(struct sockaddr_in6); - } - else -#endif - if(af == AF_INET) { - si4->sin_family = AF_INET; - si4->sin_port = htons(port); - sizeof_sa = sizeof(struct sockaddr_in); - } - } -#ifdef IP_BIND_ADDRESS_NO_PORT - (void)setsockopt(sockfd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &on, sizeof(on)); -#endif - for(;;) { - if(bind(sockfd, sock, sizeof_sa) >= 0) { - /* we succeeded to bind */ - struct Curl_sockaddr_storage add; - curl_socklen_t size = sizeof(add); - memset(&add, 0, sizeof(struct Curl_sockaddr_storage)); - if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) { - char buffer[STRERROR_LEN]; - data->state.os_errno = error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return CURLE_INTERFACE_FAILED; - } - infof(data, "Local port: %hu", port); - conn->bits.bound = TRUE; - return CURLE_OK; - } - - if(--portnum > 0) { - port++; /* try next port */ - if(port == 0) - break; - infof(data, "Bind to local port %hu failed, trying next", port - 1); - /* We re-use/clobber the port variable here below */ - if(sock->sa_family == AF_INET) - si4->sin_port = ntohs(port); -#ifdef ENABLE_IPV6 - else - si6->sin6_port = ntohs(port); -#endif - } - else - break; - } - { - char buffer[STRERROR_LEN]; - data->state.os_errno = error = SOCKERRNO; - failf(data, "bind failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - } - - return CURLE_INTERFACE_FAILED; -} - -/* - * verifyconnect() returns TRUE if the connect really has happened. - */ -static bool verifyconnect(curl_socket_t sockfd, int *error) -{ - bool rc = TRUE; -#ifdef SO_ERROR - int err = 0; - curl_socklen_t errSize = sizeof(err); - -#ifdef WIN32 - /* - * In October 2003 we effectively nullified this function on Windows due to - * problems with it using all CPU in multi-threaded cases. - * - * In May 2004, we bring it back to offer more info back on connect failures. - * Gisle Vanem could reproduce the former problems with this function, but - * could avoid them by adding this SleepEx() call below: - * - * "I don't have Rational Quantify, but the hint from his post was - * ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe - * just Sleep(0) would be enough?) would release whatever - * mutex/critical-section the ntdll call is waiting on. - * - * Someone got to verify this on Win-NT 4.0, 2000." - */ - -#ifdef _WIN32_WCE - Sleep(0); -#else - SleepEx(0, FALSE); -#endif - -#endif - - if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize)) - err = SOCKERRNO; -#ifdef _WIN32_WCE - /* Old WinCE versions don't support SO_ERROR */ - if(WSAENOPROTOOPT == err) { - SET_SOCKERRNO(0); - err = 0; - } -#endif -#if defined(EBADIOCTL) && defined(__minix) - /* Minix 3.1.x doesn't support getsockopt on UDP sockets */ - if(EBADIOCTL == err) { - SET_SOCKERRNO(0); - err = 0; - } -#endif - if((0 == err) || (EISCONN == err)) - /* we are connected, awesome! */ - rc = TRUE; - else - /* This wasn't a successful connect */ - rc = FALSE; - if(error) - *error = err; -#else - (void)sockfd; - if(error) - *error = SOCKERRNO; -#endif - return rc; -} - -/* update tempaddr[tempindex] (to the next entry), makes sure to stick - to the correct family */ -static struct Curl_addrinfo *ainext(struct connectdata *conn, - int tempindex, - bool next) /* use next entry? */ -{ - struct Curl_addrinfo *ai = conn->tempaddr[tempindex]; - if(ai && next) - ai = ai->ai_next; - while(ai && (ai->ai_family != conn->tempfamily[tempindex])) - ai = ai->ai_next; - conn->tempaddr[tempindex] = ai; - return ai; -} - -/* Used within the multi interface. Try next IP address, returns error if no - more address exists or error */ -static CURLcode trynextip(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - int tempindex) -{ - CURLcode result = CURLE_COULDNT_CONNECT; - - /* First clean up after the failed socket. - Don't close it yet to ensure that the next IP's socket gets a different - file descriptor, which can prevent bugs when the curl_multi_socket_action - interface is used with certain select() replacements such as kqueue. */ - curl_socket_t fd_to_close = conn->tempsock[tempindex]; - conn->tempsock[tempindex] = CURL_SOCKET_BAD; - - if(sockindex == FIRSTSOCKET) { - struct Curl_addrinfo *ai = conn->tempaddr[tempindex]; - - while(ai) { - result = singleipconnect(data, conn, ai, tempindex); - if(result == CURLE_COULDNT_CONNECT) { - ai = ainext(conn, tempindex, TRUE); - continue; - } - break; - } - } - - if(fd_to_close != CURL_SOCKET_BAD) - Curl_closesocket(data, conn, fd_to_close); - - return result; -} - /* Copies connection info into the transfer handle to make it available when the transfer handle is no longer associated with the connection. */ void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn, @@ -629,6 +172,28 @@ void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn, data->info.conn_local_port = local_port; } +static const struct Curl_addrinfo * +addr_first_match(const struct Curl_addrinfo *addr, int family) +{ + while(addr) { + if(addr->ai_family == family) + return addr; + addr = addr->ai_next; + } + return NULL; +} + +static const struct Curl_addrinfo * +addr_next_match(const struct Curl_addrinfo *addr, int family) +{ + while(addr && addr->ai_next) { + addr = addr->ai_next; + if(addr->ai_family == family) + return addr; + } + return NULL; +} + /* retrieves ip address and port from a sockaddr structure. note it calls Curl_inet_ntop which sets errno on fail, not SOCKERRNO. */ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, @@ -686,623 +251,493 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, return FALSE; } -/* retrieves the start/end point information of a socket of an established - connection */ -void Curl_conninfo_remote(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t sockfd) -{ -#ifdef HAVE_GETPEERNAME - char buffer[STRERROR_LEN]; - struct Curl_sockaddr_storage ssrem; - curl_socklen_t plen; - int port; - plen = sizeof(struct Curl_sockaddr_storage); - memset(&ssrem, 0, sizeof(ssrem)); - if(getpeername(sockfd, (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, - conn->primary_ip, &port)) { - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - return; - } -#else - (void)data; - (void)conn; - (void)sockfd; -#endif -} +struct connfind { + long id_tofind; + struct connectdata *found; +}; -/* retrieves the start/end point information of a socket of an established - connection */ -void Curl_conninfo_local(struct Curl_easy *data, curl_socket_t sockfd, - char *local_ip, int *local_port) +static int conn_is_conn(struct Curl_easy *data, + struct connectdata *conn, void *param) { -#ifdef HAVE_GETSOCKNAME - char buffer[STRERROR_LEN]; - struct Curl_sockaddr_storage ssloc; - curl_socklen_t slen; - slen = sizeof(struct Curl_sockaddr_storage); - memset(&ssloc, 0, sizeof(ssloc)); - if(getsockname(sockfd, (struct sockaddr*) &ssloc, &slen)) { - int error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(error, buffer, sizeof(buffer))); - return; - } - if(!Curl_addr2string((struct sockaddr*)&ssloc, slen, - local_ip, local_port)) { - failf(data, "ssloc inet_ntop() failed with errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - return; - } -#else + struct connfind *f = (struct connfind *)param; (void)data; - (void)sockfd; - (void)local_ip; - (void)local_port; -#endif -} - -/* retrieves the start/end point information of a socket of an established - connection */ -void Curl_updateconninfo(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t sockfd) -{ - /* 'local_ip' and 'local_port' get filled with local's numerical - ip address and port number whenever an outgoing connection is - **established** from the primary socket to a remote address. */ - char local_ip[MAX_IPADR_LEN] = ""; - int local_port = -1; - - if(!conn->bits.reuse && - (conn->transport != TRNSPRT_TCP || !conn->bits.tcp_fastopen)) - Curl_conninfo_remote(data, conn, sockfd); - Curl_conninfo_local(data, sockfd, local_ip, &local_port); - - /* persist connection info in session handle */ - Curl_persistconninfo(data, conn, local_ip, local_port); -} - -/* - * post_connect() is called after a successful connect to the peer - */ -static void post_connect(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) -{ - Curl_updateconninfo(data, conn, conn->sock[sockindex]); - Curl_verboseconnect(data, conn); - data->info.numconnects++; /* to track the number of connections made */ + if(conn->connection_id == f->id_tofind) { + f->found = conn; + return 1; + } + return 0; } /* - * is_connected() checks if the socket has connected. + * Used to extract socket and connectdata struct for the most recent + * transfer on the given Curl_easy. + * + * The returned socket will be CURL_SOCKET_BAD in case of failure! */ -static CURLcode is_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *connected) +curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, + struct connectdata **connp) { - CURLcode result = CURLE_OK; - timediff_t allow; - int error = 0; - struct curltime now; - int rc = 0; - int i; - - DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET); - - *connected = FALSE; /* a very negative world view is best */ - - now = Curl_now(); - - /* Check if any of the conn->tempsock we use for establishing connections - * succeeded and, if so, close any ongoing other ones. - * Transfer the successful conn->tempsock to conn->sock[sockindex] - * and set conn->tempsock to CURL_SOCKET_BAD. - * If transport is QUIC, we need to shutdown the ongoing 'other' - * connect attempts in a QUIC appropriate way. */ - for(i = 0; i<2; i++) { - const int other = i ^ 1; - if(conn->tempsock[i] == CURL_SOCKET_BAD) - continue; - error = 0; -#ifdef ENABLE_QUIC - if(conn->transport == TRNSPRT_QUIC) { - result = Curl_quic_is_connected(data, conn, i, connected); - if(!result && *connected) { - /* use this socket from now on */ - conn->sock[sockindex] = conn->tempsock[i]; - conn->ip_addr = conn->tempaddr[i]; - conn->tempsock[i] = CURL_SOCKET_BAD; - post_connect(data, conn, sockindex); - connkeep(conn, "HTTP/3 default"); - if(conn->tempsock[other] != CURL_SOCKET_BAD) - Curl_quic_disconnect(data, conn, other); - return CURLE_OK; - } - /* When a QUIC connect attempt fails, the better error explanation is in - 'result' and not in errno */ - if(result) { - conn->tempsock[i] = CURL_SOCKET_BAD; - error = SOCKERRNO; - } - } - else -#endif - { -#ifdef mpeix - /* Call this function once now, and ignore the results. We do this to - "clear" the error state on the socket so that we can later read it - reliably. This is reported necessary on the MPE/iX operating - system. */ - (void)verifyconnect(conn->tempsock[i], NULL); -#endif + DEBUGASSERT(data); - /* check socket for connect */ - rc = SOCKET_WRITABLE(conn->tempsock[i], 0); - } + /* this works for an easy handle: + * - that has been used for curl_easy_perform() + * - that is associated with a multi handle, and whose connection + * was detached with CURLOPT_CONNECT_ONLY + */ + if((data->state.lastconnect_id != -1) && (data->multi_easy || data->multi)) { + struct connectdata *c; + struct connfind find; + find.id_tofind = data->state.lastconnect_id; + find.found = NULL; - if(rc == 0) { /* no connection yet */ - if(Curl_timediff(now, conn->connecttime) >= - conn->timeoutms_per_addr[i]) { - infof(data, "After %" CURL_FORMAT_TIMEDIFF_T - "ms connect time, move on!", conn->timeoutms_per_addr[i]); - error = ETIMEDOUT; - } + Curl_conncache_foreach(data, + data->share && (data->share->specifier + & (1<< CURL_LOCK_DATA_CONNECT))? + &data->share->conn_cache: + data->multi_easy? + &data->multi_easy->conn_cache: + &data->multi->conn_cache, &find, conn_is_conn); - /* should we try another protocol family? */ - if(i == 0 && !conn->bits.parallel_connect && - (Curl_timediff(now, conn->connecttime) >= - data->set.happy_eyeballs_timeout)) { - conn->bits.parallel_connect = TRUE; /* starting now */ - trynextip(data, conn, sockindex, 1); - } + if(!find.found) { + data->state.lastconnect_id = -1; + return CURL_SOCKET_BAD; } - else if(rc == CURL_CSELECT_OUT || conn->bits.tcp_fastopen) { - if(verifyconnect(conn->tempsock[i], &error)) { - /* we are connected with TCP, awesome! */ - - /* use this socket from now on */ - conn->sock[sockindex] = conn->tempsock[i]; - conn->ip_addr = conn->tempaddr[i]; - conn->tempsock[i] = CURL_SOCKET_BAD; -#ifdef ENABLE_IPV6 - conn->bits.ipv6 = (conn->ip_addr->ai_family == AF_INET6)?TRUE:FALSE; -#endif - /* close the other socket, if open */ - if(conn->tempsock[other] != CURL_SOCKET_BAD) { - Curl_closesocket(data, conn, conn->tempsock[other]); - conn->tempsock[other] = CURL_SOCKET_BAD; - } - - *connected = TRUE; - return CURLE_OK; - } - } - else if(rc & CURL_CSELECT_ERR) { - (void)verifyconnect(conn->tempsock[i], &error); - } + c = find.found; + if(connp) + /* only store this if the caller cares for it */ + *connp = c; + return c->sock[FIRSTSOCKET]; + } + return CURL_SOCKET_BAD; +} - /* - * The connection failed here, we should attempt to connect to the "next - * address" for the given host. But first remember the latest error. - */ - if(error) { - data->state.os_errno = error; - SET_SOCKERRNO(error); - if(conn->tempaddr[i]) { - CURLcode status; -#ifndef CURL_DISABLE_VERBOSE_STRINGS - char ipaddress[MAX_IPADR_LEN]; - char buffer[STRERROR_LEN]; - Curl_printable_address(conn->tempaddr[i], ipaddress, - sizeof(ipaddress)); -#ifdef ENABLE_QUIC - if(conn->transport == TRNSPRT_QUIC) { - infof(data, "connect to %s port %u failed: %s", - ipaddress, conn->port, curl_easy_strerror(result)); - } - else +/* + * Curl_conncontrol() marks streams or connection for closure. + */ +void Curl_conncontrol(struct connectdata *conn, + int ctrl /* see defines in header */ +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + , const char *reason #endif - infof(data, "connect to %s port %u failed: %s", - ipaddress, conn->port, - Curl_strerror(error, buffer, sizeof(buffer))); + ) +{ + /* close if a connection, or a stream that isn't multiplexed. */ + /* This function will be called both before and after this connection is + associated with a transfer. */ + bool closeit, is_multiplex; + DEBUGASSERT(conn); +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + (void)reason; /* useful for debugging */ #endif - - allow = Curl_timeleft(data, &now, TRUE); - conn->timeoutms_per_addr[i] = conn->tempaddr[i]->ai_next == NULL ? - allow : allow / 2; - ainext(conn, i, TRUE); - status = trynextip(data, conn, sockindex, i); - if((status != CURLE_COULDNT_CONNECT) || - conn->tempsock[other] == CURL_SOCKET_BAD) { - /* the last attempt failed and no other sockets remain open */ - if(!result) - result = status; - } - } - } - } - - /* - * Now that we've checked whether we are connected, check whether we've - * already timed out. - * - * First figure out how long time we have left to connect */ - - allow = Curl_timeleft(data, &now, TRUE); - - if(allow < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection timeout after %ld ms", - Curl_timediff(now, data->progress.t_startsingle)); - return CURLE_OPERATION_TIMEDOUT; + is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET); + closeit = (ctrl == CONNCTRL_CONNECTION) || + ((ctrl == CONNCTRL_STREAM) && !is_multiplex); + if((ctrl == CONNCTRL_STREAM) && is_multiplex) + ; /* stream signal on multiplex conn never affects close state */ + else if((bit)closeit != conn->bits.close) { + conn->bits.close = closeit; /* the only place in the source code that + should assign this bit */ } +} - if(result && - (conn->tempsock[0] == CURL_SOCKET_BAD) && - (conn->tempsock[1] == CURL_SOCKET_BAD)) { - /* no more addresses to try */ - const char *hostname; - CURLcode failreason = result; - - /* if the first address family runs out of addresses to try before the - happy eyeball timeout, go ahead and try the next family now */ - result = trynextip(data, conn, sockindex, 1); - if(!result) - return result; +/** + * job walking the matching addr infos, creating a sub-cfilter with the + * provided method `cf_create` and running setup/connect on it. + */ +struct eyeballer { + const char *name; + const struct Curl_addrinfo *addr; /* List of addresses to try, not owned */ + int ai_family; /* matching address family only */ + cf_ip_connect_create *cf_create; /* for creating cf */ + struct Curl_cfilter *cf; /* current sub-cfilter connecting */ + struct eyeballer *primary; /* eyeballer this one is backup for */ + timediff_t delay_ms; /* delay until start */ + struct curltime started; /* start of current attempt */ + timediff_t timeoutms; /* timeout for current attempt */ + expire_id timeout_id; /* ID for Curl_expire() */ + CURLcode result; + int error; + BIT(has_started); /* attempts have started */ + BIT(is_done); /* out of addresses/time */ + BIT(connected); /* cf has connected */ +}; - result = failreason; -#ifndef CURL_DISABLE_PROXY - if(conn->bits.socksproxy) - hostname = conn->socks_proxy.host.name; - else if(conn->bits.httpproxy) - hostname = conn->http_proxy.host.name; - else -#endif - if(conn->bits.conn_to_host) - hostname = conn->conn_to_host.name; - else - hostname = conn->host.name; +typedef enum { + SCFST_INIT, + SCFST_WAITING, + SCFST_DONE +} cf_connect_state; - failf(data, "Failed to connect to %s port %u after " - "%" CURL_FORMAT_TIMEDIFF_T " ms: %s", - hostname, conn->port, - Curl_timediff(now, data->progress.t_startsingle), - curl_easy_strerror(result)); +struct cf_he_ctx { + int transport; + cf_ip_connect_create *cf_create; + const struct Curl_dns_entry *remotehost; + cf_connect_state state; + struct eyeballer *baller[2]; + struct eyeballer *winner; + struct curltime started; +}; - Curl_quic_disconnect(data, conn, 0); - Curl_quic_disconnect(data, conn, 1); +static CURLcode eyeballer_new(struct eyeballer **pballer, + cf_ip_connect_create *cf_create, + const struct Curl_addrinfo *addr, + int ai_family, + struct eyeballer *primary, + timediff_t delay_ms, + timediff_t timeout_ms, + expire_id timeout_id) +{ + struct eyeballer *baller; -#ifdef WSAETIMEDOUT - if(WSAETIMEDOUT == data->state.os_errno) - result = CURLE_OPERATION_TIMEDOUT; -#elif defined(ETIMEDOUT) - if(ETIMEDOUT == data->state.os_errno) - result = CURLE_OPERATION_TIMEDOUT; -#endif - } - else - result = CURLE_OK; /* still trying */ + *pballer = NULL; + baller = calloc(1, sizeof(*baller) + 1000); + if(!baller) + return CURLE_OUT_OF_MEMORY; - return result; + baller->name = ((ai_family == AF_INET)? "ipv4" : ( +#ifdef ENABLE_IPV6 + (ai_family == AF_INET6)? "ipv6" : +#endif + "ip")); + baller->cf_create = cf_create; + baller->addr = addr; + baller->ai_family = ai_family; + baller->primary = primary; + baller->delay_ms = delay_ms; + baller->timeoutms = addr_next_match(baller->addr, baller->ai_family)? + timeout_ms / 2 : timeout_ms; + baller->timeout_id = timeout_id; + baller->result = CURLE_COULDNT_CONNECT; + + *pballer = baller; + return CURLE_OK; } -static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd) +static void baller_close(struct eyeballer *baller, + struct Curl_easy *data) { -#if defined(TCP_NODELAY) - curl_socklen_t onoff = (curl_socklen_t) 1; - int level = IPPROTO_TCP; -#if !defined(CURL_DISABLE_VERBOSE_STRINGS) - char buffer[STRERROR_LEN]; -#else - (void) data; -#endif - - if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, - sizeof(onoff)) < 0) - infof(data, "Could not set TCP_NODELAY: %s", - Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); -#else - (void)data; - (void)sockfd; -#endif + if(baller && baller->cf) { + Curl_conn_cf_discard_chain(&baller->cf, data); + } } -#ifdef SO_NOSIGPIPE -/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when - sending data to a dead peer (instead of relying on the 4th argument to send - being MSG_NOSIGNAL). Possibly also existing and in use on other BSD - systems? */ -static void nosigpipe(struct Curl_easy *data, - curl_socket_t sockfd) +static void baller_free(struct eyeballer *baller, + struct Curl_easy *data) { - int onoff = 1; - if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, - sizeof(onoff)) < 0) { -#if !defined(CURL_DISABLE_VERBOSE_STRINGS) - char buffer[STRERROR_LEN]; - infof(data, "Could not set SO_NOSIGPIPE: %s", - Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); -#endif + if(baller) { + baller_close(baller, data); + free(baller); } } -#else -#define nosigpipe(x,y) Curl_nop_stmt -#endif - -#ifdef USE_WINSOCK -/* When you run a program that uses the Windows Sockets API, you may - experience slow performance when you copy data to a TCP server. - - https://support.microsoft.com/kb/823764 - Work-around: Make the Socket Send Buffer Size Larger Than the Program Send - Buffer Size - - The problem described in this knowledge-base is applied only to pre-Vista - Windows. Following function trying to detect OS version and skips - SO_SNDBUF adjustment for Windows Vista and above. -*/ -#define DETECT_OS_NONE 0 -#define DETECT_OS_PREVISTA 1 -#define DETECT_OS_VISTA_OR_LATER 2 - -void Curl_sndbufset(curl_socket_t sockfd) +static void baller_next_addr(struct eyeballer *baller) { - int val = CURL_MAX_WRITE_SIZE + 32; - int curval = 0; - int curlen = sizeof(curval); - - static int detectOsState = DETECT_OS_NONE; - - if(detectOsState == DETECT_OS_NONE) { - if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) - detectOsState = DETECT_OS_VISTA_OR_LATER; - else - detectOsState = DETECT_OS_PREVISTA; - } - - if(detectOsState == DETECT_OS_VISTA_OR_LATER) - return; - - if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0) - if(curval > val) - return; - - setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val)); + baller->addr = addr_next_match(baller->addr, baller->ai_family); } -#endif /* - * singleipconnect() + * Initiate a connect attempt walk. * * Note that even on connect fail it returns CURLE_OK, but with 'sock' set to * CURL_SOCKET_BAD. Other errors will however return proper errors. - * - * singleipconnect() connects to the given IP only, and it may return without - * having connected. */ -static CURLcode singleipconnect(struct Curl_easy *data, - struct connectdata *conn, - const struct Curl_addrinfo *ai, - int tempindex) +static void baller_initiate(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct eyeballer *baller) { - struct Curl_sockaddr_ex addr; - int rc = -1; - int error = 0; - bool isconnected = FALSE; - curl_socket_t sockfd; + struct cf_he_ctx *ctx = cf->ctx; + struct Curl_cfilter *cf_prev = baller->cf; + struct Curl_cfilter *wcf; CURLcode result; - char ipaddress[MAX_IPADR_LEN]; - int port; - bool is_tcp; -#ifdef TCP_FASTOPEN_CONNECT - int optval = 1; -#endif - const char *ipmsg; - char buffer[STRERROR_LEN]; - curl_socket_t *sockp = &conn->tempsock[tempindex]; - *sockp = CURL_SOCKET_BAD; - result = Curl_socket(data, ai, &addr, &sockfd); + + /* Don't close a previous cfilter yet to ensure that the next IP's + socket gets a different file descriptor, which can prevent bugs when + the curl_multi_socket_action interface is used with certain select() + replacements such as kqueue. */ + result = baller->cf_create(&baller->cf, data, cf->conn, baller->addr, + ctx->transport); if(result) - return result; + goto out; - /* store remote address and port used in this connection attempt */ - if(!Curl_addr2string(&addr.sa_addr, addr.addrlen, - ipaddress, &port)) { - /* malformed address or bug in inet_ntop, try next address */ - failf(data, "sa_addr inet_ntop() failed with errno %d: %s", - errno, Curl_strerror(errno, buffer, sizeof(buffer))); - Curl_closesocket(data, conn, sockfd); - return CURLE_OK; + /* the new filter might have sub-filters */ + for(wcf = baller->cf; wcf; wcf = wcf->next) { + wcf->conn = cf->conn; + wcf->sockindex = cf->sockindex; } -#ifdef ENABLE_IPV6 - if(addr.family == AF_INET6) - ipmsg = " Trying [%s]:%d..."; - else -#endif - ipmsg = " Trying %s:%d..."; - infof(data, ipmsg, ipaddress, port); -#ifdef ENABLE_IPV6 - is_tcp = (addr.family == AF_INET || addr.family == AF_INET6) && - addr.socktype == SOCK_STREAM; -#else - is_tcp = (addr.family == AF_INET) && addr.socktype == SOCK_STREAM; -#endif - if(is_tcp && data->set.tcp_nodelay) - tcpnodelay(data, sockfd); - - nosigpipe(data, sockfd); + if(addr_next_match(baller->addr, baller->ai_family)) { + Curl_expire(data, baller->timeoutms, baller->timeout_id); + } - Curl_sndbufset(sockfd); +out: + if(result) { + DEBUGF(LOG_CF(data, cf, "%s failed", baller->name)); + baller_close(baller, data); + } + if(cf_prev) + Curl_conn_cf_discard_chain(&cf_prev, data); + baller->result = result; +} - if(is_tcp && data->set.tcp_keepalive) - tcpkeepalive(data, sockfd); +/** + * Start a connection attempt on the current baller address. + * Will return CURLE_OK on the first address where a socket + * could be created and the non-blocking connect started. + * Returns error when all remaining addresses have been tried. + */ +static CURLcode baller_start(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct eyeballer *baller, + timediff_t timeoutms) +{ + baller->error = 0; + baller->connected = FALSE; + baller->has_started = TRUE; + + while(baller->addr) { + baller->started = Curl_now(); + baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ? + timeoutms / 2 : timeoutms; + baller_initiate(cf, data, baller); + if(!baller->result) + break; + baller_next_addr(baller); + } + if(!baller->addr) { + baller->is_done = TRUE; + } + return baller->result; +} - if(data->set.fsockopt) { - /* activate callback for setting socket options */ - Curl_set_in_callback(data, true); - error = data->set.fsockopt(data->set.sockopt_client, - sockfd, - CURLSOCKTYPE_IPCXN); - Curl_set_in_callback(data, false); - if(error == CURL_SOCKOPT_ALREADY_CONNECTED) - isconnected = TRUE; - else if(error) { - Curl_closesocket(data, conn, sockfd); /* close the socket and bail out */ - return CURLE_ABORTED_BY_CALLBACK; - } +/* Used within the multi interface. Try next IP address, returns error if no + more address exists or error */ +static CURLcode baller_start_next(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct eyeballer *baller, + timediff_t timeoutms) +{ + if(cf->sockindex == FIRSTSOCKET) { + baller_next_addr(baller); + baller_start(cf, data, baller, timeoutms); + } + else { + baller->error = 0; + baller->connected = FALSE; + baller->has_started = TRUE; + baller->is_done = TRUE; + baller->result = CURLE_COULDNT_CONNECT; } + return baller->result; +} - /* possibly bind the local end to an IP, interface or port */ - if(addr.family == AF_INET -#ifdef ENABLE_IPV6 - || addr.family == AF_INET6 +static CURLcode baller_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct eyeballer *baller, + struct curltime *now, + bool *connected) +{ + (void)cf; + *connected = baller->connected; + if(!baller->result && !*connected) { + /* evaluate again */ + baller->result = Curl_conn_cf_connect(baller->cf, data, 0, connected); + + if(!baller->result) { + if (*connected) { + baller->connected = TRUE; + baller->is_done = TRUE; + } + else if(Curl_timediff(*now, baller->started) >= baller->timeoutms) { + infof(data, "%s connect timeout after %" CURL_FORMAT_TIMEDIFF_T + "ms, move on!", baller->name, baller->timeoutms); +#if defined(ETIMEDOUT) + baller->error = ETIMEDOUT; #endif - ) { - result = bindlocal(data, conn, sockfd, addr.family, - Curl_ipv6_scope(&addr.sa_addr)); - if(result) { - Curl_closesocket(data, conn, sockfd); /* close socket and bail out */ - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - /* The address family is not supported on this interface. - We can continue trying addresses */ - return CURLE_COULDNT_CONNECT; + baller->result = CURLE_OPERATION_TIMEDOUT; } - return result; } } + return baller->result; +} - /* set socket non-blocking */ - (void)curlx_nonblock(sockfd, TRUE); +/* + * is_connected() checks if the socket has connected. + */ +static CURLcode is_connected(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *connected) +{ + struct cf_he_ctx *ctx = cf->ctx; + struct connectdata *conn = cf->conn; + CURLcode result; + struct curltime now; + size_t i; + int ongoing, not_started; + const char *hostname; - conn->connecttime = Curl_now(); - if(conn->num_addr > 1) { - Curl_expire(data, conn->timeoutms_per_addr[0], EXPIRE_DNS_PER_NAME); - Curl_expire(data, conn->timeoutms_per_addr[1], EXPIRE_DNS_PER_NAME2); - } + /* Check if any of the conn->tempsock we use for establishing connections + * succeeded and, if so, close any ongoing other ones. + * Transfer the successful conn->tempsock to conn->sock[sockindex] + * and set conn->tempsock to CURL_SOCKET_BAD. + * If transport is QUIC, we need to shutdown the ongoing 'other' + * cot ballers in a QUIC appropriate way. */ +evaluate: + *connected = FALSE; /* a very negative world view is best */ + now = Curl_now(); + ongoing = not_started = 0; + for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + struct eyeballer *baller = ctx->baller[i]; - /* Connect TCP and QUIC sockets */ - if(!isconnected && (conn->transport != TRNSPRT_UDP)) { - if(conn->bits.tcp_fastopen) { -#if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */ -# if defined(HAVE_BUILTIN_AVAILABLE) - /* while connectx function is available since macOS 10.11 / iOS 9, - it did not have the interface declared correctly until - Xcode 9 / macOS SDK 10.13 */ - if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) { - sa_endpoints_t endpoints; - endpoints.sae_srcif = 0; - endpoints.sae_srcaddr = NULL; - endpoints.sae_srcaddrlen = 0; - endpoints.sae_dstaddr = &addr.sa_addr; - endpoints.sae_dstaddrlen = addr.addrlen; - - rc = connectx(sockfd, &endpoints, SAE_ASSOCID_ANY, - CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT, - NULL, 0, NULL, NULL); + if(!baller || baller->is_done) + continue; + + if(!baller->has_started) { + ++not_started; + continue; + } + baller->result = baller_connect(cf, data, baller, &now, connected); + DEBUGF(LOG_CF(data, cf, "%s connect -> %d, connected=%d", + baller->name, baller->result, *connected)); + + if(!baller->result) { + if(*connected) { + /* connected, declare the winner */ + ctx->winner = baller; + ctx->baller[i] = NULL; + break; } - else { - rc = connect(sockfd, &addr.sa_addr, addr.addrlen); + else { /* still waiting */ + ++ongoing; } -# else - rc = connect(sockfd, &addr.sa_addr, addr.addrlen); -# endif /* HAVE_BUILTIN_AVAILABLE */ -#elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */ - if(setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, - (void *)&optval, sizeof(optval)) < 0) - infof(data, "Failed to enable TCP Fast Open on fd %d", sockfd); - - rc = connect(sockfd, &addr.sa_addr, addr.addrlen); -#elif defined(MSG_FASTOPEN) /* old Linux */ - if(conn->given->flags & PROTOPT_SSL) - rc = connect(sockfd, &addr.sa_addr, addr.addrlen); - else - rc = 0; /* Do nothing */ -#endif } - else { - rc = connect(sockfd, &addr.sa_addr, addr.addrlen); + else if(!baller->is_done) { + /* The baller failed to connect, start its next attempt */ + if(baller->error) { + data->state.os_errno = baller->error; + SET_SOCKERRNO(baller->error); + } + baller_start_next(cf, data, baller, Curl_timeleft(data, &now, TRUE)); + if(baller->is_done) { + DEBUGF(LOG_CF(data, cf, "%s done", baller->name)); + } + else { + /* next attempt was started */ + DEBUGF(LOG_CF(data, cf, "%s trying next", baller->name)); + ++ongoing; + } } + } - if(-1 == rc) - error = SOCKERRNO; -#ifdef ENABLE_QUIC - else if(conn->transport == TRNSPRT_QUIC) { - /* pass in 'sockfd' separately since it hasn't been put into the - tempsock array at this point */ - result = Curl_quic_connect(data, conn, sockfd, tempindex, - &addr.sa_addr, addr.addrlen); - if(result) - error = SOCKERRNO; + if(ctx->winner) { + *connected = TRUE; + return CURLE_OK; + } + + /* Nothing connected, check the time before we might + * start new ballers or return ok. */ + if((ongoing || not_started) && Curl_timeleft(data, &now, TRUE) < 0) { + failf(data, "Connection timeout after %ld ms", + Curl_timediff(now, data->progress.t_startsingle)); + return CURLE_OPERATION_TIMEDOUT; + } + + /* Check if we have any waiting ballers to start now. */ + if(not_started > 0) { + int added = 0; + + for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + struct eyeballer *baller = ctx->baller[i]; + + if(!baller || baller->has_started) + continue; + /* We start its primary baller has failed to connect or if + * its start delay_ms have expired */ + if((baller->primary && baller->primary->is_done) || + Curl_timediff(now, ctx->started) >= baller->delay_ms) { + baller_start(cf, data, baller, Curl_timeleft(data, &now, TRUE)); + if(baller->is_done) { + DEBUGF(LOG_CF(data, cf, "%s done", baller->name)); + } + else { + DEBUGF(LOG_CF(data, cf, "%s starting (timeout=%ldms)", + baller->name, baller->timeoutms)); + ++ongoing; + ++added; + } + } } -#endif + if(added > 0) + goto evaluate; } - else { - *sockp = sockfd; + + if(ongoing > 0) { + /* We are still trying, return for more waiting */ + *connected = FALSE; return CURLE_OK; } - if(-1 == rc) { - switch(error) { - case EINPROGRESS: - case EWOULDBLOCK: -#if defined(EAGAIN) -#if (EAGAIN) != (EWOULDBLOCK) - /* On some platforms EAGAIN and EWOULDBLOCK are the - * same value, and on others they are different, hence - * the odd #if - */ - case EAGAIN: -#endif -#endif - result = CURLE_OK; + /* all ballers have failed to connect. */ + DEBUGF(LOG_CF(data, cf, "all eyeballers failed")); + result = CURLE_COULDNT_CONNECT; + for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + struct eyeballer *baller = ctx->baller[i]; + DEBUGF(LOG_CF(data, cf, "%s assess started=%d, result=%d", + baller?baller->name:NULL, + baller?baller->has_started:0, + baller?baller->result:0)); + if(baller && baller->has_started && baller->result) { + result = baller->result; break; - - default: - /* unknown error, fallthrough and try another address! */ - infof(data, "Immediate connect fail for %s: %s", - ipaddress, Curl_strerror(error, buffer, sizeof(buffer))); - data->state.os_errno = error; - - /* connect failed */ - Curl_closesocket(data, conn, sockfd); - result = CURLE_COULDNT_CONNECT; } } - if(!result) - *sockp = sockfd; +#ifndef CURL_DISABLE_PROXY + if(conn->bits.socksproxy) + hostname = conn->socks_proxy.host.name; + else if(conn->bits.httpproxy) + hostname = conn->http_proxy.host.name; + else +#endif + if(conn->bits.conn_to_host) + hostname = conn->conn_to_host.name; + else + hostname = conn->host.name; + + failf(data, "Failed to connect to %s port %u after " + "%" CURL_FORMAT_TIMEDIFF_T " ms: %s", + hostname, conn->port, + Curl_timediff(now, data->progress.t_startsingle), + curl_easy_strerror(result)); + +#ifdef WSAETIMEDOUT + if(WSAETIMEDOUT == data->state.os_errno) + result = CURLE_OPERATION_TIMEDOUT; +#elif defined(ETIMEDOUT) + if(ETIMEDOUT == data->state.os_errno) + result = CURLE_OPERATION_TIMEDOUT; +#endif return result; } /* - * TCP connect to the given host with timeout, proxy or remote doesn't matter. - * There might be more than one IP address to try out. Fill in the passed - * pointer with the connected socket. + * Connect to the given host with timeout, proxy or remote doesn't matter. + * There might be more than one IP address to try out. */ - -CURLcode Curl_connecthost(struct Curl_easy *data, - struct connectdata *conn, /* context */ - const struct Curl_dns_entry *remotehost) +static CURLcode start_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost) { + struct cf_he_ctx *ctx = cf->ctx; + struct connectdata *conn = cf->conn; CURLcode result = CURLE_COULDNT_CONNECT; - int i; + int ai_family0, ai_family1; timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + const struct Curl_addrinfo *addr0, *addr1; if(timeout_ms < 0) { /* a precaution, no need to continue if time already is up */ @@ -1310,58 +745,76 @@ CURLcode Curl_connecthost(struct Curl_easy *data, return CURLE_OPERATION_TIMEDOUT; } - conn->num_addr = Curl_num_addresses(remotehost->addr); - conn->tempaddr[0] = conn->tempaddr[1] = remotehost->addr; - conn->tempsock[0] = conn->tempsock[1] = CURL_SOCKET_BAD; - - /* Max time for the next connection attempt */ - conn->timeoutms_per_addr[0] = - conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2; - conn->timeoutms_per_addr[1] = - conn->tempaddr[1]->ai_next == NULL ? timeout_ms : timeout_ms / 2; + ctx->started = Curl_now(); + /* remotehost->addr is the list of addresses from the resolver, each + * with an address family. The list has at least one entry, possibly + * many more. + * We try at most 2 at a time, until we either get a connection or + * run out of addresses to try. Since likelihood of success is tied + * to the address family (e.g. IPV6 might not work at all ), we want + * the 2 connect attempt ballers to try different families, if possible. + * + */ if(conn->ip_version == CURL_IPRESOLVE_WHATEVER) { /* any IP version is allowed */ - conn->tempfamily[0] = conn->tempaddr[0]? - conn->tempaddr[0]->ai_family:0; + ai_family0 = remotehost->addr? + remotehost->addr->ai_family : 0; #ifdef ENABLE_IPV6 - conn->tempfamily[1] = conn->tempfamily[0] == AF_INET6 ? + ai_family1 = ai_family0 == AF_INET6 ? AF_INET : AF_INET6; #else - conn->tempfamily[1] = AF_UNSPEC; + ai_family1 = AF_UNSPEC; #endif } else { /* only one IP version is allowed */ - conn->tempfamily[0] = (conn->ip_version == CURL_IPRESOLVE_V4) ? + ai_family0 = (conn->ip_version == CURL_IPRESOLVE_V4) ? AF_INET : #ifdef ENABLE_IPV6 AF_INET6; #else AF_UNSPEC; #endif - conn->tempfamily[1] = AF_UNSPEC; - - ainext(conn, 0, FALSE); /* find first address of the right type */ + ai_family1 = AF_UNSPEC; } - ainext(conn, 1, FALSE); /* assigns conn->tempaddr[1] accordingly */ - - DEBUGF(infof(data, "family0 == %s, family1 == %s", - conn->tempfamily[0] == AF_INET ? "v4" : "v6", - conn->tempfamily[1] == AF_INET ? "v4" : "v6")); + /* Get the first address in the list that matches the family, + * this might give NULL, if we do not have any matches. */ + addr0 = addr_first_match(remotehost->addr, ai_family0); + addr1 = addr_first_match(remotehost->addr, ai_family1); + if(!addr0 && addr1) { + /* switch around, so a single baller always uses addr0 */ + addr0 = addr1; + ai_family0 = ai_family1; + addr1 = NULL; + } - /* get through the list in family order in case of quick failures */ - for(i = 0; (i < 2) && result; i++) { - while(conn->tempaddr[i]) { - result = singleipconnect(data, conn, conn->tempaddr[i], i); - if(!result) - break; - ainext(conn, i, TRUE); - } + /* We found no address that matches our criteria, we cannot connect */ + if(!addr0) { + return CURLE_COULDNT_CONNECT; } + + memset(ctx->baller, 0, sizeof(ctx->baller)); + result = eyeballer_new(&ctx->baller[0], ctx->cf_create, addr0, ai_family0, + NULL, 0, /* no primary/delay, start now */ + timeout_ms, EXPIRE_DNS_PER_NAME); if(result) return result; + DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)", + ctx->baller[0]->name, ctx->baller[0]->timeoutms)); + if(addr1) { + /* second one gets a delayed start */ + result = eyeballer_new(&ctx->baller[1], ctx->cf_create, addr1, ai_family1, + ctx->baller[0], /* wait on that to fail */ + /* or start this delayed */ + data->set.happy_eyeballs_timeout, + timeout_ms, EXPIRE_DNS_PER_NAME2); + if(result) + return result; + DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)", + ctx->baller[1]->name, ctx->baller[1]->timeoutms)); + } Curl_expire(data, data->set.happy_eyeballs_timeout, EXPIRE_HAPPY_EYEBALLS); @@ -1369,310 +822,335 @@ CURLcode Curl_connecthost(struct Curl_easy *data, return CURLE_OK; } -struct connfind { - long id_tofind; - struct connectdata *found; -}; - -static int conn_is_conn(struct Curl_easy *data, - struct connectdata *conn, void *param) +static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct connfind *f = (struct connfind *)param; - (void)data; - if(conn->connection_id == f->id_tofind) { - f->found = conn; - return 1; + struct cf_he_ctx *ctx = cf->ctx; + size_t i; + + DEBUGASSERT(ctx); + DEBUGASSERT(data); + for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + baller_free(ctx->baller[i], data); + ctx->baller[i] = NULL; } - return 0; + baller_free(ctx->winner, data); + ctx->winner = NULL; } -/* - * Used to extract socket and connectdata struct for the most recent - * transfer on the given Curl_easy. - * - * The returned socket will be CURL_SOCKET_BAD in case of failure! - */ -curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, - struct connectdata **connp) +static int cf_he_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *socks) { - DEBUGASSERT(data); + struct cf_he_ctx *ctx = cf->ctx; + size_t i, s; + int wrc, rc = GETSOCK_BLANK; + curl_socket_t wsocks[MAX_SOCKSPEREASYHANDLE]; - /* this works for an easy handle: - * - that has been used for curl_easy_perform() - * - that is associated with a multi handle, and whose connection - * was detached with CURLOPT_CONNECT_ONLY - */ - if((data->state.lastconnect_id != -1) && (data->multi_easy || data->multi)) { - struct connectdata *c; - struct connfind find; - find.id_tofind = data->state.lastconnect_id; - find.found = NULL; + if(cf->connected) + return cf->next->cft->get_select_socks(cf->next, data, socks); - Curl_conncache_foreach(data, - data->share && (data->share->specifier - & (1<< CURL_LOCK_DATA_CONNECT))? - &data->share->conn_cache: - data->multi_easy? - &data->multi_easy->conn_cache: - &data->multi->conn_cache, &find, conn_is_conn); + for(i = s = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller || !baller->cf) + continue; - if(!find.found) { - data->state.lastconnect_id = -1; - return CURL_SOCKET_BAD; + wrc = Curl_conn_cf_get_select_socks(baller->cf, data, wsocks); + if(wrc) { + /* TODO: we assume we get at most one socket back */ + socks[s] = wsocks[0]; + if(wrc & GETSOCK_WRITESOCK(0)) + rc |= GETSOCK_WRITESOCK(s); + if(wrc & GETSOCK_READSOCK(0)) + rc |= GETSOCK_READSOCK(s); + s++; } - - c = find.found; - if(connp) - /* only store this if the caller cares for it */ - *connp = c; - return c->sock[FIRSTSOCKET]; } - return CURL_SOCKET_BAD; + return rc; } -/* - * Check if a connection seems to be alive. - */ -bool Curl_connalive(struct Curl_easy *data, struct connectdata *conn) +static CURLcode cf_he_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) { - (void)data; - /* First determine if ssl */ - if(Curl_conn_is_ssl(data, FIRSTSOCKET)) { - /* use the SSL context */ - if(!Curl_ssl_check_cxn(data, conn)) - return false; /* FIN received */ + struct cf_he_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; } -/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */ -#ifdef MSG_PEEK - else if(conn->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) - return false; - else { - /* use the socket */ - char buf; - if(recv((RECV_TYPE_ARG1)conn->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf, - (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) { - return false; /* FIN received */ - } + + (void)blocking; /* TODO: do we want to support this? */ + DEBUGASSERT(ctx); + *done = FALSE; + + switch(ctx->state) { + case SCFST_INIT: + DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data)); + DEBUGASSERT(!cf->connected); + result = start_connect(cf, data, ctx->remotehost); + if(result) + return result; + ctx->state = SCFST_WAITING; + /* FALLTHROUGH */ + case SCFST_WAITING: + result = is_connected(cf, data, done); + if(!result && *done) { + DEBUGASSERT(ctx->winner); + DEBUGASSERT(ctx->winner->cf); + DEBUGASSERT(ctx->winner->cf->connected); + /* we have a winner. Install and activate it. + * close/free all others. */ + ctx->state = SCFST_DONE; + cf->connected = TRUE; + cf->next = ctx->winner->cf; + ctx->winner->cf = NULL; + cf_he_ctx_clear(cf, data); + Curl_conn_cf_cntrl(cf->next, data, TRUE, + CF_CTRL_CONN_INFO_UPDATE, 0, NULL); + + if(cf->conn->handler->protocol & PROTO_FAMILY_SSH) + Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */ + Curl_verboseconnect(data, cf->conn); + data->info.numconnects++; /* to track the # of connections made */ + } + break; + case SCFST_DONE: + *done = TRUE; + break; } -#endif - return true; + return result; } -/* - * Close a socket. - * - * 'conn' can be NULL, beware! - */ -int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t sock) +static void cf_he_close(struct Curl_cfilter *cf, + struct Curl_easy *data) { - if(conn && conn->fclosesocket) { - if((sock == conn->sock[SECONDARYSOCKET]) && conn->bits.sock_accepted) - /* if this socket matches the second socket, and that was created with - accept, then we MUST NOT call the callback but clear the accepted - status */ - conn->bits.sock_accepted = FALSE; - else { - int rc; - Curl_multi_closed(data, sock); - Curl_set_in_callback(data, true); - rc = conn->fclosesocket(conn->closesocket_client, sock); - Curl_set_in_callback(data, false); - return rc; - } - } + struct cf_he_ctx *ctx = cf->ctx; - if(conn) - /* tell the multi-socket code about this */ - Curl_multi_closed(data, sock); - - sclose(sock); + DEBUGF(LOG_CF(data, cf, "close")); + cf_he_ctx_clear(cf, data); + cf->connected = FALSE; + ctx->state = SCFST_INIT; - return 0; + if(cf->next) { + cf->next->cft->close(cf->next, data); + Curl_conn_cf_discard_chain(&cf->next, data); + } } -/* - * Create a socket based on info from 'conn' and 'ai'. - * - * 'addr' should be a pointer to the correct struct to get data back, or NULL. - * 'sockfd' must be a pointer to a socket descriptor. - * - * If the open socket callback is set, used that! - * - */ -CURLcode Curl_socket(struct Curl_easy *data, - const struct Curl_addrinfo *ai, - struct Curl_sockaddr_ex *addr, - curl_socket_t *sockfd) +static bool cf_he_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) { - struct connectdata *conn = data->conn; - struct Curl_sockaddr_ex dummy; - - if(!addr) - /* if the caller doesn't want info back, use a local temp copy */ - addr = &dummy; - - /* - * The Curl_sockaddr_ex structure is basically libcurl's external API - * curl_sockaddr structure with enough space available to directly hold - * any protocol-specific address structures. The variable declared here - * will be used to pass / receive data to/from the fopensocket callback - * if this has been set, before that, it is initialized from parameters. - */ + struct cf_he_ctx *ctx = cf->ctx; + size_t i; - addr->family = ai->ai_family; - switch(conn->transport) { - case TRNSPRT_TCP: - addr->socktype = SOCK_STREAM; - addr->protocol = IPPROTO_TCP; - break; - case TRNSPRT_UNIX: - addr->socktype = SOCK_STREAM; - addr->protocol = IPPROTO_IP; - break; - default: /* UDP and QUIC */ - addr->socktype = SOCK_DGRAM; - addr->protocol = IPPROTO_UDP; - break; - } - addr->addrlen = ai->ai_addrlen; - - if(addr->addrlen > sizeof(struct Curl_sockaddr_storage)) - addr->addrlen = sizeof(struct Curl_sockaddr_storage); - memcpy(&addr->sa_addr, ai->ai_addr, addr->addrlen); - - if(data->set.fopensocket) { - /* - * If the opensocket callback is set, all the destination address - * information is passed to the callback. Depending on this information the - * callback may opt to abort the connection, this is indicated returning - * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When - * the callback returns a valid socket the destination address information - * might have been changed and this 'new' address will actually be used - * here to connect. - */ - Curl_set_in_callback(data, true); - *sockfd = data->set.fopensocket(data->set.opensocket_client, - CURLSOCKTYPE_IPCXN, - (struct curl_sockaddr *)addr); - Curl_set_in_callback(data, false); - } - else - /* opensocket callback not set, so simply create the socket now */ - *sockfd = socket(addr->family, addr->socktype, addr->protocol); + if(cf->connected) + return cf->next->cft->has_data_pending(cf->next, data); - if(*sockfd == CURL_SOCKET_BAD) - /* no socket, no connection */ - return CURLE_COULDNT_CONNECT; + for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + struct eyeballer *baller = ctx->baller[i]; + if(!baller || !baller->cf) + continue; + if(baller->cf->cft->has_data_pending(baller->cf, data)) + return TRUE; + } + return FALSE; +} - if(conn->transport == TRNSPRT_QUIC) { - /* QUIC sockets need to be nonblocking */ - (void)curlx_nonblock(*sockfd, TRUE); - switch(addr->family) { -#if defined(__linux__) && defined(IP_MTU_DISCOVER) - case AF_INET: { - int val = IP_PMTUDISC_DO; - (void)setsockopt(*sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &val, - sizeof(val)); - break; +static CURLcode cf_he_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_he_ctx *ctx = cf->ctx; + + if(!cf->connected) { + switch(query) { + case CF_QUERY_CONNECT_REPLY_MS: { + int reply_ms = -1; + size_t i; + + for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) { + struct eyeballer *baller = ctx->baller[i]; + int breply_ms; + + if(baller && baller->cf && + !baller->cf->cft->query(baller->cf, data, query, + &breply_ms, NULL)) { + if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms)) + reply_ms = breply_ms; + } + } + *pres1 = reply_ms; + DEBUGF(LOG_CF(data, cf, "query connect reply: %dms", *pres1)); + return CURLE_OK; } -#endif -#if defined(__linux__) && defined(IPV6_MTU_DISCOVER) - case AF_INET6: { - int val = IPV6_PMTUDISC_DO; - (void)setsockopt(*sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val, - sizeof(val)); + + default: break; } -#endif - } } -#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) - if(conn->scope_id && (addr->family == AF_INET6)) { - struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; - sa6->sin6_scope_id = conn->scope_id; - } -#endif - - return CURLE_OK; + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; } -/* - * Curl_conncontrol() marks streams or connection for closure. - */ -void Curl_conncontrol(struct connectdata *conn, - int ctrl /* see defines in header */ -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - , const char *reason -#endif - ) +static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - /* close if a connection, or a stream that isn't multiplexed. */ - /* This function will be called both before and after this connection is - associated with a transfer. */ - bool closeit; - DEBUGASSERT(conn); -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - (void)reason; /* useful for debugging */ -#endif - closeit = (ctrl == CONNCTRL_CONNECTION) || - ((ctrl == CONNCTRL_STREAM) && !(conn->handler->flags & PROTOPT_STREAM)); - if((ctrl == CONNCTRL_STREAM) && - (conn->handler->flags & PROTOPT_STREAM)) - ; - else if((bit)closeit != conn->bits.close) { - conn->bits.close = closeit; /* the only place in the source code that - should assign this bit */ + struct cf_he_ctx *ctx = cf->ctx; + + DEBUGF(LOG_CF(data, cf, "destroy")); + if(ctx) { + cf_he_ctx_clear(cf, data); } + /* release any resources held in state */ + Curl_safefree(ctx); } -typedef enum { - SCFST_INIT, - SCFST_WAITING, - SCFST_DONE -} cf_connect_state; - -struct socket_cf_ctx { - const struct Curl_dns_entry *remotehost; - cf_connect_state state; +struct Curl_cftype Curl_cft_happy_eyeballs = { + "HAPPY-EYEBALLS", + 0, + CURL_LOG_DEFAULT, + cf_he_destroy, + cf_he_connect, + cf_he_close, + Curl_cf_def_get_host, + cf_he_get_select_socks, + cf_he_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_he_query, }; -static int socket_cf_get_select_socks(struct Curl_cfilter *cf, - struct Curl_easy *data, - curl_socket_t *socks) +CURLcode Curl_cf_happy_eyeballs_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + cf_ip_connect_create *cf_create, + const struct Curl_dns_entry *remotehost, + int transport) { - struct connectdata *conn = cf->conn; - int i, s, rc = GETSOCK_BLANK; + struct cf_he_ctx *ctx = NULL; + CURLcode result; (void)data; - if(cf->connected) { - return rc; + (void)conn; + *pcf = NULL; + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->transport = transport; + ctx->cf_create = cf_create; + ctx->remotehost = remotehost; + + result = Curl_cf_create(pcf, &Curl_cft_happy_eyeballs, ctx); + +out: + if(result) { + Curl_safefree(*pcf); + Curl_safefree(ctx); } + return result; +} + +struct transport_provider { + int transport; + cf_ip_connect_create *cf_create; +}; - for(i = s = 0; i<2; i++) { - if(conn->tempsock[i] != CURL_SOCKET_BAD) { - socks[s] = conn->tempsock[i]; - rc |= GETSOCK_WRITESOCK(s); +static +#ifndef DEBUGBUILD +const +#endif +struct transport_provider transport_providers[] = { + { TRNSPRT_TCP, Curl_cf_tcp_create }, #ifdef ENABLE_QUIC - if(conn->transport == TRNSPRT_QUIC) - /* when connecting QUIC, we want to read the socket too */ - rc |= GETSOCK_READSOCK(s); + { TRNSPRT_QUIC, Curl_cf_quic_create }, #endif - s++; + { TRNSPRT_UDP, Curl_cf_udp_create }, + { TRNSPRT_UNIX, Curl_cf_unix_create }, +}; + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +static cf_ip_connect_create *get_cf_create(int transport) +{ + size_t i; + for(i = 0; i < ARRAYSIZE(transport_providers); ++i) { + if(transport == transport_providers[i].transport) + return transport_providers[i].cf_create; + } + return NULL; +} + +#ifdef DEBUGBUILD +void Curl_debug_set_transport_provider(int transport, + cf_ip_connect_create *cf_create) +{ + size_t i; + for(i = 0; i < ARRAYSIZE(transport_providers); ++i) { + if(transport == transport_providers[i].transport) { + transport_providers[i].cf_create = cf_create; + return; } } +} +#endif /* DEBUGBUILD */ - return rc; +static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport) +{ + cf_ip_connect_create *cf_create; + struct Curl_cfilter *cf; + CURLcode result; + + /* Need to be first */ + DEBUGASSERT(cf_at); + cf_create = get_cf_create(transport); + if(!cf_create) { + DEBUGF(LOG_CF(data, cf_at, "unsupported transport type %d", transport)); + return CURLE_UNSUPPORTED_PROTOCOL; + } + result = Curl_cf_happy_eyeballs_create(&cf, data, cf_at->conn, + cf_create, remotehost, + transport); + if(result) + return result; + + Curl_conn_cf_insert_after(cf_at, cf); + return CURLE_OK; } -static CURLcode socket_cf_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool blocking, bool *done) +typedef enum { + CF_SETUP_INIT, + CF_SETUP_CNNCT_EYEBALLS, + CF_SETUP_CNNCT_SOCKS, + CF_SETUP_CNNCT_HTTP_PROXY, + CF_SETUP_CNNCT_HAPROXY, + CF_SETUP_CNNCT_SSL, + CF_SETUP_DONE +} cf_setup_state; + +struct cf_setup_ctx { + cf_setup_state state; + const struct Curl_dns_entry *remotehost; + int ssl_mode; + int transport; +}; + +static CURLcode cf_setup_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) { - struct connectdata *conn = cf->conn; - int sockindex = cf->sockindex; - struct socket_cf_ctx *ctx = cf->ctx; + struct cf_setup_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; if(cf->connected) { @@ -1680,253 +1158,241 @@ static CURLcode socket_cf_connect(struct Curl_cfilter *cf, return CURLE_OK; } - (void)blocking; - DEBUGASSERT(ctx); - *done = FALSE; - switch(ctx->state) { - case SCFST_INIT: - DEBUGASSERT(CURL_SOCKET_BAD == conn->sock[sockindex]); - DEBUGASSERT(!cf->connected); - result = Curl_connecthost(data, conn, ctx->remotehost); - if(!result) - ctx->state = SCFST_WAITING; - break; - case SCFST_WAITING: - result = is_connected(data, conn, sockindex, done); - if(!result && *done) { - Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */ - if(Curl_conn_is_ssl(data, FIRSTSOCKET) || - (conn->handler->protocol & PROTO_FAMILY_SSH)) - Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */ - post_connect(data, conn, sockindex); - ctx->state = SCFST_DONE; - cf->connected = TRUE; - } - break; - case SCFST_DONE: - *done = TRUE; - break; + /* connect current sub-chain */ +connect_sub_chain: + if(cf->next && !cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; } - return result; -} -static CURLcode socket_cf_setup(struct Curl_cfilter *cf, - struct Curl_easy *data, - const struct Curl_dns_entry *remotehost) -{ - struct socket_cf_ctx *ctx = cf->ctx; + if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) { + result = cf_he_insert_after(cf, data, ctx->remotehost, ctx->transport); + if(result) + return result; + ctx->state = CF_SETUP_CNNCT_EYEBALLS; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + } - (void)data; - DEBUGASSERT(ctx); - if(ctx->remotehost != remotehost) { - if(ctx->remotehost) { - /* switching dns entry? TODO: reset? */ - } - ctx->remotehost = remotehost; + /* sub-chain connected, do we need to add more? */ +#ifndef CURL_DISABLE_PROXY + if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) { + result = Curl_cf_socks_proxy_insert_after(cf, data); + if(result) + return result; + ctx->state = CF_SETUP_CNNCT_SOCKS; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; } - DEBUGF(infof(data, CFMSG(cf, "setup(remotehost=%s)"), - cf->conn->hostname_resolve)); - return CURLE_OK; -} -static void socket_cf_close(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - int sockindex = cf->sockindex; - struct socket_cf_ctx *ctx = cf->ctx; - - DEBUGASSERT(ctx); - /* close possibly still open sockets */ - if(CURL_SOCKET_BAD != cf->conn->sock[sockindex]) { - Curl_closesocket(data, cf->conn, cf->conn->sock[sockindex]); - cf->conn->sock[sockindex] = CURL_SOCKET_BAD; + if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) { +#ifdef USE_SSL + if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS + && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { + result = Curl_cf_ssl_proxy_insert_after(cf, data); + if(result) + return result; + } +#endif /* USE_SSL */ + +#if !defined(CURL_DISABLE_HTTP) + if(cf->conn->bits.tunnel_proxy) { + result = Curl_cf_http_proxy_insert_after(cf, data); + if(result) + return result; + } +#endif /* !CURL_DISABLE_HTTP */ + ctx->state = CF_SETUP_CNNCT_HTTP_PROXY; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; } - if(CURL_SOCKET_BAD != cf->conn->tempsock[sockindex]) { - Curl_closesocket(data, cf->conn, cf->conn->tempsock[sockindex]); - cf->conn->tempsock[sockindex] = CURL_SOCKET_BAD; +#endif /* !CURL_DISABLE_PROXY */ + + if(ctx->state < CF_SETUP_CNNCT_HAPROXY) { +#if !defined(CURL_DISABLE_PROXY) + if(data->set.haproxyprotocol) { + if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) { + failf(data, "haproxy protocol not support with SSL " + "encryption in place (QUIC?)"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + result = Curl_cf_haproxy_insert_after(cf, data); + if(result) + return result; + } +#endif /* !CURL_DISABLE_PROXY */ + ctx->state = CF_SETUP_CNNCT_HAPROXY; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; } - cf->connected = FALSE; - ctx->state = SCFST_INIT; -} - -static void socket_cf_get_host(struct Curl_cfilter *cf, - struct Curl_easy *data, - const char **phost, - const char **pdisplay_host, - int *pport) -{ - (void)data; - *phost = cf->conn->host.name; - *pdisplay_host = cf->conn->host.dispname; - *pport = cf->conn->port; -} -static bool socket_cf_data_pending(struct Curl_cfilter *cf, - const struct Curl_easy *data) -{ - int readable; - (void)data; - DEBUGASSERT(cf); + if(ctx->state < CF_SETUP_CNNCT_SSL) { +#ifdef USE_SSL + if((ctx->ssl_mode == CURL_CF_SSL_ENABLE + || (ctx->ssl_mode != CURL_CF_SSL_DISABLE + && cf->conn->handler->flags & PROTOPT_SSL)) /* we want SSL */ + && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */ + result = Curl_cf_ssl_insert_after(cf, data); + if(result) + return result; + } +#endif /* USE_SSL */ + ctx->state = CF_SETUP_CNNCT_SSL; + if(!cf->next || !cf->next->connected) + goto connect_sub_chain; + } - readable = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0); - return (readable > 0 && (readable & CURL_CSELECT_IN)); + ctx->state = CF_SETUP_DONE; + cf->connected = TRUE; + *done = TRUE; + return CURLE_OK; } -static ssize_t socket_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, - const void *buf, size_t len, CURLcode *err) +static void cf_setup_close(struct Curl_cfilter *cf, + struct Curl_easy *data) { - ssize_t nwritten; - nwritten = Curl_send_plain(data, cf->sockindex, buf, len, err); - return nwritten; -} + struct cf_setup_ctx *ctx = cf->ctx; -static ssize_t socket_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, - char *buf, size_t len, CURLcode *err) -{ - ssize_t nread; - nread = Curl_recv_plain(data, cf->sockindex, buf, len, err); - return nread; + DEBUGF(LOG_CF(data, cf, "close")); + cf->connected = FALSE; + ctx->state = CF_SETUP_INIT; + + if(cf->next) { + cf->next->cft->close(cf->next, data); + Curl_conn_cf_discard_chain(&cf->next, data); + } } -static void socket_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct socket_cf_ctx *state = cf->ctx; + struct cf_setup_ctx *ctx = cf->ctx; (void)data; - if(cf->connected) { - socket_cf_close(cf, data); - } - /* release any resources held in state */ - Curl_safefree(state); + DEBUGF(LOG_CF(data, cf, "destroy")); + Curl_safefree(ctx); } -static const struct Curl_cftype cft_socket = { - "SOCKET", - CF_TYPE_IP_CONNECT, - socket_cf_destroy, - socket_cf_setup, - socket_cf_connect, - socket_cf_close, - socket_cf_get_host, - socket_cf_get_select_socks, - socket_cf_data_pending, - socket_cf_send, - socket_cf_recv, - Curl_cf_def_attach_data, - Curl_cf_def_detach_data, + +struct Curl_cftype Curl_cft_setup = { + "SETUP", + 0, + CURL_LOG_DEFAULT, + cf_setup_destroy, + cf_setup_connect, + cf_setup_close, + Curl_cf_def_get_host, + Curl_cf_def_get_select_socks, + Curl_cf_def_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, }; -CURLcode Curl_conn_socket_set(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) +static CURLcode cf_setup_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode) { - CURLcode result; struct Curl_cfilter *cf = NULL; - struct socket_cf_ctx *scf_ctx = NULL; + struct cf_setup_ctx *ctx; + CURLcode result = CURLE_OK; - /* Need to be first */ - DEBUGASSERT(conn); - DEBUGASSERT(!conn->cfilter[sockindex]); - scf_ctx = calloc(sizeof(*scf_ctx), 1); - if(!scf_ctx) { + (void)data; + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - result = Curl_cf_create(&cf, &cft_socket, scf_ctx); + ctx->state = CF_SETUP_INIT; + ctx->remotehost = remotehost; + ctx->ssl_mode = ssl_mode; + ctx->transport = transport; + + result = Curl_cf_create(&cf, &Curl_cft_setup, ctx); if(result) goto out; - Curl_conn_cf_add(data, conn, sockindex, cf); + ctx = NULL; out: - if(result) { - Curl_safefree(cf); - Curl_safefree(scf_ctx); - } + *pcf = result? NULL : cf; + free(ctx); return result; } -static CURLcode socket_accept_cf_connect(struct Curl_cfilter *cf, - struct Curl_easy *data, - bool blocking, bool *done) +CURLcode Curl_cf_setup_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode) { - /* we start accepted, if we ever close, we cannot go on */ - (void)data; - (void)blocking; - if(cf->connected) { - *done = TRUE; - return CURLE_OK; - } - return CURLE_FAILED_INIT; + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); +out: + return result; } -static CURLcode socket_accept_cf_setup(struct Curl_cfilter *cf, - struct Curl_easy *data, - const struct Curl_dns_entry *remotehost) +CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode) { - /* we start accepted, if we ever close, we cannot go on */ - (void)data; - (void)remotehost; - if(cf->connected) { - return CURLE_OK; - } - return CURLE_FAILED_INIT; -} + struct Curl_cfilter *cf; + CURLcode result; -static const struct Curl_cftype cft_socket_accept = { - "SOCKET-ACCEPT", - CF_TYPE_IP_CONNECT, - socket_cf_destroy, - socket_accept_cf_setup, - socket_accept_cf_connect, - socket_cf_close, - socket_cf_get_host, /* TODO: not accurate */ - Curl_cf_def_get_select_socks, - socket_cf_data_pending, - socket_cf_send, - socket_cf_recv, - Curl_cf_def_attach_data, - Curl_cf_def_detach_data, -}; + DEBUGASSERT(data); + result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); +out: + return result; +} -CURLcode Curl_conn_socket_accepted_set(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, curl_socket_t *s) +CURLcode Curl_conn_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int ssl_mode) { - CURLcode result; - struct Curl_cfilter *cf = NULL; - struct socket_cf_ctx *scf_ctx = NULL; + CURLcode result = CURLE_OK; - cf = conn->cfilter[sockindex]; - if(cf && cf->cft == &cft_socket_accept) { - /* already an accept filter installed, just replace the socket */ - scf_ctx = cf->ctx; - result = CURLE_OK; - } - else { - /* replace any existing */ - Curl_conn_cf_discard_all(data, conn, sockindex); - scf_ctx = calloc(sizeof(*scf_ctx), 1); - if(!scf_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - result = Curl_cf_create(&cf, &cft_socket_accept, scf_ctx); + DEBUGASSERT(data); + DEBUGASSERT(conn->handler); + +#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + if(!conn->cfilter[sockindex] && + conn->handler->protocol == CURLPROTO_HTTPS && + (ssl_mode == CURL_CF_SSL_ENABLE || ssl_mode != CURL_CF_SSL_DISABLE)) { + + result = Curl_cf_https_setup(data, conn, sockindex, remotehost); if(result) goto out; - Curl_conn_cf_add(data, conn, sockindex, cf); } +#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ - /* close any existing socket and replace */ - Curl_closesocket(data, conn, conn->sock[sockindex]); - conn->sock[sockindex] = *s; - conn->bits.sock_accepted = TRUE; - cf->connected = TRUE; - scf_ctx->state = SCFST_DONE; + /* Still no cfilter set, apply default. */ + if(!conn->cfilter[sockindex]) { + result = Curl_cf_setup_add(data, conn, sockindex, remotehost, + conn->transport, ssl_mode); + if(result) + goto out; + } + DEBUGASSERT(conn->cfilter[sockindex]); out: - if(result) { - Curl_safefree(cf); - Curl_safefree(scf_ctx); - } return result; } + diff --git a/Utilities/cmcurl/lib/connect.h b/Utilities/cmcurl/lib/connect.h index 1e90a8561e..e4fa10c4cb 100644 --- a/Utilities/cmcurl/lib/connect.h +++ b/Utilities/cmcurl/lib/connect.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -29,9 +29,7 @@ #include "sockaddr.h" #include "timeval.h" -CURLcode Curl_connecthost(struct Curl_easy *data, - struct connectdata *conn, - const struct Curl_dns_entry *host); +struct Curl_dns_entry; /* generic function that returns how much time there's left to run, according to the timeouts set */ @@ -53,67 +51,8 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, char *addr, int *port); -/* - * Check if a connection seems to be alive. - */ -bool Curl_connalive(struct Curl_easy *data, struct connectdata *conn); - -#ifdef USE_WINSOCK -/* When you run a program that uses the Windows Sockets API, you may - experience slow performance when you copy data to a TCP server. - - https://support.microsoft.com/kb/823764 - - Work-around: Make the Socket Send Buffer Size Larger Than the Program Send - Buffer Size - -*/ -void Curl_sndbufset(curl_socket_t sockfd); -#else -#define Curl_sndbufset(y) Curl_nop_stmt -#endif - -void Curl_updateconninfo(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t sockfd); -void Curl_conninfo_remote(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t sockfd); -void Curl_conninfo_local(struct Curl_easy *data, curl_socket_t sockfd, - char *local_ip, int *local_port); void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn, char *local_ip, int local_port); -int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t sock); - -/* - * The Curl_sockaddr_ex structure is basically libcurl's external API - * curl_sockaddr structure with enough space available to directly hold any - * protocol-specific address structures. The variable declared here will be - * used to pass / receive data to/from the fopensocket callback if this has - * been set, before that, it is initialized from parameters. - */ -struct Curl_sockaddr_ex { - int family; - int socktype; - int protocol; - unsigned int addrlen; - union { - struct sockaddr addr; - struct Curl_sockaddr_storage buff; - } _sa_ex_u; -}; -#define sa_addr _sa_ex_u.addr - -/* - * Create a socket based on info from 'conn' and 'ai'. - * - * Fill in 'addr' and 'sockfd' accordingly if OK is returned. If the open - * socket callback is set, used that! - * - */ -CURLcode Curl_socket(struct Curl_easy *data, - const struct Curl_addrinfo *ai, - struct Curl_sockaddr_ex *addr, - curl_socket_t *sockfd); /* * Curl_conncontrol() marks the end of a connection/stream. The 'closeit' @@ -148,13 +87,71 @@ void Curl_conncontrol(struct connectdata *conn, #define connkeep(x,y) Curl_conncontrol(x, CONNCTRL_KEEP) #endif -CURLcode Curl_conn_socket_set(struct Curl_easy *data, +/** + * Create a cfilter for making an "ip" connection to the + * given address, using parameters from `conn`. The "ip" connection + * can be a TCP socket, a UDP socket or even a QUIC connection. + * + * It MUST use only the supplied `ai` for its connection attempt. + * + * Such a filter may be used in "happy eyeball" scenarios, and its + * `connect` implementation needs to support non-blocking. Once connected, + * it MAY be installed in the connection filter chain to serve transfers. + */ +typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +/** + * Create a happy eyeball connection filter that uses the, once resolved, + * address information to connect on ip families based on connection + * configuration. + * @param pcf output, the created cfilter + * @param data easy handle used in creation + * @param conn connection the filter is created for + * @param cf_create method to create the sub-filters performing the + * actual connects. + */ +CURLcode +Curl_cf_happy_eyeballs_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, struct connectdata *conn, - int sockindex); - -CURLcode Curl_conn_socket_accepted_set(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - curl_socket_t *s); + cf_ip_connect_create *cf_create, + const struct Curl_dns_entry *remotehost, + int transport); + +CURLcode Curl_cf_setup_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode); + +CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost, + int transport, + int ssl_mode); + +/** + * Setup the cfilters at `sockindex` in connection `conn`. + * If no filter chain is installed yet, inspects the configuration + * in `data` and `conn? to install a suitable filter chain. + */ +CURLcode Curl_conn_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int ssl_mode); + +extern struct Curl_cftype Curl_cft_happy_eyeballs; +extern struct Curl_cftype Curl_cft_setup; + +#ifdef DEBUGBUILD +void Curl_debug_set_transport_provider(int transport, + cf_ip_connect_create *cf_create); +#endif #endif /* HEADER_CURL_CONNECT_H */ diff --git a/Utilities/cmcurl/lib/content_encoding.c b/Utilities/cmcurl/lib/content_encoding.c index 9e345b197e..a29d2041b6 100644 --- a/Utilities/cmcurl/lib/content_encoding.c +++ b/Utilities/cmcurl/lib/content_encoding.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -977,7 +977,8 @@ static const struct content_encoding error_encoding = { static struct contenc_writer * new_unencoding_writer(struct Curl_easy *data, const struct content_encoding *handler, - struct contenc_writer *downstream) + struct contenc_writer *downstream, + int order) { struct contenc_writer *writer; @@ -987,6 +988,7 @@ new_unencoding_writer(struct Curl_easy *data, if(writer) { writer->handler = handler; writer->downstream = downstream; + writer->order = order; if(handler->init_writer(data, writer)) { free(writer); writer = NULL; @@ -1042,10 +1044,10 @@ static const struct content_encoding *find_encoding(const char *name, /* Set-up the unencoding stack from the Content-Encoding header value. * See RFC 7231 section 3.1.2.2. */ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, - const char *enclist, int maybechunked) + const char *enclist, int is_transfer) { struct SingleRequest *k = &data->req; - int counter = 0; + unsigned int order = is_transfer? 2: 1; do { const char *name; @@ -1062,7 +1064,7 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, namelen = enclist - name + 1; /* Special case: chunked encoding is handled at the reader level. */ - if(maybechunked && namelen == 7 && strncasecompare(name, "chunked", 7)) { + if(is_transfer && namelen == 7 && strncasecompare(name, "chunked", 7)) { k->chunk = TRUE; /* chunks coming our way. */ Curl_httpchunk_init(data); /* init our chunky engine. */ } @@ -1071,7 +1073,8 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, struct contenc_writer *writer; if(!k->writer_stack) { - k->writer_stack = new_unencoding_writer(data, &client_encoding, NULL); + k->writer_stack = new_unencoding_writer(data, &client_encoding, + NULL, 0); if(!k->writer_stack) return CURLE_OUT_OF_MEMORY; @@ -1080,16 +1083,29 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, if(!encoding) encoding = &error_encoding; /* Defer error at stack use. */ - if(++counter >= MAX_ENCODE_STACK) { - failf(data, "Reject response due to %u content encodings", - counter); + if(k->writer_stack_depth++ >= MAX_ENCODE_STACK) { + failf(data, "Reject response due to more than %u content encodings", + MAX_ENCODE_STACK); return CURLE_BAD_CONTENT_ENCODING; } /* Stack the unencoding stage. */ - writer = new_unencoding_writer(data, encoding, k->writer_stack); - if(!writer) - return CURLE_OUT_OF_MEMORY; - k->writer_stack = writer; + if(order >= k->writer_stack->order) { + writer = new_unencoding_writer(data, encoding, + k->writer_stack, order); + if(!writer) + return CURLE_OUT_OF_MEMORY; + k->writer_stack = writer; + } + else { + struct contenc_writer *w = k->writer_stack; + while(w->downstream && order < w->downstream->order) + w = w->downstream; + writer = new_unencoding_writer(data, encoding, + w->downstream, order); + if(!writer) + return CURLE_OUT_OF_MEMORY; + w->downstream = writer; + } } } while(*enclist); @@ -1099,11 +1115,11 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, #else /* Stubs for builds without HTTP. */ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, - const char *enclist, int maybechunked) + const char *enclist, int is_transfer) { (void) data; (void) enclist; - (void) maybechunked; + (void) is_transfer; return CURLE_NOT_BUILT_IN; } diff --git a/Utilities/cmcurl/lib/content_encoding.h b/Utilities/cmcurl/lib/content_encoding.h index 3c278cf727..56e7f97f70 100644 --- a/Utilities/cmcurl/lib/content_encoding.h +++ b/Utilities/cmcurl/lib/content_encoding.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -28,6 +28,7 @@ struct contenc_writer { const struct content_encoding *handler; /* Encoding handler. */ struct contenc_writer *downstream; /* Downstream writer. */ + unsigned int order; /* Ordering within writer stack. */ }; /* Content encoding writer. */ @@ -46,7 +47,7 @@ struct content_encoding { CURLcode Curl_build_unencoding_stack(struct Curl_easy *data, - const char *enclist, int maybechunked); + const char *enclist, int is_transfer); CURLcode Curl_unencode_write(struct Curl_easy *data, struct contenc_writer *writer, const char *buf, size_t nbytes); diff --git a/Utilities/cmcurl/lib/cookie.c b/Utilities/cmcurl/lib/cookie.c index bccf2e8882..c457b2d95f 100644 --- a/Utilities/cmcurl/lib/cookie.c +++ b/Utilities/cmcurl/lib/cookie.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -329,7 +329,7 @@ static char *sanitize_cookie_path(const char *cookie_path) */ void Curl_cookie_loadfiles(struct Curl_easy *data) { - struct curl_slist *list = data->state.cookielist; + struct curl_slist *list = data->set.cookielist; if(list) { Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); while(list) { @@ -347,8 +347,6 @@ void Curl_cookie_loadfiles(struct Curl_easy *data) data->cookies = newcookies; list = list->next; } - curl_slist_free_all(data->state.cookielist); /* clean up list */ - data->state.cookielist = NULL; /* don't do this again! */ Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } } @@ -1301,7 +1299,7 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, */ remove_expired(c); - if(fromfile && fp) + if(fromfile) fclose(fp); } @@ -1800,12 +1798,10 @@ void Curl_flush_cookies(struct Curl_easy *data, bool cleanup) CURLcode res; if(data->set.str[STRING_COOKIEJAR]) { - if(data->state.cookielist) { - /* If there is a list of cookie files to read, do it first so that - we have all the told files read before we write the new jar. - Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */ - Curl_cookie_loadfiles(data); - } + /* If there is a list of cookie files to read, do it first so that + we have all the told files read before we write the new jar. + Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */ + Curl_cookie_loadfiles(data); Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); @@ -1816,12 +1812,6 @@ void Curl_flush_cookies(struct Curl_easy *data, bool cleanup) data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res)); } else { - if(cleanup && data->state.cookielist) { - /* since nothing is written, we can just free the list of cookie file - names */ - curl_slist_free_all(data->state.cookielist); /* clean up list */ - data->state.cookielist = NULL; - } Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); } diff --git a/Utilities/cmcurl/lib/cookie.h b/Utilities/cmcurl/lib/cookie.h index abc0a2e8a0..39bb08bc49 100644 --- a/Utilities/cmcurl/lib/cookie.h +++ b/Utilities/cmcurl/lib/cookie.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_addrinfo.c b/Utilities/cmcurl/lib/curl_addrinfo.c index bcea883564..35a06350d8 100644 --- a/Utilities/cmcurl/lib/curl_addrinfo.c +++ b/Utilities/cmcurl/lib/curl_addrinfo.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_addrinfo.h b/Utilities/cmcurl/lib/curl_addrinfo.h index b778121a7c..c757c49c5c 100644 --- a/Utilities/cmcurl/lib/curl_addrinfo.h +++ b/Utilities/cmcurl/lib/curl_addrinfo.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_base64.h b/Utilities/cmcurl/lib/curl_base64.h index 85368a163c..806d4431cf 100644 --- a/Utilities/cmcurl/lib/curl_base64.h +++ b/Utilities/cmcurl/lib/curl_base64.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_config.h.cmake b/Utilities/cmcurl/lib/curl_config.h.cmake index 9cde948f1c..6e2458dc8b 100644 --- a/Utilities/cmcurl/lib/curl_config.h.cmake +++ b/Utilities/cmcurl/lib/curl_config.h.cmake @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_ctype.h b/Utilities/cmcurl/lib/curl_ctype.h index dc6b8cab86..1d1d60c28d 100644 --- a/Utilities/cmcurl/lib/curl_ctype.h +++ b/Utilities/cmcurl/lib/curl_ctype.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -27,7 +27,7 @@ #define ISLOWHEXALHA(x) (((x) >= 'a') && ((x) <= 'f')) #define ISUPHEXALHA(x) (((x) >= 'A') && ((x) <= 'F')) -#define ISLOWCNTRL(x) ((x) >= 0 && ((x) <= 0x1f)) +#define ISLOWCNTRL(x) ((unsigned char)(x) <= 0x1f) #define IS7F(x) ((x) == 0x7f) #define ISLOWPRINT(x) (((x) >= 9) && ((x) <= 0x0d)) diff --git a/Utilities/cmcurl/lib/curl_des.c b/Utilities/cmcurl/lib/curl_des.c index a2bf648c29..5c623b35bc 100644 --- a/Utilities/cmcurl/lib/curl_des.c +++ b/Utilities/cmcurl/lib/curl_des.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2015 - 2022, Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/curl_des.h b/Utilities/cmcurl/lib/curl_des.h index c1c167471a..6ec450accc 100644 --- a/Utilities/cmcurl/lib/curl_des.h +++ b/Utilities/cmcurl/lib/curl_des.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2015 - 2022, Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/curl_endian.c b/Utilities/cmcurl/lib/curl_endian.c index 3cc7734f31..11c662a4c7 100644 --- a/Utilities/cmcurl/lib/curl_endian.c +++ b/Utilities/cmcurl/lib/curl_endian.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_endian.h b/Utilities/cmcurl/lib/curl_endian.h index 08d52e7c52..fa283214b1 100644 --- a/Utilities/cmcurl/lib/curl_endian.h +++ b/Utilities/cmcurl/lib/curl_endian.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_fnmatch.c b/Utilities/cmcurl/lib/curl_fnmatch.c index b8a85a96ec..5f9ca4f1be 100644 --- a/Utilities/cmcurl/lib/curl_fnmatch.c +++ b/Utilities/cmcurl/lib/curl_fnmatch.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_fnmatch.h b/Utilities/cmcurl/lib/curl_fnmatch.h index 8324be533d..595646ff0d 100644 --- a/Utilities/cmcurl/lib/curl_fnmatch.h +++ b/Utilities/cmcurl/lib/curl_fnmatch.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_get_line.c b/Utilities/cmcurl/lib/curl_get_line.c index 0d8c285a1d..686abe7511 100644 --- a/Utilities/cmcurl/lib/curl_get_line.c +++ b/Utilities/cmcurl/lib/curl_get_line.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_get_line.h b/Utilities/cmcurl/lib/curl_get_line.h index b2a534d001..0ff32c5c2c 100644 --- a/Utilities/cmcurl/lib/curl_get_line.h +++ b/Utilities/cmcurl/lib/curl_get_line.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_gethostname.c b/Utilities/cmcurl/lib/curl_gethostname.c index 4747e938db..706b2e6892 100644 --- a/Utilities/cmcurl/lib/curl_gethostname.c +++ b/Utilities/cmcurl/lib/curl_gethostname.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_gethostname.h b/Utilities/cmcurl/lib/curl_gethostname.h index b7360969ac..9281d9c242 100644 --- a/Utilities/cmcurl/lib/curl_gethostname.h +++ b/Utilities/cmcurl/lib/curl_gethostname.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_gssapi.c b/Utilities/cmcurl/lib/curl_gssapi.c index 01ab48ec3e..c4c4f88ef9 100644 --- a/Utilities/cmcurl/lib/curl_gssapi.c +++ b/Utilities/cmcurl/lib/curl_gssapi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_gssapi.h b/Utilities/cmcurl/lib/curl_gssapi.h index b4ed482aa2..7b9a534ea2 100644 --- a/Utilities/cmcurl/lib/curl_gssapi.h +++ b/Utilities/cmcurl/lib/curl_gssapi.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_hmac.h b/Utilities/cmcurl/lib/curl_hmac.h index 36c0bd62e5..11625c0cb4 100644 --- a/Utilities/cmcurl/lib/curl_hmac.h +++ b/Utilities/cmcurl/lib/curl_hmac.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_krb5.h b/Utilities/cmcurl/lib/curl_krb5.h index ccd6f10e3d..ccf6b96a87 100644 --- a/Utilities/cmcurl/lib/curl_krb5.h +++ b/Utilities/cmcurl/lib/curl_krb5.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_ldap.h b/Utilities/cmcurl/lib/curl_ldap.h index ba3ede4260..8a1d807ed9 100644 --- a/Utilities/cmcurl/lib/curl_ldap.h +++ b/Utilities/cmcurl/lib/curl_ldap.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_log.c b/Utilities/cmcurl/lib/curl_log.c new file mode 100644 index 0000000000..b5ce1c7e09 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_log.c @@ -0,0 +1,223 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 <curl/curl.h> + +#include "curl_log.h" +#include "urldata.h" +#include "easyif.h" +#include "cfilters.h" +#include "timeval.h" +#include "multiif.h" +#include "strcase.h" + +#include "cf-socket.h" +#include "connect.h" +#include "http2.h" +#include "http_proxy.h" +#include "cf-http.h" +#include "socks.h" +#include "strtok.h" +#include "vtls/vtls.h" +#include "vquic/vquic.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +void Curl_debug(struct Curl_easy *data, curl_infotype type, + char *ptr, size_t size) +{ + if(data->set.verbose) { + static const char s_infotype[CURLINFO_END][3] = { + "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; + if(data->set.fdebug) { + bool inCallback = Curl_is_in_callback(data); + Curl_set_in_callback(data, true); + (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata); + Curl_set_in_callback(data, inCallback); + } + else { + switch(type) { + case CURLINFO_TEXT: + case CURLINFO_HEADER_OUT: + case CURLINFO_HEADER_IN: + fwrite(s_infotype[type], 2, 1, data->set.err); + fwrite(ptr, size, 1, data->set.err); + break; + default: /* nada */ + break; + } + } + } +} + + +/* Curl_failf() is for messages stating why we failed. + * The message SHALL NOT include any LF or CR. + */ +void Curl_failf(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(data->set.verbose || data->set.errorbuffer) { + va_list ap; + int len; + char error[CURL_ERROR_SIZE + 2]; + va_start(ap, fmt); + len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap); + + if(data->set.errorbuffer && !data->state.errorbuf) { + strcpy(data->set.errorbuffer, error); + data->state.errorbuf = TRUE; /* wrote error string */ + } + error[len++] = '\n'; + error[len] = '\0'; + Curl_debug(data, CURLINFO_TEXT, error, len); + va_end(ap); + } +} + +/* Curl_infof() is for info message along the way */ +#define MAXINFO 2048 + +void Curl_infof(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(data && data->set.verbose) { + va_list ap; + int len; + char buffer[MAXINFO + 2]; + va_start(ap, fmt); + len = mvsnprintf(buffer, MAXINFO, fmt, ap); + va_end(ap); + buffer[len++] = '\n'; + buffer[len] = '\0'; + Curl_debug(data, CURLINFO_TEXT, buffer, len); + } +} + +#ifdef DEBUGBUILD + +void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf, + const char *fmt, ...) +{ + DEBUGASSERT(cf); + if(data && Curl_log_cf_is_debug(cf)) { + va_list ap; + int len; + char buffer[MAXINFO + 2]; + len = msnprintf(buffer, MAXINFO, "[CONN-%ld%s-%s] ", + cf->conn->connection_id, cf->sockindex? "/2" : "", + cf->cft->name); + va_start(ap, fmt); + len += mvsnprintf(buffer + len, MAXINFO - len, fmt, ap); + va_end(ap); + buffer[len++] = '\n'; + buffer[len] = '\0'; + Curl_debug(data, CURLINFO_TEXT, buffer, len); + } +} + + +static struct Curl_cftype *cf_types[] = { + &Curl_cft_tcp, + &Curl_cft_udp, + &Curl_cft_unix, + &Curl_cft_tcp_accept, + &Curl_cft_happy_eyeballs, + &Curl_cft_setup, +#ifdef USE_NGHTTP2 + &Curl_cft_nghttp2, +#endif +#ifdef USE_SSL + &Curl_cft_ssl, + &Curl_cft_ssl_proxy, +#endif +#if !defined(CURL_DISABLE_PROXY) +#if !defined(CURL_DISABLE_HTTP) + &Curl_cft_http_proxy, +#endif /* !CURL_DISABLE_HTTP */ + &Curl_cft_haproxy, + &Curl_cft_socks_proxy, +#endif /* !CURL_DISABLE_PROXY */ +#ifdef ENABLE_QUIC + &Curl_cft_http3, +#endif +#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + &Curl_cft_http_connect, +#endif + NULL, +}; + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +CURLcode Curl_log_init(void) +{ + const char *setting = getenv("CURL_DEBUG"); + if(setting) { + char *token, *tok_buf, *tmp; + size_t i; + + tmp = strdup(setting); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + + token = strtok_r(tmp, ", ", &tok_buf); + while(token) { + for(i = 0; cf_types[i]; ++i) { + if(strcasecompare(token, cf_types[i]->name)) { + cf_types[i]->log_level = CURL_LOG_DEBUG; + break; + } + } + token = strtok_r(NULL, ", ", &tok_buf); + } + free(tmp); + } + return CURLE_OK; +} +#else /* DEBUGBUILD */ + +CURLcode Curl_log_init(void) +{ + return CURLE_OK; +} + +#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) +void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf, + const char *fmt, ...) +{ + (void)data; + (void)cf; + (void)fmt; +} +#endif + +#endif /* !DEBUGBUILD */ diff --git a/Utilities/cmcurl/lib/curl_log.h b/Utilities/cmcurl/lib/curl_log.h new file mode 100644 index 0000000000..ad6143fa99 --- /dev/null +++ b/Utilities/cmcurl/lib/curl_log.h @@ -0,0 +1,138 @@ +#ifndef HEADER_CURL_LOG_H +#define HEADER_CURL_LOG_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 + * + ***************************************************************************/ + +struct Curl_easy; +struct Curl_cfilter; + +/** + * Init logging, return != 0 on failure. + */ +CURLcode Curl_log_init(void); + + +void Curl_infof(struct Curl_easy *, const char *fmt, ...); +void Curl_failf(struct Curl_easy *, const char *fmt, ...); + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + +#if defined(HAVE_VARIADIC_MACROS_C99) +#define infof(...) Curl_nop_stmt +#elif defined(HAVE_VARIADIC_MACROS_GCC) +#define infof(x...) Curl_nop_stmt +#else +#error "missing VARIADIC macro define, fix and rebuild!" +#endif + +#else /* CURL_DISABLE_VERBOSE_STRINGS */ + +#define infof Curl_infof + +#endif /* CURL_DISABLE_VERBOSE_STRINGS */ + +#define failf Curl_failf + + +#define CURL_LOG_DEFAULT 0 +#define CURL_LOG_DEBUG 1 +#define CURL_LOG_TRACE 2 + + +/* the function used to output verbose information */ +void Curl_debug(struct Curl_easy *data, curl_infotype type, + char *ptr, size_t size); + +#ifdef DEBUGBUILD + +/* explainer: we have some mix configuration and werror settings + * that define HAVE_VARIADIC_MACROS_C99 even though C89 is enforced + * on gnuc and some other compiler. Need to treat carefully. + */ +#if defined(HAVE_VARIADIC_MACROS_C99) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + +#define LOG_CF(data, cf, ...) \ + do { if(Curl_log_cf_is_debug(cf)) \ + Curl_log_cf_debug(data, cf, __VA_ARGS__); } while(0) +#else +#define LOG_CF Curl_log_cf_debug +#endif + +void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf, +#if defined(__GNUC__) && !defined(printf) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__MINGW32__) + const char *fmt, ...) + __attribute__((format(printf, 3, 4))); +#else + const char *fmt, ...); +#endif + +#define Curl_log_cf_is_debug(cf) \ + ((cf) && (cf)->cft->log_level >= CURL_LOG_DEBUG) + +#else /* !DEBUGBUILD */ + +#if defined(HAVE_VARIADIC_MACROS_C99) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#define LOG_CF(...) Curl_nop_stmt +#define Curl_log_cf_debug(...) Curl_nop_stmt +#elif defined(HAVE_VARIADIC_MACROS_GCC) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#define LOG_CF(x...) Curl_nop_stmt +#define Curl_log_cf_debug(x...) Curl_nop_stmt +#else +#define LOG_CF Curl_log_cf_debug +/* without c99, we seem unable to completely define away this function. */ +void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf, + const char *fmt, ...); +#endif + +#define Curl_log_cf_is_debug(x) ((void)(x), FALSE) + +#endif /* !DEBUGBUILD */ + +#define LOG_CF_IS_DEBUG(x) Curl_log_cf_is_debug(x) + +/* Macros intended for DEBUGF logging, use like: + * DEBUGF(infof(data, CFMSG(cf, "this filter %s rocks"), "very much")); + * and it will output: + * [CONN-1-0][CF-SSL] this filter very much rocks + * on connection #1 with sockindex 0 for filter of type "SSL". */ +#define DMSG(d,msg) \ + "[CONN-%ld] "msg, (d)->conn->connection_id +#define DMSGI(d,i,msg) \ + "[CONN-%ld-%d] "msg, (d)->conn->connection_id, (i) +#define CMSG(c,msg) \ + "[CONN-%ld] "msg, (c)->connection_id +#define CMSGI(c,i,msg) \ + "[CONN-%ld-%d] "msg, (c)->connection_id, (i) +#define CFMSG(cf,msg) \ + "[CONN-%ld-%d][CF-%s] "msg, (cf)->conn->connection_id, \ + (cf)->sockindex, (cf)->cft->name + + + +#endif /* HEADER_CURL_LOG_H */ diff --git a/Utilities/cmcurl/lib/curl_md4.h b/Utilities/cmcurl/lib/curl_md4.h index 8049355cff..03567b9916 100644 --- a/Utilities/cmcurl/lib/curl_md4.h +++ b/Utilities/cmcurl/lib/curl_md4.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_md5.h b/Utilities/cmcurl/lib/curl_md5.h index 789329654b..ec2512f002 100644 --- a/Utilities/cmcurl/lib/curl_md5.h +++ b/Utilities/cmcurl/lib/curl_md5.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_memory.h b/Utilities/cmcurl/lib/curl_memory.h index 092fc9f758..7af139196e 100644 --- a/Utilities/cmcurl/lib/curl_memory.h +++ b/Utilities/cmcurl/lib/curl_memory.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_memrchr.c b/Utilities/cmcurl/lib/curl_memrchr.c index c329a6176a..3f3dc6de16 100644 --- a/Utilities/cmcurl/lib/curl_memrchr.c +++ b/Utilities/cmcurl/lib/curl_memrchr.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_memrchr.h b/Utilities/cmcurl/lib/curl_memrchr.h index e7654e1d9a..a1a4ba0927 100644 --- a/Utilities/cmcurl/lib/curl_memrchr.h +++ b/Utilities/cmcurl/lib/curl_memrchr.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_multibyte.c b/Utilities/cmcurl/lib/curl_multibyte.c index 309dccbfef..522ea34e82 100644 --- a/Utilities/cmcurl/lib/curl_multibyte.c +++ b/Utilities/cmcurl/lib/curl_multibyte.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_multibyte.h b/Utilities/cmcurl/lib/curl_multibyte.h index 929714873a..ddac1f6382 100644 --- a/Utilities/cmcurl/lib/curl_multibyte.h +++ b/Utilities/cmcurl/lib/curl_multibyte.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.c b/Utilities/cmcurl/lib/curl_ntlm_core.c index 690f8f7674..25d2526025 100644 --- a/Utilities/cmcurl/lib/curl_ntlm_core.c +++ b/Utilities/cmcurl/lib/curl_ntlm_core.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -36,12 +36,13 @@ /* Please keep the SSL backend-specific #if branches in this order: 1. USE_OPENSSL - 2. USE_GNUTLS - 3. USE_NSS - 4. USE_MBEDTLS - 5. USE_SECTRANSP - 6. USE_OS400CRYPTO - 7. USE_WIN32_CRYPTO + 2. USE_WOLFSSL + 3. USE_GNUTLS + 4. USE_NSS + 5. USE_MBEDTLS + 6. USE_SECTRANSP + 7. USE_OS400CRYPTO + 8. USE_WIN32_CRYPTO This ensures that: - the same SSL branch gets activated throughout this source diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.h b/Utilities/cmcurl/lib/curl_ntlm_core.h index 60444c9dc6..33b651f5ea 100644 --- a/Utilities/cmcurl/lib/curl_ntlm_core.h +++ b/Utilities/cmcurl/lib/curl_ntlm_core.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -37,11 +37,11 @@ #define NTLM_NEEDS_NSS_INIT #endif -#ifdef USE_WOLFSSL +#if defined(USE_OPENSSL) +# include <openssl/ssl.h> +#elif defined(USE_WOLFSSL) # include <wolfssl/options.h> # include <wolfssl/openssl/ssl.h> -#elif defined(USE_OPENSSL) -# include <openssl/ssl.h> #endif /* Helpers to generate function byte arguments in little endian order */ diff --git a/Utilities/cmcurl/lib/curl_ntlm_wb.c b/Utilities/cmcurl/lib/curl_ntlm_wb.c index fcf5075227..a10e2a1b09 100644 --- a/Utilities/cmcurl/lib/curl_ntlm_wb.c +++ b/Utilities/cmcurl/lib/curl_ntlm_wb.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_ntlm_wb.h b/Utilities/cmcurl/lib/curl_ntlm_wb.h index 1f04db8c3d..37704c0fe0 100644 --- a/Utilities/cmcurl/lib/curl_ntlm_wb.h +++ b/Utilities/cmcurl/lib/curl_ntlm_wb.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_path.c b/Utilities/cmcurl/lib/curl_path.c index f00e3ee74d..2df8687a55 100644 --- a/Utilities/cmcurl/lib/curl_path.c +++ b/Utilities/cmcurl/lib/curl_path.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_path.h b/Utilities/cmcurl/lib/curl_path.h index 98e56eaad3..9ed09dea83 100644 --- a/Utilities/cmcurl/lib/curl_path.h +++ b/Utilities/cmcurl/lib/curl_path.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_printf.h b/Utilities/cmcurl/lib/curl_printf.h index 3823828cd9..6d3d492cc4 100644 --- a/Utilities/cmcurl/lib/curl_printf.h +++ b/Utilities/cmcurl/lib/curl_printf.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_range.c b/Utilities/cmcurl/lib/curl_range.c index 49999367d7..d499953c9e 100644 --- a/Utilities/cmcurl/lib/curl_range.c +++ b/Utilities/cmcurl/lib/curl_range.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_range.h b/Utilities/cmcurl/lib/curl_range.h index 33570abc25..77679e2b94 100644 --- a/Utilities/cmcurl/lib/curl_range.h +++ b/Utilities/cmcurl/lib/curl_range.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_rtmp.c b/Utilities/cmcurl/lib/curl_rtmp.c index 1932cb4ecb..2679a2cdc1 100644 --- a/Utilities/cmcurl/lib/curl_rtmp.c +++ b/Utilities/cmcurl/lib/curl_rtmp.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2012, Howard Chu, <hyc@highlandsun.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Howard Chu, <hyc@highlandsun.com> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/curl_rtmp.h b/Utilities/cmcurl/lib/curl_rtmp.h index f856085dc3..9b93ee060b 100644 --- a/Utilities/cmcurl/lib/curl_rtmp.h +++ b/Utilities/cmcurl/lib/curl_rtmp.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2010 - 2022, Howard Chu, <hyc@highlandsun.com> + * Copyright (C) Howard Chu, <hyc@highlandsun.com> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/curl_sasl.c b/Utilities/cmcurl/lib/curl_sasl.c index 46ee800a43..119fb9b25a 100644 --- a/Utilities/cmcurl/lib/curl_sasl.c +++ b/Utilities/cmcurl/lib/curl_sasl.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -36,7 +36,8 @@ #include "curl_setup.h" #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ - !defined(CURL_DISABLE_POP3) + !defined(CURL_DISABLE_POP3) || \ + (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) #include <curl/curl.h> #include "urldata.h" diff --git a/Utilities/cmcurl/lib/curl_sasl.h b/Utilities/cmcurl/lib/curl_sasl.h index c709d56a67..e94e6431a2 100644 --- a/Utilities/cmcurl/lib/curl_sasl.h +++ b/Utilities/cmcurl/lib/curl_sasl.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -125,9 +125,9 @@ struct SASL { unsigned short authmechs; /* Accepted authentication mechanisms */ unsigned short prefmech; /* Preferred authentication mechanism */ unsigned short authused; /* Auth mechanism used for the connection */ - bool resetprefs; /* For URL auth option parsing. */ - bool mutual_auth; /* Mutual authentication enabled (GSSAPI only) */ - bool force_ir; /* Protocol always supports initial response */ + BIT(resetprefs); /* For URL auth option parsing. */ + BIT(mutual_auth); /* Mutual authentication enabled (GSSAPI only) */ + BIT(force_ir); /* Protocol always supports initial response */ }; /* This is used to test whether the line starts with the given mechanism */ diff --git a/Utilities/cmcurl/lib/curl_setup.h b/Utilities/cmcurl/lib/curl_setup.h index 084d19a4e8..b58f813bf9 100644 --- a/Utilities/cmcurl/lib/curl_setup.h +++ b/Utilities/cmcurl/lib/curl_setup.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -792,14 +792,16 @@ endings either CRLF or LF so 't' is appropriate. #define FOPEN_APPENDTEXT "a" #endif -/* WinSock destroys recv() buffer when send() failed. - * Enabled automatically for Windows and for Cygwin as Cygwin sockets are - * wrappers for WinSock sockets. https://github.com/curl/curl/issues/657 - * Define DONT_USE_RECV_BEFORE_SEND_WORKAROUND to force disable workaround. +/* Windows workaround to recv before every send, because apparently Winsock + * destroys destroys recv() buffer when send() failed. + * This workaround is now disabled by default since it caused hard to fix bugs. + * Define USE_RECV_BEFORE_SEND_WORKAROUND to enable it. + * https://github.com/curl/curl/issues/657 + * https://github.com/curl/curl/pull/10409 */ #if !defined(DONT_USE_RECV_BEFORE_SEND_WORKAROUND) # if defined(WIN32) || defined(__CYGWIN__) -# define USE_RECV_BEFORE_SEND_WORKAROUND +/* # define USE_RECV_BEFORE_SEND_WORKAROUND */ # endif #else /* DONT_USE_RECV_BEFORE_SEND_WORKAROUND */ # ifdef USE_RECV_BEFORE_SEND_WORKAROUND @@ -851,6 +853,13 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, #define USE_HTTP3 #endif +/* Certain Windows implementations are not aligned with what curl expects, + so always use the local one on this platform. E.g. the mingw-w64 + implementation can return wrong results for non-ASCII inputs. */ +#if defined(HAVE_BASENAME) && defined(WIN32) +#undef HAVE_BASENAME +#endif + #if defined(USE_UNIX_SOCKETS) && defined(WIN32) # if defined(__MINGW32__) && !defined(LUP_SECURE) typedef u_short ADDRESS_FAMILY; /* Classic mingw, 11y+ old mingw-w64 */ @@ -868,4 +877,10 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, # endif #endif +/* OpenSSLv3 marks DES, MD5 and ENGINE functions deprecated but we have no + replacements (yet) so tell the compiler to not warn for them. */ +#ifdef USE_OPENSSL +#define OPENSSL_SUPPRESS_DEPRECATED +#endif + #endif /* HEADER_CURL_SETUP_H */ diff --git a/Utilities/cmcurl/lib/curl_setup_once.h b/Utilities/cmcurl/lib/curl_setup_once.h index ac4a7f1b35..e68eb502a0 100644 --- a/Utilities/cmcurl/lib/curl_setup_once.h +++ b/Utilities/cmcurl/lib/curl_setup_once.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_sha256.h b/Utilities/cmcurl/lib/curl_sha256.h index 39523af0c8..c5e157bee1 100644 --- a/Utilities/cmcurl/lib/curl_sha256.h +++ b/Utilities/cmcurl/lib/curl_sha256.h @@ -7,8 +7,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017, Florin Petriuc, <petriuc.florin@gmail.com> - * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Florin Petriuc, <petriuc.florin@gmail.com> + * 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 diff --git a/Utilities/cmcurl/lib/curl_sspi.c b/Utilities/cmcurl/lib/curl_sspi.c index 33108c48e9..eb21e7e2b0 100644 --- a/Utilities/cmcurl/lib/curl_sspi.c +++ b/Utilities/cmcurl/lib/curl_sspi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_sspi.h b/Utilities/cmcurl/lib/curl_sspi.h index ad111309c5..9816d59c84 100644 --- a/Utilities/cmcurl/lib/curl_sspi.h +++ b/Utilities/cmcurl/lib/curl_sspi.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_threads.c b/Utilities/cmcurl/lib/curl_threads.c index dff61676c9..e13e2947c2 100644 --- a/Utilities/cmcurl/lib/curl_threads.c +++ b/Utilities/cmcurl/lib/curl_threads.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curl_threads.h b/Utilities/cmcurl/lib/curl_threads.h index 63392f671f..facbc73705 100644 --- a/Utilities/cmcurl/lib/curl_threads.h +++ b/Utilities/cmcurl/lib/curl_threads.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/curlx.h b/Utilities/cmcurl/lib/curlx.h index 1796afa00a..7a753d6824 100644 --- a/Utilities/cmcurl/lib/curlx.h +++ b/Utilities/cmcurl/lib/curlx.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/dict.c b/Utilities/cmcurl/lib/dict.c index 993373ecae..0ce62a00e9 100644 --- a/Utilities/cmcurl/lib/dict.c +++ b/Utilities/cmcurl/lib/dict.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -98,37 +98,27 @@ const struct Curl_handler Curl_handler_dict = { PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ }; -static char *unescape_word(const char *inputbuff) +#define DYN_DICT_WORD 10000 +static char *unescape_word(const char *input) { - char *newp = NULL; - char *dictp; - size_t len; - - CURLcode result = Curl_urldecode(inputbuff, 0, &newp, &len, - REJECT_NADA); - if(!newp || result) - return NULL; - - dictp = malloc(len*2 + 1); /* add one for terminating zero */ - if(dictp) { - char *ptr; - char ch; - int olen = 0; - /* According to RFC2229 section 2.2, these letters need to be escaped with - \[letter] */ - for(ptr = newp; - (ch = *ptr) != 0; - ptr++) { - if((ch <= 32) || (ch == 127) || - (ch == '\'') || (ch == '\"') || (ch == '\\')) { - dictp[olen++] = '\\'; - } - dictp[olen++] = ch; - } - dictp[olen] = 0; + struct dynbuf out; + const char *ptr; + CURLcode result = CURLE_OK; + Curl_dyn_init(&out, DYN_DICT_WORD); + + /* According to RFC2229 section 2.2, these letters need to be escaped with + \[letter] */ + for(ptr = input; *ptr; ptr++) { + char ch = *ptr; + if((ch <= 32) || (ch == 127) || + (ch == '\'') || (ch == '\"') || (ch == '\\')) + result = Curl_dyn_addn(&out, "\\", 1); + if(!result) + result = Curl_dyn_addn(&out, ptr, 1); + if(result) + return NULL; } - free(newp); - return dictp; + return Curl_dyn_ptr(&out); } /* sendf() sends formatted data to the server */ @@ -178,20 +168,25 @@ static CURLcode sendf(curl_socket_t sockfd, struct Curl_easy *data, static CURLcode dict_do(struct Curl_easy *data, bool *done) { char *word; - char *eword; + char *eword = NULL; char *ppath; char *database = NULL; char *strategy = NULL; char *nthdef = NULL; /* This is not part of the protocol, but required by RFC 2229 */ - CURLcode result = CURLE_OK; + CURLcode result; struct connectdata *conn = data->conn; curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - char *path = data->state.up.path; + char *path; *done = TRUE; /* unconditionally */ + /* url-decode path before further evaluation */ + result = Curl_urldecode(data->state.up.path, 0, &path, NULL, REJECT_CTRL); + if(result) + return result; + if(strncasecompare(path, DICT_MATCH, sizeof(DICT_MATCH)-1) || strncasecompare(path, DICT_MATCH2, sizeof(DICT_MATCH2)-1) || strncasecompare(path, DICT_MATCH3, sizeof(DICT_MATCH3)-1)) { @@ -225,8 +220,10 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) } eword = unescape_word(word); - if(!eword) - return CURLE_OUT_OF_MEMORY; + if(!eword) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } result = sendf(sockfd, data, "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" @@ -239,11 +236,9 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) strategy, eword); - free(eword); - if(result) { failf(data, "Failed sending DICT request"); - return result; + goto error; } Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); /* no upload */ } @@ -273,8 +268,10 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) } eword = unescape_word(word); - if(!eword) - return CURLE_OUT_OF_MEMORY; + if(!eword) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } result = sendf(sockfd, data, "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" @@ -285,11 +282,9 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) database, eword); - free(eword); - if(result) { failf(data, "Failed sending DICT request"); - return result; + goto error; } Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); } @@ -310,13 +305,16 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done) "QUIT\r\n", ppath); if(result) { failf(data, "Failed sending DICT request"); - return result; + goto error; } Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); } } - return CURLE_OK; + error: + free(eword); + free(path); + return result; } #endif /* CURL_DISABLE_DICT */ diff --git a/Utilities/cmcurl/lib/dict.h b/Utilities/cmcurl/lib/dict.h index b283a0dfc1..ba9a92719b 100644 --- a/Utilities/cmcurl/lib/dict.h +++ b/Utilities/cmcurl/lib/dict.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/doh.c b/Utilities/cmcurl/lib/doh.c index 3b1d5d60ef..2e6a37785c 100644 --- a/Utilities/cmcurl/lib/doh.c +++ b/Utilities/cmcurl/lib/doh.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -396,6 +396,7 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, goto error; dohp->pending++; +#ifdef ENABLE_IPV6 if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { /* create IPv6 DoH request */ result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6], @@ -405,6 +406,7 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data, goto error; dohp->pending++; } +#endif return NULL; error: diff --git a/Utilities/cmcurl/lib/doh.h b/Utilities/cmcurl/lib/doh.h index 678e807fe4..7d7b694f33 100644 --- a/Utilities/cmcurl/lib/doh.h +++ b/Utilities/cmcurl/lib/doh.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/dynbuf.c b/Utilities/cmcurl/lib/dynbuf.c index 0b1cf9afd8..847f6f6ad1 100644 --- a/Utilities/cmcurl/lib/dynbuf.c +++ b/Utilities/cmcurl/lib/dynbuf.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/dynbuf.h b/Utilities/cmcurl/lib/dynbuf.h index 04a728c779..57ad62b22b 100644 --- a/Utilities/cmcurl/lib/dynbuf.h +++ b/Utilities/cmcurl/lib/dynbuf.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/easy.c b/Utilities/cmcurl/lib/easy.c index d7f93be1e0..db03026c95 100644 --- a/Utilities/cmcurl/lib/easy.c +++ b/Utilities/cmcurl/lib/easy.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -65,6 +65,7 @@ #include "easyif.h" #include "multiif.h" #include "select.h" +#include "cfilters.h" #include "sendf.h" /* for failf function prototype */ #include "connect.h" /* for Curl_getconnectinfo */ #include "slist.h" @@ -113,7 +114,7 @@ static curl_simple_lock s_lock = CURL_SIMPLE_LOCK_INIT; #if defined(_WIN32_WCE) #define system_strdup _strdup #elif !defined(HAVE_STRDUP) -#define system_strdup curlx_strdup +#define system_strdup Curl_strdup #else #define system_strdup strdup #endif @@ -164,6 +165,11 @@ static CURLcode global_init(long flags, bool memoryfuncs) #endif } + if(Curl_log_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_log_init failed\n")); + goto fail; + } + if(!Curl_ssl_init()) { DEBUGF(fprintf(stderr, "Error: Curl_ssl_init failed\n")); goto fail; @@ -913,11 +919,9 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) goto fail; } - /* duplicate all values in 'change' */ - if(data->state.cookielist) { - outcurl->state.cookielist = - Curl_slist_duplicate(data->state.cookielist); - if(!outcurl->state.cookielist) + if(data->set.cookielist) { + outcurl->set.cookielist = Curl_slist_duplicate(data->set.cookielist); + if(!outcurl->set.cookielist) goto fail; } #endif @@ -1003,8 +1007,8 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) if(outcurl) { #ifndef CURL_DISABLE_COOKIES - curl_slist_free_all(outcurl->state.cookielist); - outcurl->state.cookielist = NULL; + curl_slist_free_all(outcurl->set.cookielist); + outcurl->set.cookielist = NULL; #endif Curl_safefree(outcurl->state.buffer); Curl_dyn_free(&outcurl->state.headerb); @@ -1101,7 +1105,7 @@ CURLcode curl_easy_pause(struct Curl_easy *data, int action) k->keepon = newstate; if(!(newstate & KEEP_RECV_PAUSE)) { - Curl_http2_stream_pause(data, FALSE); + Curl_conn_ev_data_pause(data, FALSE); if(data->state.tempcount) { /* there are buffers for sending that can be delivered as the receive @@ -1224,7 +1228,7 @@ CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen, return result; *n = (size_t)n1; - + infof(data, "reached %s:%d", __FILE__, __LINE__); return CURLE_OK; } @@ -1294,29 +1298,34 @@ static int conn_upkeep(struct Curl_easy *data, struct connectdata *conn, void *param) { - /* Param is unused. */ - (void)param; + struct curltime *now = param; - if(conn->handler->connection_check) { - /* briefly attach the connection to this transfer for the purpose of - checking it */ - Curl_attach_connection(data, conn); + if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms) + return 0; + /* briefly attach for action */ + Curl_attach_connection(data, conn); + if(conn->handler->connection_check) { /* Do a protocol-specific keepalive check on the connection. */ conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE); - /* detach the connection again */ - Curl_detach_connection(data); } + else { + /* Do the generic action on the FIRSTSOCKE filter chain */ + Curl_conn_keep_alive(data, conn, FIRSTSOCKET); + } + Curl_detach_connection(data); + conn->keepalive = *now; return 0; /* continue iteration */ } static CURLcode upkeep(struct conncache *conn_cache, void *data) { + struct curltime now = Curl_now(); /* Loop over every connection and make connection alive. */ Curl_conncache_foreach(data, conn_cache, - data, + &now, conn_upkeep); return CURLE_OK; } diff --git a/Utilities/cmcurl/lib/easy_lock.h b/Utilities/cmcurl/lib/easy_lock.h index d96e56b8d8..5fa9477d06 100644 --- a/Utilities/cmcurl/lib/easy_lock.h +++ b/Utilities/cmcurl/lib/easy_lock.h @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/easygetopt.c b/Utilities/cmcurl/lib/easygetopt.c index a639bb3758..2b8a521cd2 100644 --- a/Utilities/cmcurl/lib/easygetopt.c +++ b/Utilities/cmcurl/lib/easygetopt.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * ___|___/|_| ______| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel.se>, et al. + * Copyright (C) Daniel Stenberg, <daniel.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 diff --git a/Utilities/cmcurl/lib/easyif.h b/Utilities/cmcurl/lib/easyif.h index 205382cd07..570ebef326 100644 --- a/Utilities/cmcurl/lib/easyif.h +++ b/Utilities/cmcurl/lib/easyif.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/easyoptions.c b/Utilities/cmcurl/lib/easyoptions.c index 03b814e5aa..a9c1efd006 100644 --- a/Utilities/cmcurl/lib/easyoptions.c +++ b/Utilities/cmcurl/lib/easyoptions.c @@ -1,11 +1,11 @@ /*************************************************************************** * _ _ ____ _ - * Project ___| | | | _ | | + * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ - * ___|___/|_| ______| + * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel.se>, et al. + * Copyright (C) Daniel Stenberg, <daniel.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 diff --git a/Utilities/cmcurl/lib/easyoptions.h b/Utilities/cmcurl/lib/easyoptions.h index 33f816d6b6..24b4cd93ed 100644 --- a/Utilities/cmcurl/lib/easyoptions.h +++ b/Utilities/cmcurl/lib/easyoptions.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/escape.c b/Utilities/cmcurl/lib/escape.c index ed59838bd1..56aa2b3988 100644 --- a/Utilities/cmcurl/lib/escape.c +++ b/Utilities/cmcurl/lib/escape.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -97,7 +97,7 @@ char *curl_easy_escape(struct Curl_easy *data, const char *string, return strdup(""); while(length--) { - unsigned char in = *string; /* we need to treat the characters unsigned */ + unsigned char in = *string++; /* treat the characters unsigned */ if(Curl_isunreserved(in)) { /* append this */ @@ -106,15 +106,28 @@ char *curl_easy_escape(struct Curl_easy *data, const char *string, } else { /* encode it */ - if(Curl_dyn_addf(&d, "%%%02X", in)) + const char hex[] = "0123456789ABCDEF"; + char out[3]={'%'}; + out[1] = hex[in>>4]; + out[2] = hex[in & 0xf]; + if(Curl_dyn_addn(&d, out, 3)) return NULL; } - string++; } return Curl_dyn_ptr(&d); } +static const unsigned char hextable[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ + 0, 10, 11, 12, 13, 14, 15 /* 0x60 - 0x66 */ +}; + +/* the input is a single hex digit */ +#define onehex2dec(x) hextable[x - '0'] + /* * Curl_urldecode() URL decodes the given string. * @@ -137,54 +150,47 @@ CURLcode Curl_urldecode(const char *string, size_t length, { size_t alloc; char *ns; - size_t strindex = 0; - unsigned long hex; DEBUGASSERT(string); DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */ - alloc = (length?length:strlen(string)) + 1; - ns = malloc(alloc); + alloc = (length?length:strlen(string)); + ns = malloc(alloc + 1); if(!ns) return CURLE_OUT_OF_MEMORY; - while(--alloc > 0) { + /* store output string */ + *ostring = ns; + + while(alloc) { unsigned char in = *string; if(('%' == in) && (alloc > 2) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { /* this is two hexadecimal digits following a '%' */ - char hexstr[3]; - char *ptr; - hexstr[0] = string[1]; - hexstr[1] = string[2]; - hexstr[2] = 0; - - hex = strtoul(hexstr, &ptr, 16); + in = (unsigned char)(onehex2dec(string[1]) << 4) | onehex2dec(string[2]); - in = curlx_ultouc(hex); /* this long is never bigger than 255 anyway */ - - string += 2; - alloc -= 2; + string += 3; + alloc -= 3; + } + else { + string++; + alloc--; } if(((ctrl == REJECT_CTRL) && (in < 0x20)) || ((ctrl == REJECT_ZERO) && (in == 0))) { - free(ns); + Curl_safefree(*ostring); return CURLE_URL_MALFORMAT; } - ns[strindex++] = in; - string++; + *ns++ = in; } - ns[strindex] = 0; /* terminate it */ + *ns = 0; /* terminate it */ if(olen) /* store output size */ - *olen = strindex; - - /* store output string */ - *ostring = ns; + *olen = ns - *ostring; return CURLE_OK; } diff --git a/Utilities/cmcurl/lib/escape.h b/Utilities/cmcurl/lib/escape.h index 61d4611bf7..cdbb712acc 100644 --- a/Utilities/cmcurl/lib/escape.h +++ b/Utilities/cmcurl/lib/escape.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/file.c b/Utilities/cmcurl/lib/file.c index 3f642be461..51c5d07ce4 100644 --- a/Utilities/cmcurl/lib/file.c +++ b/Utilities/cmcurl/lib/file.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/file.h b/Utilities/cmcurl/lib/file.h index 826d45380d..4565525592 100644 --- a/Utilities/cmcurl/lib/file.h +++ b/Utilities/cmcurl/lib/file.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/fileinfo.c b/Utilities/cmcurl/lib/fileinfo.c index 7bbf24bdeb..409b38fcbc 100644 --- a/Utilities/cmcurl/lib/fileinfo.c +++ b/Utilities/cmcurl/lib/fileinfo.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2010 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/fileinfo.h b/Utilities/cmcurl/lib/fileinfo.h index 5bad718cc8..af44212501 100644 --- a/Utilities/cmcurl/lib/fileinfo.h +++ b/Utilities/cmcurl/lib/fileinfo.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2010 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/fopen.c b/Utilities/cmcurl/lib/fopen.c index ad3691ba9d..f710dbf05a 100644 --- a/Utilities/cmcurl/lib/fopen.c +++ b/Utilities/cmcurl/lib/fopen.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -106,7 +106,6 @@ fail: free(tempstore); - *tempname = NULL; return result; } diff --git a/Utilities/cmcurl/lib/fopen.h b/Utilities/cmcurl/lib/fopen.h index 289e55f2af..e3a919d073 100644 --- a/Utilities/cmcurl/lib/fopen.h +++ b/Utilities/cmcurl/lib/fopen.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/formdata.c b/Utilities/cmcurl/lib/formdata.c index b30e8deee4..2bdb9f26ec 100644 --- a/Utilities/cmcurl/lib/formdata.c +++ b/Utilities/cmcurl/lib/formdata.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/formdata.h b/Utilities/cmcurl/lib/formdata.h index c6c6397cd2..caabb6324c 100644 --- a/Utilities/cmcurl/lib/formdata.h +++ b/Utilities/cmcurl/lib/formdata.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/ftp.c b/Utilities/cmcurl/lib/ftp.c index 874725bae3..8c728745d9 100644 --- a/Utilities/cmcurl/lib/ftp.c +++ b/Utilities/cmcurl/lib/ftp.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -61,6 +61,7 @@ #include "strcase.h" #include "vtls/vtls.h" #include "cfilters.h" +#include "cf-socket.h" #include "connect.h" #include "strerror.h" #include "inet_ntop.h" @@ -285,8 +286,8 @@ static CURLcode AcceptServerConnect(struct Curl_easy *data) conn->bits.do_more = FALSE; (void)curlx_nonblock(s, TRUE); /* enable non-blocking */ - /* Replace any filter on SECONDARY with one listeing on this socket */ - result = Curl_conn_socket_accepted_set(data, conn, SECONDARYSOCKET, &s); + /* Replace any filter on SECONDARY with one listening on this socket */ + result = Curl_conn_tcp_accepted_set(data, conn, SECONDARYSOCKET, &s); if(result) return result; @@ -819,26 +820,11 @@ static int ftp_domore_getsock(struct Curl_easy *data, if(FTP_STOP == ftpc->state) { int bits = GETSOCK_READSOCK(0); - bool any = FALSE; /* if stopped and still in this state, then we're also waiting for a connect on the secondary connection */ socks[0] = conn->sock[FIRSTSOCKET]; - - if(!data->set.ftp_use_port) { - int s; - int i; - /* PORT is used to tell the server to connect to us, and during that we - don't do happy eyeballs, but we do if we connect to the server */ - for(s = 1, i = 0; i<2; i++) { - if(conn->tempsock[i] != CURL_SOCKET_BAD) { - socks[s] = conn->tempsock[i]; - bits |= GETSOCK_WRITESOCK(s++); - any = TRUE; - } - } - } - if(!any) { + if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { socks[1] = conn->sock[SECONDARYSOCKET]; bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1); } @@ -1024,9 +1010,9 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, if(*addr != '\0') { /* attempt to get the address of the given interface name */ - switch(Curl_if2ip(conn->ip_addr->ai_family, + switch(Curl_if2ip(conn->remote_addr->family, #ifdef ENABLE_IPV6 - Curl_ipv6_scope(conn->ip_addr->ai_addr), + Curl_ipv6_scope(&conn->remote_addr->sa_addr), conn->scope_id, #endif addr, hbuf, sizeof(hbuf))) { @@ -1097,7 +1083,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, portsock = CURL_SOCKET_BAD; error = 0; for(ai = res; ai; ai = ai->ai_next) { - if(Curl_socket(data, ai, NULL, &portsock)) { + if(Curl_socket_open(data, ai, NULL, conn->transport, &portsock)) { error = SOCKERRNO; continue; } @@ -1266,9 +1252,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data, /* store which command was sent */ ftpc->count1 = fcmd; - /* Replace any filter on SECONDARY with one listeing on this socket */ - result = Curl_conn_socket_accepted_set(data, conn, SECONDARYSOCKET, - &portsock); + /* Replace any filter on SECONDARY with one listening on this socket */ + result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock); if(result) goto out; portsock = CURL_SOCKET_BAD; /* now held in filter */ @@ -1279,7 +1264,7 @@ out: state(data, FTP_STOP); } if(portsock != CURL_SOCKET_BAD) - Curl_closesocket(data, conn, portsock); + Curl_socket_close(data, conn, portsock); free(addr); return result; } @@ -1954,7 +1939,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, /* postponed address resolution in case of tcp fastopen */ if(conn->bits.tcp_fastopen && !conn->bits.reuse && !ftpc->newhost[0]) { - Curl_conninfo_remote(data, conn, conn->sock[FIRSTSOCKET]); + Curl_conn_ev_update_info(data, conn); Curl_safefree(ftpc->newhost); ftpc->newhost = strdup(control_address(conn)); if(!ftpc->newhost) @@ -2569,13 +2554,11 @@ static CURLcode ftp_state_loggedin(struct Curl_easy *data) /* for USER and PASS responses */ static CURLcode ftp_state_user_resp(struct Curl_easy *data, - int ftpcode, - ftpstate instate) + int ftpcode) { CURLcode result = CURLE_OK; struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; - (void)instate; /* no use for this yet */ /* some need password anyway, and others just return 2xx ignored */ if((ftpcode == 331) && (ftpc->state == FTP_USER)) { @@ -2670,7 +2653,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, /* 230 User logged in - already! Take as 220 if TLS required. */ if(data->set.use_ssl <= CURLUSESSL_TRY || conn->bits.ftp_use_control_ssl) - return ftp_state_user_resp(data, ftpcode, ftpc->state); + return ftp_state_user_resp(data, ftpcode); } else if(ftpcode != 220) { failf(data, "Got a %03d ftp-server response when 220 was expected", @@ -2742,7 +2725,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, if((ftpcode == 234) || (ftpcode == 334)) { /* this was BLOCKING, keep it so for now */ bool done; - if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) { + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); if(result) { /* we failed and bail out */ @@ -2775,7 +2758,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data, case FTP_USER: case FTP_PASS: - result = ftp_state_user_resp(data, ftpcode, ftpc->state); + result = ftp_state_user_resp(data, ftpcode); break; case FTP_ACCT: diff --git a/Utilities/cmcurl/lib/ftp.h b/Utilities/cmcurl/lib/ftp.h index 7f6f4328d1..65efa6f941 100644 --- a/Utilities/cmcurl/lib/ftp.h +++ b/Utilities/cmcurl/lib/ftp.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -42,7 +42,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, ssize_t *nread, /**************************************************************************** * FTP unique setup ***************************************************************************/ -typedef enum { +enum { FTP_STOP, /* do nothing state, stops the state machine */ FTP_WAIT220, /* waiting for the initial 220 response immediately after a connect */ @@ -80,7 +80,8 @@ typedef enum { FTP_STOR, /* generic state for STOR and APPE */ FTP_QUIT, FTP_LAST /* never used */ -} ftpstate; +}; +typedef unsigned char ftpstate; /* use the enum values */ struct ftp_parselist_data; /* defined later in ftplistparser.c */ @@ -122,38 +123,38 @@ struct ftp_conn { char *entrypath; /* the PWD reply when we logged on */ char *file; /* url-decoded file name (or path) */ char **dirs; /* realloc()ed array for path components */ - int dirdepth; /* number of entries used in the 'dirs' array */ - bool dont_check; /* Set to TRUE to prevent the final (post-transfer) - file size and 226/250 status check. It should still - read the line, just ignore the result. */ - bool ctl_valid; /* Tells Curl_ftp_quit() whether or not to do anything. If - the connection has timed out or been closed, this - should be FALSE when it gets to Curl_ftp_quit() */ - bool cwddone; /* if it has been determined that the proper CWD combo - already has been done */ - int cwdcount; /* number of CWD commands issued */ - bool cwdfail; /* set TRUE if a CWD command fails, as then we must prevent - caching the current directory */ - bool wait_data_conn; /* this is set TRUE if data connection is waited */ - /* newhost is the (allocated) IP addr or host name to connect the data - connection to */ - unsigned short newport; char *newhost; char *prevpath; /* url-decoded conn->path from the previous transfer */ char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a and others (A/I or zero) */ - int count1; /* general purpose counter for the state machine */ - int count2; /* general purpose counter for the state machine */ - int count3; /* general purpose counter for the state machine */ - ftpstate state; /* always use ftp.c:state() to change state! */ - ftpstate state_saved; /* transfer type saved to be reloaded after - data connection is established */ curl_off_t retr_size_saved; /* Size of retrieved file saved */ char *server_os; /* The target server operating system. */ curl_off_t known_filesize; /* file size is different from -1, if wildcard LIST parsing was done and wc_statemach set it */ + int dirdepth; /* number of entries used in the 'dirs' array */ + int cwdcount; /* number of CWD commands issued */ + int count1; /* general purpose counter for the state machine */ + int count2; /* general purpose counter for the state machine */ + int count3; /* general purpose counter for the state machine */ + /* newhost is the (allocated) IP addr or host name to connect the data + connection to */ + unsigned short newport; + ftpstate state; /* always use ftp.c:state() to change state! */ + ftpstate state_saved; /* transfer type saved to be reloaded after data + connection is established */ BIT(ftp_trying_alternative); + BIT(dont_check); /* Set to TRUE to prevent the final (post-transfer) + file size and 226/250 status check. It should still + read the line, just ignore the result. */ + BIT(ctl_valid); /* Tells Curl_ftp_quit() whether or not to do anything. If + the connection has timed out or been closed, this + should be FALSE when it gets to Curl_ftp_quit() */ + BIT(cwddone); /* if it has been determined that the proper CWD combo + already has been done */ + BIT(cwdfail); /* set TRUE if a CWD command fails, as then we must prevent + caching the current directory */ + BIT(wait_data_conn); /* this is set TRUE if data connection is waited */ }; #define DEFAULT_ACCEPT_TIMEOUT 60000 /* milliseconds == one minute */ diff --git a/Utilities/cmcurl/lib/ftplistparser.c b/Utilities/cmcurl/lib/ftplistparser.c index 3d529ef236..f7c6434eb0 100644 --- a/Utilities/cmcurl/lib/ftplistparser.c +++ b/Utilities/cmcurl/lib/ftplistparser.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/ftplistparser.h b/Utilities/cmcurl/lib/ftplistparser.h index 0a80543417..3d9a43b0df 100644 --- a/Utilities/cmcurl/lib/ftplistparser.h +++ b/Utilities/cmcurl/lib/ftplistparser.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/functypes.h b/Utilities/cmcurl/lib/functypes.h index 8891b1d5d6..075c02e54f 100644 --- a/Utilities/cmcurl/lib/functypes.h +++ b/Utilities/cmcurl/lib/functypes.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/getenv.c b/Utilities/cmcurl/lib/getenv.c index 5f00fd13a4..8069784728 100644 --- a/Utilities/cmcurl/lib/getenv.c +++ b/Utilities/cmcurl/lib/getenv.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/getinfo.c b/Utilities/cmcurl/lib/getinfo.c index 3a24c65294..826ffd0b0c 100644 --- a/Utilities/cmcurl/lib/getinfo.c +++ b/Utilities/cmcurl/lib/getinfo.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/getinfo.h b/Utilities/cmcurl/lib/getinfo.h index 1b5e8c20f8..56bb440b43 100644 --- a/Utilities/cmcurl/lib/getinfo.h +++ b/Utilities/cmcurl/lib/getinfo.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/gopher.c b/Utilities/cmcurl/lib/gopher.c index 6fbb7de327..4a11d9364e 100644 --- a/Utilities/cmcurl/lib/gopher.c +++ b/Utilities/cmcurl/lib/gopher.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/gopher.h b/Utilities/cmcurl/lib/gopher.h index 4ea269d2b4..9e3365b71c 100644 --- a/Utilities/cmcurl/lib/gopher.h +++ b/Utilities/cmcurl/lib/gopher.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/h2h3.c b/Utilities/cmcurl/lib/h2h3.c index 3a9288df5c..3b21699c3c 100644 --- a/Utilities/cmcurl/lib/h2h3.c +++ b/Utilities/cmcurl/lib/h2h3.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -118,6 +118,7 @@ static header_instruction inspect_header(const char *name, size_t namelen, CURLcode Curl_pseudo_headers(struct Curl_easy *data, const char *mem, /* the request */ const size_t len /* size of request */, + size_t* hdrlen /* opt size of headers read */, struct h2h3req **hp) { struct connectdata *conn = data->conn; @@ -291,6 +292,12 @@ CURLcode Curl_pseudo_headers(struct Curl_easy *data, } } + if(hdrlen) { + /* Skip trailing CRLF */ + end += 4; + *hdrlen = end - mem; + } + hreq->entries = nheader; *hp = hreq; diff --git a/Utilities/cmcurl/lib/h2h3.h b/Utilities/cmcurl/lib/h2h3.h index c35b7061fa..396c12c625 100644 --- a/Utilities/cmcurl/lib/h2h3.h +++ b/Utilities/cmcurl/lib/h2h3.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -51,6 +51,7 @@ struct h2h3req { CURLcode Curl_pseudo_headers(struct Curl_easy *data, const char *request, const size_t len, + size_t* hdrlen /* optional */, struct h2h3req **hp); /* diff --git a/Utilities/cmcurl/lib/hash.c b/Utilities/cmcurl/lib/hash.c index b6a2a33c72..06ce92cbd5 100644 --- a/Utilities/cmcurl/lib/hash.c +++ b/Utilities/cmcurl/lib/hash.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/hash.h b/Utilities/cmcurl/lib/hash.h index 5b59bf1118..9cfffc25b0 100644 --- a/Utilities/cmcurl/lib/hash.h +++ b/Utilities/cmcurl/lib/hash.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/headers.c b/Utilities/cmcurl/lib/headers.c index 978c918f4f..22e0e01258 100644 --- a/Utilities/cmcurl/lib/headers.c +++ b/Utilities/cmcurl/lib/headers.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/headers.h b/Utilities/cmcurl/lib/headers.h index 96332dbd0b..a5229ea22f 100644 --- a/Utilities/cmcurl/lib/headers.h +++ b/Utilities/cmcurl/lib/headers.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/hmac.c b/Utilities/cmcurl/lib/hmac.c index dfb0db5757..8d8de1757d 100644 --- a/Utilities/cmcurl/lib/hmac.c +++ b/Utilities/cmcurl/lib/hmac.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/hostasyn.c b/Utilities/cmcurl/lib/hostasyn.c index df50d13e19..536eb731c4 100644 --- a/Utilities/cmcurl/lib/hostasyn.c +++ b/Utilities/cmcurl/lib/hostasyn.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/hostip.c b/Utilities/cmcurl/lib/hostip.c index dd427a2c66..9738806b26 100644 --- a/Utilities/cmcurl/lib/hostip.c +++ b/Utilities/cmcurl/lib/hostip.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/hostip.h b/Utilities/cmcurl/lib/hostip.h index 3b1d814dab..e0d13cd402 100644 --- a/Utilities/cmcurl/lib/hostip.h +++ b/Utilities/cmcurl/lib/hostip.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/hostip4.c b/Utilities/cmcurl/lib/hostip4.c index 109bd1e5ea..9140180ffd 100644 --- a/Utilities/cmcurl/lib/hostip4.c +++ b/Utilities/cmcurl/lib/hostip4.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/hostip6.c b/Utilities/cmcurl/lib/hostip6.c index af8bc23021..6b0ba55e9f 100644 --- a/Utilities/cmcurl/lib/hostip6.c +++ b/Utilities/cmcurl/lib/hostip6.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/hostsyn.c b/Utilities/cmcurl/lib/hostsyn.c index 73d1e50569..ca8b0758c4 100644 --- a/Utilities/cmcurl/lib/hostsyn.c +++ b/Utilities/cmcurl/lib/hostsyn.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/hsts.c b/Utilities/cmcurl/lib/hsts.c index c449120f31..64cbae10c9 100644 --- a/Utilities/cmcurl/lib/hsts.c +++ b/Utilities/cmcurl/lib/hsts.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -39,6 +39,7 @@ #include "parsedate.h" #include "fopen.h" #include "rename.h" +#include "share.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -425,14 +426,23 @@ static CURLcode hsts_add(struct hsts *h, char *line) if(2 == rc) { time_t expires = strcmp(date, UNLIMITED) ? Curl_getdate_capped(date) : TIME_T_MAX; - CURLcode result; + CURLcode result = CURLE_OK; char *p = host; bool subdomain = FALSE; + struct stsentry *e; if(p[0] == '.') { p++; subdomain = TRUE; } - result = hsts_create(h, p, subdomain, expires); + /* only add it if not already present */ + e = Curl_hsts(h, p, subdomain); + if(!e) + result = hsts_create(h, p, subdomain, expires); + else { + /* the same host name, use the largest expire time */ + if(expires > e->expires) + e->expires = expires; + } if(result) return result; } @@ -551,4 +561,18 @@ CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h) return CURLE_OK; } +void Curl_hsts_loadfiles(struct Curl_easy *data) +{ + struct curl_slist *l = data->set.hstslist; + if(l) { + Curl_share_lock(data, CURL_LOCK_DATA_HSTS, CURL_LOCK_ACCESS_SINGLE); + + while(l) { + (void)Curl_hsts_loadfile(data, data->hsts, l->data); + l = l->next; + } + Curl_share_unlock(data, CURL_LOCK_DATA_HSTS); + } +} + #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */ diff --git a/Utilities/cmcurl/lib/hsts.h b/Utilities/cmcurl/lib/hsts.h index 0e36a7756b..d3431a5d7a 100644 --- a/Utilities/cmcurl/lib/hsts.h +++ b/Utilities/cmcurl/lib/hsts.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -59,9 +59,11 @@ CURLcode Curl_hsts_loadfile(struct Curl_easy *data, struct hsts *h, const char *file); CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h); +void Curl_hsts_loadfiles(struct Curl_easy *data); #else #define Curl_hsts_cleanup(x) #define Curl_hsts_loadcb(x,y) CURLE_OK #define Curl_hsts_save(x,y,z) +#define Curl_hsts_loadfiles(x) #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */ #endif /* HEADER_CURL_HSTS_H */ diff --git a/Utilities/cmcurl/lib/http.c b/Utilities/cmcurl/lib/http.c index 1b7502280e..cb585e72fd 100644 --- a/Utilities/cmcurl/lib/http.c +++ b/Utilities/cmcurl/lib/http.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -62,6 +62,7 @@ #include "cookie.h" #include "vauth/vauth.h" #include "vtls/vtls.h" +#include "vquic/vquic.h" #include "http_digest.h" #include "http_ntlm.h" #include "curl_ntlm_wb.h" @@ -150,7 +151,7 @@ const struct Curl_handler Curl_handler_ws = { http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ + Curl_ws_disconnect, /* disconnect */ ZERO_NULL, /* readwrite */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ @@ -204,7 +205,7 @@ const struct Curl_handler Curl_handler_wss = { http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ + Curl_ws_disconnect, /* disconnect */ ZERO_NULL, /* readwrite */ ZERO_NULL, /* connection_check */ ZERO_NULL, /* attach connection */ @@ -218,41 +219,6 @@ const struct Curl_handler Curl_handler_wss = { #endif -static CURLcode h3_setup_conn(struct Curl_easy *data, - struct connectdata *conn) -{ -#ifdef ENABLE_QUIC - /* We want HTTP/3 directly, setup the filter chain ourself, - * overriding the default behaviour. */ - DEBUGASSERT(conn->transport == TRNSPRT_QUIC); - - if(!(conn->handler->flags & PROTOPT_SSL)) { - failf(data, "HTTP/3 requested for non-HTTPS URL"); - return CURLE_URL_MALFORMAT; - } -#ifndef CURL_DISABLE_PROXY - if(conn->bits.socksproxy) { - failf(data, "HTTP/3 is not supported over a SOCKS proxy"); - return CURLE_URL_MALFORMAT; - } - if(conn->bits.httpproxy && conn->bits.tunnel_proxy) { - failf(data, "HTTP/3 is not supported over a HTTP proxy"); - return CURLE_URL_MALFORMAT; - } -#endif - - DEBUGF(infof(data, "HTTP/3 direct conn setup(conn #%ld, index=%d)", - conn->connection_id, FIRSTSOCKET)); - return Curl_conn_socket_set(data, conn, FIRSTSOCKET); - -#else /* ENABLE_QUIC */ - (void)conn; - (void)data; - DEBUGF(infof(data, "QUIC is not supported in this build")); - return CURLE_NOT_BUILT_IN; -#endif /* !ENABLE_QUIC */ -} - static CURLcode http_setup_conn(struct Curl_easy *data, struct connectdata *conn) { @@ -268,19 +234,16 @@ static CURLcode http_setup_conn(struct Curl_easy *data, Curl_mime_initpart(&http->form); data->req.p.http = http; - if(data->state.httpwant == CURL_HTTP_VERSION_3) { + if((data->state.httpwant == CURL_HTTP_VERSION_3) + || (data->state.httpwant == CURL_HTTP_VERSION_3ONLY)) { + CURLcode result = Curl_conn_may_http3(data, conn); + if(result) + return result; + + /* TODO: HTTP lower version eyeballing */ conn->transport = TRNSPRT_QUIC; } - if(conn->transport == TRNSPRT_QUIC) { - return h3_setup_conn(data, conn); - } - else { - if(!CONN_INUSE(conn)) - /* if not already multi-using, setup connection details */ - Curl_http2_setup_conn(conn); - Curl_http2_setup_req(data); - } return CURLE_OK; } @@ -1256,8 +1219,8 @@ static size_t readmoredata(char *buffer, size_t nitems, void *userp) { - struct Curl_easy *data = (struct Curl_easy *)userp; - struct HTTP *http = data->req.p.http; + struct HTTP *http = (struct HTTP *)userp; + struct Curl_easy *data = http->backup.data; size_t fullsize = size * nitems; if(!http->postsize) @@ -1309,6 +1272,7 @@ static size_t readmoredata(char *buffer, */ CURLcode Curl_buffer_send(struct dynbuf *in, struct Curl_easy *data, + struct HTTP *http, /* add the number of sent bytes to this counter */ curl_off_t *bytes_written, @@ -1321,14 +1285,13 @@ CURLcode Curl_buffer_send(struct dynbuf *in, char *ptr; size_t size; struct connectdata *conn = data->conn; - struct HTTP *http = data->req.p.http; size_t sendsize; curl_socket_t sockfd; size_t headersize; DEBUGASSERT(socketindex <= SECONDARYSOCKET); - sockfd = conn->sock[socketindex]; + sockfd = Curl_conn_get_socket(data, socketindex); /* The looping below is required since we use non-blocking sockets, but due to the circumstances we will just loop and try again and again etc */ @@ -1456,10 +1419,11 @@ CURLcode Curl_buffer_send(struct dynbuf *in, http->backup.fread_in = data->state.in; http->backup.postdata = http->postdata; http->backup.postsize = http->postsize; + http->backup.data = data; /* set the new pointers for the request-sending */ data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)data; + data->state.in = (void *)http; http->postdata = ptr; http->postsize = (curl_off_t)size; @@ -1468,7 +1432,6 @@ CURLcode Curl_buffer_send(struct dynbuf *in, http->send_buffer = *in; /* copy the whole struct */ http->sending = HTTPSEND_REQUEST; - return CURLE_OK; } http->sending = HTTPSEND_BODY; @@ -1579,8 +1542,8 @@ static int http_getsock_do(struct Curl_easy *data, curl_socket_t *socks) { /* write mode */ - (void)data; - socks[0] = conn->sock[FIRSTSOCKET]; + (void)conn; + socks[0] = Curl_conn_get_socket(data, FIRSTSOCKET); return GETSOCK_WRITESOCK(0); } @@ -1610,8 +1573,6 @@ CURLcode Curl_http_done(struct Curl_easy *data, return CURLE_OK; Curl_dyn_free(&http->send_buffer); - Curl_http2_done(data, premature); - Curl_quic_done(data, premature); Curl_mime_cleanpart(&http->form); Curl_dyn_reset(&data->state.headerb); Curl_hyper_done(data); @@ -1664,17 +1625,10 @@ bool Curl_use_http_1_1plus(const struct Curl_easy *data, static const char *get_http_string(const struct Curl_easy *data, const struct connectdata *conn) { -#ifdef ENABLE_QUIC - if((data->state.httpwant == CURL_HTTP_VERSION_3) || - (conn->httpversion == 30)) + if(Curl_conn_is_http3(data, conn, FIRSTSOCKET)) return "3"; -#endif - -#ifdef USE_NGHTTP2 - if(conn->proto.httpc.h2) + if(Curl_conn_is_http2(data, conn, FIRSTSOCKET)) return "2"; -#endif - if(Curl_use_http_1_1plus(data, conn)) return "1.1"; @@ -2359,7 +2313,7 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, curl_off_t included_body = 0; #else /* from this point down, this function should not be used */ -#define Curl_buffer_send(a,b,c,d,e) CURLE_OK +#define Curl_buffer_send(a,b,c,d,e,f) CURLE_OK #endif CURLcode result = CURLE_OK; struct HTTP *http = data->req.p.http; @@ -2403,7 +2357,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, Curl_pgrsSetUploadSize(data, http->postsize); /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(r, data, &data->info.request_size, 0, + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending PUT request"); @@ -2424,7 +2379,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, if(result) return result; - result = Curl_buffer_send(r, data, &data->info.request_size, 0, + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending POST request"); @@ -2440,8 +2396,7 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, we don't upload data chunked, as RFC2616 forbids us to set both kinds of headers (Transfer-Encoding: chunked and Content-Length) */ if(http->postsize != -1 && !data->req.upload_chunky && - (conn->bits.authneg || - !Curl_checkheaders(data, STRCONST("Content-Length")))) { + (!Curl_checkheaders(data, STRCONST("Content-Length")))) { /* we allow replacing this header if not during auth negotiation, although it isn't very wise to actually set your own */ result = Curl_dyn_addf(r, @@ -2495,7 +2450,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, http->sending = HTTPSEND_BODY; /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(r, data, &data->info.request_size, 0, + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending POST request"); @@ -2561,7 +2517,7 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, /* In HTTP2, we send request body in DATA frame regardless of its size. */ - if(conn->httpversion != 20 && + if(conn->httpversion < 20 && !data->state.expect100header && (http->postsize < MAX_INITIAL_POST_SIZE)) { /* if we don't use expect: 100 AND @@ -2612,11 +2568,10 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, else { /* A huge POST coming up, do data separate from the request */ http->postdata = data->set.postfields; - http->sending = HTTPSEND_BODY; - + http->backup.data = data; data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)data; + data->state.in = (void *)http; /* set the upload size to the progress meter */ Curl_pgrsSetUploadSize(data, http->postsize); @@ -2655,7 +2610,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, } } /* issue the request */ - result = Curl_buffer_send(r, data, &data->info.request_size, included_body, + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, included_body, FIRSTSOCKET); if(result) @@ -2671,7 +2627,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, return result; /* issue the request */ - result = Curl_buffer_send(r, data, &data->info.request_size, 0, + result = Curl_buffer_send(r, data, data->req.p.http, + &data->info.request_size, 0, FIRSTSOCKET); if(result) failf(data, "Failed sending HTTP request"); @@ -3021,50 +2978,27 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) the rest of the request in the PERFORM phase. */ *done = TRUE; - if(conn->transport != TRNSPRT_QUIC) { - if(conn->httpversion < 20) { /* unless the connection is re-used and - already http2 */ - switch(conn->alpn) { - case CURL_HTTP_VERSION_2: - conn->httpversion = 20; /* we know we're on HTTP/2 now */ - - result = Curl_http2_switched(data, NULL, 0); - if(result) - return result; - break; - case CURL_HTTP_VERSION_1_1: - /* continue with HTTP/1.1 when explicitly requested */ - break; - default: - /* Check if user wants to use HTTP/2 with clear TCP */ -#ifdef USE_NGHTTP2 - if(data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - /* We don't support HTTP/2 proxies yet. Also it's debatable - whether or not this setting should apply to HTTP/2 proxies. */ - infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); - break; - } -#endif - DEBUGF(infof(data, "HTTP/2 over clean TCP")); - conn->httpversion = 20; - - result = Curl_http2_switched(data, NULL, 0); - if(result) - return result; - } -#endif - break; - } - } - else { - /* prepare for an http2 request */ - result = Curl_http2_setup(data, conn); + switch(conn->alpn) { + case CURL_HTTP_VERSION_3: + DEBUGASSERT(Curl_conn_is_http3(data, conn, FIRSTSOCKET)); + break; + case CURL_HTTP_VERSION_2: + DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET)); + break; + case CURL_HTTP_VERSION_1_1: + /* continue with HTTP/1.1 when explicitly requested */ + break; + default: + /* Check if user wants to use HTTP/2 with clear TCP */ + if(Curl_http2_may_switch(data, conn, FIRSTSOCKET)) { + DEBUGF(infof(data, "HTTP/2 over clean TCP")); + result = Curl_http2_switch(data, conn, FIRSTSOCKET); if(result) return result; } + break; } + http = data->req.p.http; DEBUGASSERT(http); @@ -3224,7 +3158,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) } if(!(conn->handler->flags&PROTOPT_SSL) && - conn->httpversion != 20 && + conn->httpversion < 20 && (data->state.httpwant == CURL_HTTP_VERSION_2)) { /* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done over SSL */ @@ -3236,8 +3170,10 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) } result = Curl_http_cookies(data, conn, &req); +#ifdef USE_WEBSOCKETS if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) result = Curl_ws_request(data, &req); +#endif if(!result) result = Curl_add_timecondition(data, &req); if(!result) @@ -3282,7 +3218,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done) } } - if((conn->httpversion == 20) && data->req.upload_chunky) + if((conn->httpversion >= 20) && data->req.upload_chunky) /* upload_chunky was set above to set up the request in a chunky fashion, but is disabled here again to avoid that the chunked encoded version is actually used when sending the request body over h2 */ @@ -3669,7 +3605,8 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, #endif )) { /* the ALPN of the current request */ - enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; + enum alpnid id = (conn->httpversion == 30)? ALPN_h3 : + (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; result = Curl_altsvc_parse(data, data->asi, headp + strlen("Alt-Svc:"), id, conn->host.name, @@ -3963,7 +3900,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, /* switch to http2 now. The bytes after response headers are also processed here, otherwise they are lost. */ - result = Curl_http2_switched(data, k->str, *nread); + result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, + k->str, *nread); if(result) return result; *nread = 0; @@ -3971,7 +3909,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, #ifdef USE_WEBSOCKETS else if(k->upgr101 == UPGR101_WS) { /* verify the response */ - result = Curl_ws_accept(data); + result = Curl_ws_accept(data, k->str, *nread); if(result) return result; k->header = FALSE; /* no more header to parse! */ @@ -4191,11 +4129,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, stream. In order to do this, we keep reading until we close the stream. */ if(0 == k->maxdownload -#if defined(USE_NGHTTP2) - && !((conn->handler->protocol & PROTO_FAMILY_HTTP) && - conn->httpversion == 20) -#endif - ) + && !Curl_conn_is_http2(data, conn, FIRSTSOCKET) + && !Curl_conn_is_http3(data, conn, FIRSTSOCKET)) *stop_reading = TRUE; if(*stop_reading) { @@ -4290,7 +4225,6 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, } if(conn->httpversion < 20) { conn->bundle->multiuse = BUNDLE_NO_MULTIUSE; - infof(data, "Mark bundle as not supporting multiuse"); } } else if(!nc) { diff --git a/Utilities/cmcurl/lib/http.h b/Utilities/cmcurl/lib/http.h index ecfe4eed0a..444abc0be6 100644 --- a/Utilities/cmcurl/lib/http.h +++ b/Utilities/cmcurl/lib/http.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -24,6 +24,11 @@ * ***************************************************************************/ #include "curl_setup.h" + +#if defined(USE_MSH3) && !defined(_WIN32) +#include <pthread.h> +#endif + #include "ws.h" typedef enum { @@ -37,11 +42,7 @@ typedef enum { #ifndef CURL_DISABLE_HTTP -#ifdef USE_NGHTTP2 -#include <nghttp2/nghttp2.h> -#endif - -#if defined(_WIN32) && defined(ENABLE_QUIC) +#if defined(ENABLE_QUIC) || defined(USE_NGHTTP2) #include <stdint.h> #endif @@ -73,8 +74,10 @@ char *Curl_checkProxyheaders(struct Curl_easy *data, const struct connectdata *conn, const char *thisheader, const size_t thislen); +struct HTTP; /* see below */ CURLcode Curl_buffer_send(struct dynbuf *in, struct Curl_easy *data, + struct HTTP *http, curl_off_t *bytes_written, curl_off_t included_body_bytes, int socketindex); @@ -179,29 +182,6 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data); struct h3out; /* see ngtcp2 */ #endif -#ifdef USE_MSH3 -#ifdef _WIN32 -#define msh3_lock CRITICAL_SECTION -#define msh3_lock_initialize(lock) InitializeCriticalSection(lock) -#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock) -#define msh3_lock_acquire(lock) EnterCriticalSection(lock) -#define msh3_lock_release(lock) LeaveCriticalSection(lock) -#else /* !_WIN32 */ -#include <pthread.h> -#define msh3_lock pthread_mutex_t -#define msh3_lock_initialize(lock) { \ - pthread_mutexattr_t attr; \ - pthread_mutexattr_init(&attr); \ - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \ - pthread_mutex_init(lock, &attr); \ - pthread_mutexattr_destroy(&attr); \ -} -#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock) -#define msh3_lock_acquire(lock) pthread_mutex_lock(lock) -#define msh3_lock_release(lock) pthread_mutex_unlock(lock) -#endif /* _WIN32 */ -#endif /* USE_MSH3 */ - /**************************************************************************** * HTTP unique setup ***************************************************************************/ @@ -220,6 +200,7 @@ struct HTTP { void *fread_in; /* backup storage for fread_in pointer */ const char *postdata; curl_off_t postsize; + struct Curl_easy *data; } backup; enum { @@ -258,7 +239,6 @@ struct HTTP { #if defined(USE_NGHTTP2) || defined(USE_NGHTTP3) bool bodystarted; int status_code; /* HTTP status code */ - bool closed; /* TRUE on HTTP2 stream close */ char *mem; /* points to a buffer in memory to store received data */ size_t len; /* size of the buffer 'mem' points to */ size_t memlen; /* size of data copied to mem */ @@ -268,6 +248,8 @@ struct HTTP { const uint8_t *upload_mem; /* points to a buffer to read from */ size_t upload_len; /* size of the buffer 'upload_mem' points to */ curl_off_t upload_left; /* number of bytes left to upload */ + bool closed; /* TRUE on stream close */ + bool reset; /* TRUE on stream reset */ #endif #ifdef ENABLE_QUIC @@ -278,20 +260,25 @@ struct HTTP { bool firstheader; /* FALSE until headers arrive */ bool firstbody; /* FALSE until body arrives */ bool h3req; /* FALSE until request is issued */ -#endif +#endif /* !USE_MSH3 */ bool upload_done; -#endif +#endif /* ENABLE_QUIC */ #ifdef USE_NGHTTP3 - size_t unacked_window; + size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */ struct h3out *h3out; /* per-stream buffers for upload */ struct dynbuf overflow; /* excess data received during a single Curl_read */ -#endif +#endif /* USE_NGHTTP3 */ #ifdef USE_MSH3 struct MSH3_REQUEST *req; - msh3_lock recv_lock; +#ifdef _WIN32 + CRITICAL_SECTION recv_lock; +#else /* !_WIN32 */ + pthread_mutex_t recv_lock; +#endif /* _WIN32 */ /* Receive Buffer (Headers and Data) */ uint8_t* recv_buf; size_t recv_buf_alloc; + size_t recv_buf_max; /* Receive Headers */ size_t recv_header_len; bool recv_header_complete; @@ -300,53 +287,13 @@ struct HTTP { bool recv_data_complete; /* General Receive Error */ CURLcode recv_error; -#endif -}; - -#ifdef USE_NGHTTP2 -/* h2 settings for this connection */ -struct h2settings { - uint32_t max_concurrent_streams; - bool enable_push; -}; -#endif - -struct http_conn { -#ifdef USE_NGHTTP2 -#define H2_BINSETTINGS_LEN 80 - uint8_t binsettings[H2_BINSETTINGS_LEN]; - size_t binlen; /* length of the binsettings data */ - - /* We associate the connectdata struct with the connection, but we need to - make sure we can identify the current "driving" transfer. This is a - work-around for the lack of nghttp2_session_set_user_data() in older - nghttp2 versions that we want to support. (Added in 1.31.0) */ - struct Curl_easy *trnsfr; - - nghttp2_session *h2; - Curl_send *send_underlying; /* underlying send Curl_send callback */ - Curl_recv *recv_underlying; /* underlying recv Curl_recv callback */ - char *inbuf; /* buffer to receive data from underlying socket */ - size_t inbuflen; /* number of bytes filled in inbuf */ - size_t nread_inbuf; /* number of bytes read from in inbuf */ - /* We need separate buffer for transmission and reception because we - may call nghttp2_session_send() after the - nghttp2_session_mem_recv() but mem buffer is still not full. In - this case, we wrongly sends the content of mem buffer if we share - them for both cases. */ - int32_t pause_stream_id; /* stream ID which paused - nghttp2_session_mem_recv */ - size_t drain_total; /* sum of all stream's UrlState.drain */ - - /* this is a hash of all individual streams (Curl_easy structs) */ - struct h2settings settings; - - /* list of settings that will be sent */ - nghttp2_settings_entry local_settings[3]; - size_t local_settings_num; -#else - int unused; /* prevent a compiler warning */ -#endif +#endif /* USE_MSH3 */ +#ifdef USE_QUICHE + bool h3_got_header; /* TRUE when h3 stream has recvd some HEADER */ + bool h3_recving_data; /* TRUE when h3 stream is reading DATA */ + bool h3_body_pending; /* TRUE when h3 stream may have more body DATA */ + struct h3_event_node *pending; +#endif /* USE_QUICHE */ }; CURLcode Curl_http_size(struct Curl_easy *data); diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c index b9d3245cf8..bdb5e7378e 100644 --- a/Utilities/cmcurl/lib/http2.c +++ b/Utilities/cmcurl/lib/http2.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -35,6 +35,7 @@ #include "strcase.h" #include "multiif.h" #include "url.h" +#include "cfilters.h" #include "connect.h" #include "strtoofft.h" #include "strdup.h" @@ -63,124 +64,322 @@ #define HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */ -#ifdef DEBUG_HTTP2 -#define H2BUGF(x) x -#else -#define H2BUGF(x) do { } while(0) -#endif -static ssize_t http2_recv(struct Curl_easy *data, int sockindex, - char *mem, size_t len, CURLcode *err); -static bool http2_connisdead(struct Curl_easy *data, - struct connectdata *conn); -static int h2_session_send(struct Curl_easy *data, - nghttp2_session *h2); -static int h2_process_pending_input(struct Curl_easy *data, - struct http_conn *httpc, - CURLcode *err); +#define H2_SETTINGS_IV_LEN 3 +#define H2_BINSETTINGS_LEN 80 -/* - * Curl_http2_init_state() is called when the easy handle is created and - * allows for HTTP/2 specific init of state. - */ -void Curl_http2_init_state(struct UrlState *state) +static int populate_settings(nghttp2_settings_entry *iv, + struct Curl_easy *data) { - state->stream_weight = NGHTTP2_DEFAULT_WEIGHT; + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = Curl_multi_max_concurrent_streams(data->multi); + + iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = HTTP2_HUGE_WINDOW_SIZE; + + iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[2].value = data->multi->push_cb != NULL; + + return 3; +} + +static size_t populate_binsettings(uint8_t *binsettings, + struct Curl_easy *data) +{ + nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; + int ivlen; + + ivlen = populate_settings(iv, data); + /* this returns number of bytes it wrote */ + return nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN, + iv, ivlen); +} + +struct cf_h2_ctx { + nghttp2_session *h2; + uint32_t max_concurrent_streams; + bool enable_push; + /* The easy handle used in the current filter call, cleared at return */ + struct cf_call_data call_data; + + char *inbuf; /* buffer to receive data from underlying socket */ + size_t inbuflen; /* number of bytes filled in inbuf */ + size_t nread_inbuf; /* number of bytes read from in inbuf */ + + struct dynbuf outbuf; + + /* We need separate buffer for transmission and reception because we + may call nghttp2_session_send() after the + nghttp2_session_mem_recv() but mem buffer is still not full. In + this case, we wrongly sends the content of mem buffer if we share + them for both cases. */ + int32_t pause_stream_id; /* stream ID which paused + nghttp2_session_mem_recv */ + size_t drain_total; /* sum of all stream's UrlState.drain */ +}; + +/* How to access `call_data` from a cf_h2 filter */ +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_h2_ctx *)(cf)->ctx)->call_data + + +static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + if(ctx->h2) { + nghttp2_session_del(ctx->h2); + } + free(ctx->inbuf); + Curl_dyn_free(&ctx->outbuf); + memset(ctx, 0, sizeof(*ctx)); + ctx->call_data = save; +} + +static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) +{ + if(ctx) { + cf_h2_ctx_clear(ctx); + free(ctx); + } +} + +static int h2_client_new(struct Curl_cfilter *cf, + nghttp2_session_callbacks *cbs) +{ + struct cf_h2_ctx *ctx = cf->ctx; + +#if NGHTTP2_VERSION_NUM < 0x013200 + /* before 1.50.0 */ + return nghttp2_session_client_new(&ctx->h2, cbs, cf); +#else + nghttp2_option *o; + int rc = nghttp2_option_new(&o); + if(rc) + return rc; + /* turn off RFC 9113 leading and trailing white spaces validation against + HTTP field value. */ + nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); + rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o); + nghttp2_option_del(o); + return rc; +#endif } +static ssize_t send_callback(nghttp2_session *h2, + const uint8_t *mem, size_t length, int flags, + void *userp); +static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, + void *userp); +static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const uint8_t *mem, size_t len, void *userp); +static int on_stream_close(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *userp); +static int on_begin_headers(nghttp2_session *session, + const nghttp2_frame *frame, void *userp); +static int on_header(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, + void *userp); +static int error_callback(nghttp2_session *session, const char *msg, + size_t len, void *userp); + /* - * Curl_http2_init_userset() is called when the easy handle is created and - * allows for HTTP/2 specific user-set fields. + * multi_connchanged() is called to tell that there is a connection in + * this multi handle that has changed state (multiplexing become possible, the + * number of allowed streams changed or similar), and a subsequent use of this + * multi handle should move CONNECT_PEND handles back to CONNECT to have them + * retry. */ -void Curl_http2_init_userset(struct UserDefined *set) +static void multi_connchanged(struct Curl_multi *multi) { - set->stream_weight = NGHTTP2_DEFAULT_WEIGHT; + multi->recheckstate = TRUE; } -static int http2_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *sock) +static CURLcode http2_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) { - const struct http_conn *c = &conn->proto.httpc; - struct SingleRequest *k = &data->req; - int bitmap = GETSOCK_BLANK; struct HTTP *stream = data->req.p.http; - sock[0] = conn->sock[FIRSTSOCKET]; + (void)cf; + DEBUGASSERT(stream); + DEBUGASSERT(data->state.buffer); - if(!(k->keepon & KEEP_RECV_PAUSE)) - /* Unless paused - in an HTTP/2 connection we can basically always get a - frame so we should always be ready for one */ - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); + stream->stream_id = -1; - /* we're (still uploading OR the HTTP/2 layer wants to send data) AND - there's a window to send data in */ - if((((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) || - nghttp2_session_want_write(c->h2)) && - (nghttp2_session_get_remote_window_size(c->h2) && - nghttp2_session_get_stream_remote_window_size(c->h2, - stream->stream_id))) - bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); + Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS); + Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS); - return bitmap; + stream->bodystarted = FALSE; + stream->status_code = -1; + stream->pausedata = NULL; + stream->pauselen = 0; + stream->closed = FALSE; + stream->close_handled = FALSE; + stream->memlen = 0; + stream->error = NGHTTP2_NO_ERROR; + stream->upload_left = 0; + stream->upload_mem = NULL; + stream->upload_len = 0; + stream->mem = data->state.buffer; + stream->len = data->set.buffer_size; + + return CURLE_OK; } /* - * http2_stream_free() free HTTP2 stream related data + * Initialize the cfilter context */ -static void http2_stream_free(struct HTTP *http) +static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool via_h1_upgrade) { - if(http) { - Curl_dyn_free(&http->header_recvbuf); - for(; http->push_headers_used > 0; --http->push_headers_used) { - free(http->push_headers[http->push_headers_used - 1]); + struct cf_h2_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + CURLcode result = CURLE_OUT_OF_MEMORY; + int rc; + nghttp2_session_callbacks *cbs = NULL; + + DEBUGASSERT(!ctx->h2); + ctx->inbuf = malloc(H2_BUFSIZE); + if(!ctx->inbuf) + goto out; + /* we want to aggregate small frames, SETTINGS, PRIO, UPDATES */ + Curl_dyn_init(&ctx->outbuf, 4*1024); + + rc = nghttp2_session_callbacks_new(&cbs); + if(rc) { + failf(data, "Couldn't initialize nghttp2 callbacks"); + goto out; + } + + nghttp2_session_callbacks_set_send_callback(cbs, send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + cbs, on_data_chunk_recv); + nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close); + nghttp2_session_callbacks_set_on_begin_headers_callback( + cbs, on_begin_headers); + nghttp2_session_callbacks_set_on_header_callback(cbs, on_header); + nghttp2_session_callbacks_set_error_callback(cbs, error_callback); + + /* The nghttp2 session is not yet setup, do it */ + rc = h2_client_new(cf, cbs); + if(rc) { + failf(data, "Couldn't initialize nghttp2"); + goto out; + } + ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS; + + result = http2_data_setup(cf, data); + if(result) + goto out; + + if(via_h1_upgrade) { + /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted + * in the H1 request and we upgrade from there. This stream + * is opened implicitly as #1. */ + uint8_t binsettings[H2_BINSETTINGS_LEN]; + size_t binlen; /* length of the binsettings data */ + + binlen = populate_binsettings(binsettings, data); + + stream->stream_id = 1; + /* queue SETTINGS frame (again) */ + rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen, + data->state.httpreq == HTTPREQ_HEAD, + NULL); + if(rc) { + failf(data, "nghttp2_session_upgrade2() failed: %s(%d)", + nghttp2_strerror(rc), rc); + result = CURLE_HTTP2; + goto out; + } + + rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->stream_id, + data); + if(rc) { + infof(data, "http/2: failed to set user_data for stream %u", + stream->stream_id); + DEBUGASSERT(0); } - free(http->push_headers); - http->push_headers = NULL; } -} + else { + nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; + int ivlen; -/* - * Disconnects *a* connection used for HTTP/2. It might be an old one from the - * connection cache and not the "main" one. Don't touch the easy handle! - */ + /* H2 Settings need to be submitted. Stream is not open yet. */ + DEBUGASSERT(stream->stream_id == -1); -static CURLcode http2_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) -{ - struct http_conn *c = &conn->proto.httpc; - (void)dead_connection; -#ifndef DEBUG_HTTP2 - (void)data; -#endif + ivlen = populate_settings(iv, data); + rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, + iv, ivlen); + if(rc) { + failf(data, "nghttp2_submit_settings() failed: %s(%d)", + nghttp2_strerror(rc), rc); + result = CURLE_HTTP2; + goto out; + } + } - H2BUGF(infof(data, "HTTP/2 DISCONNECT starts now")); + rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0, + HTTP2_HUGE_WINDOW_SIZE); + if(rc) { + failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", + nghttp2_strerror(rc), rc); + result = CURLE_HTTP2; + goto out; + } - nghttp2_session_del(c->h2); - Curl_safefree(c->inbuf); + /* all set, traffic will be send on connect */ + result = CURLE_OK; - H2BUGF(infof(data, "HTTP/2 DISCONNECT done")); +out: + if(cbs) + nghttp2_session_callbacks_del(cbs); + return result; +} - return CURLE_OK; +static CURLcode h2_session_send(struct Curl_cfilter *cf, + struct Curl_easy *data); +static int h2_process_pending_input(struct Curl_cfilter *cf, + struct Curl_easy *data, + CURLcode *err); + +/* + * http2_stream_free() free HTTP2 stream related data + */ +static void http2_stream_free(struct HTTP *stream) +{ + if(stream) { + Curl_dyn_free(&stream->header_recvbuf); + for(; stream->push_headers_used > 0; --stream->push_headers_used) { + free(stream->push_headers[stream->push_headers_used - 1]); + } + free(stream->push_headers); + stream->push_headers = NULL; + } } /* * The server may send us data at any point (e.g. PING frames). Therefore, * we cannot assume that an HTTP/2 socket is dead just because it is readable. * - * Instead, if it is readable, run Curl_connalive() to peek at the socket + * Check the lower filters first and, if successful, peek at the socket * and distinguish between closed and data. */ -static bool http2_connisdead(struct Curl_easy *data, struct connectdata *conn) +static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data) { + struct cf_h2_ctx *ctx = cf->ctx; int sval; bool dead = TRUE; - if(conn->bits.close) + if(!cf->next || !cf->next->cft->is_alive(cf->next, data)) return TRUE; - sval = SOCKET_READABLE(conn->sock[FIRSTSOCKET], 0); + sval = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); if(sval == 0) { /* timeout */ dead = FALSE; @@ -190,177 +389,57 @@ static bool http2_connisdead(struct Curl_easy *data, struct connectdata *conn) dead = TRUE; } else if(sval & CURL_CSELECT_IN) { - /* readable with no error. could still be closed */ - dead = !Curl_connalive(data, conn); - if(!dead) { - /* 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" */ - CURLcode result; - struct http_conn *httpc = &conn->proto.httpc; - ssize_t nread = -1; - if(httpc->recv_underlying) - /* if called "too early", this pointer isn't setup yet! */ - nread = ((Curl_recv *)httpc->recv_underlying)( - data, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, &result); - if(nread != -1) { - H2BUGF(infof(data, - "%d bytes stray data read before trying h2 connection", - (int)nread)); - httpc->nread_inbuf = 0; - httpc->inbuflen = nread; - if(h2_process_pending_input(data, httpc, &result) < 0) - /* immediate error, considered dead */ - dead = TRUE; - } - else - /* the read failed so let's say this is dead anyway */ + /* 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" */ + CURLcode result; + ssize_t nread = -1; + + Curl_attach_connection(data, cf->conn); + nread = Curl_conn_cf_recv(cf->next, data, + ctx->inbuf, H2_BUFSIZE, &result); + dead = FALSE; + if(nread != -1) { + DEBUGF(LOG_CF(data, cf, "%d bytes stray data read before trying " + "h2 connection", (int)nread)); + ctx->nread_inbuf = 0; + ctx->inbuflen = nread; + if(h2_process_pending_input(cf, data, &result) < 0) + /* immediate error, considered dead */ dead = TRUE; } + else + /* the read failed so let's say this is dead anyway */ + dead = TRUE; + Curl_detach_connection(data); } return dead; } -/* - * Set the transfer that is currently using this HTTP/2 connection. - */ -static void set_transfer(struct http_conn *c, - struct Curl_easy *data) -{ - c->trnsfr = data; -} - -/* - * Get the transfer that is currently using this HTTP/2 connection. - */ -static struct Curl_easy *get_transfer(struct http_conn *c) -{ - DEBUGASSERT(c && c->trnsfr); - return c->trnsfr; -} - -static unsigned int http2_conncheck(struct Curl_easy *data, - struct connectdata *conn, - unsigned int checks_to_perform) +static CURLcode http2_send_ping(struct Curl_cfilter *cf, + struct Curl_easy *data) { - unsigned int ret_val = CONNRESULT_NONE; - struct http_conn *c = &conn->proto.httpc; + struct cf_h2_ctx *ctx = cf->ctx; int rc; - bool send_frames = false; - if(checks_to_perform & CONNCHECK_ISDEAD) { - if(http2_connisdead(data, conn)) - ret_val |= CONNRESULT_DEAD; + rc = nghttp2_submit_ping(ctx->h2, 0, ZERO_NULL); + if(rc) { + failf(data, "nghttp2_submit_ping() failed: %s(%d)", + nghttp2_strerror(rc), rc); + return CURLE_HTTP2; } - if(checks_to_perform & CONNCHECK_KEEPALIVE) { - struct curltime now = Curl_now(); - timediff_t elapsed = Curl_timediff(now, conn->keepalive); - - if(elapsed > data->set.upkeep_interval_ms) { - /* Perform an HTTP/2 PING */ - rc = nghttp2_submit_ping(c->h2, 0, ZERO_NULL); - if(!rc) { - /* Successfully added a PING frame to the session. Need to flag this - so the frame is sent. */ - send_frames = true; - } - else { - failf(data, "nghttp2_submit_ping() failed: %s(%d)", - nghttp2_strerror(rc), rc); - } - - conn->keepalive = now; - } + rc = nghttp2_session_send(ctx->h2); + if(rc) { + failf(data, "nghttp2_session_send() failed: %s(%d)", + nghttp2_strerror(rc), rc); + return CURLE_SEND_ERROR; } - - if(send_frames) { - set_transfer(c, data); /* set the transfer */ - rc = nghttp2_session_send(c->h2); - if(rc) - failf(data, "nghttp2_session_send() failed: %s(%d)", - nghttp2_strerror(rc), rc); - } - - return ret_val; -} - -/* called from http_setup_conn */ -void Curl_http2_setup_req(struct Curl_easy *data) -{ - struct HTTP *http = data->req.p.http; - http->bodystarted = FALSE; - http->status_code = -1; - http->pausedata = NULL; - http->pauselen = 0; - http->closed = FALSE; - http->close_handled = FALSE; - http->mem = NULL; - http->len = 0; - http->memlen = 0; - http->error = NGHTTP2_NO_ERROR; -} - -/* called from http_setup_conn */ -void Curl_http2_setup_conn(struct connectdata *conn) -{ - conn->proto.httpc.settings.max_concurrent_streams = - DEFAULT_MAX_CONCURRENT_STREAMS; + return CURLE_OK; } /* - * HTTP2 handler interface. This isn't added to the general list of protocols - * but will be used at run-time when the protocol is dynamically switched from - * HTTP to HTTP2. - */ -static const struct Curl_handler Curl_handler_http2 = { - "HTTP", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - http2_getsock, /* proto_getsock */ - http2_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - http2_getsock, /* perform_getsock */ - http2_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - http2_conncheck, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_STREAM /* flags */ -}; - -static const struct Curl_handler Curl_handler_http2_ssl = { - "HTTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - http2_getsock, /* proto_getsock */ - http2_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - http2_getsock, /* perform_getsock */ - http2_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - http2_conncheck, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTPS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_STREAM /* flags */ -}; - -/* * Store nghttp2 version info in this buffer. */ void Curl_http2_ver(char *p, size_t len) @@ -369,31 +448,75 @@ void Curl_http2_ver(char *p, size_t len) (void)msnprintf(p, len, "nghttp2/%s", h2->version_str); } +static CURLcode flush_output(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + size_t buflen = Curl_dyn_len(&ctx->outbuf); + ssize_t written; + CURLcode result; + + if(!buflen) + return CURLE_OK; + + DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes", buflen)); + written = Curl_conn_cf_send(cf->next, data, Curl_dyn_ptr(&ctx->outbuf), + buflen, &result); + if(written < 0) { + return result; + } + if((size_t)written < buflen) { + Curl_dyn_tail(&ctx->outbuf, buflen - (size_t)written); + return CURLE_AGAIN; + } + else { + Curl_dyn_reset(&ctx->outbuf); + } + return CURLE_OK; +} + /* * The implementation of nghttp2_send_callback type. Here we write |data| with * size |length| to the network and return the number of bytes actually * written. See the documentation of nghttp2_send_callback for the details. */ static ssize_t send_callback(nghttp2_session *h2, - const uint8_t *mem, size_t length, int flags, + const uint8_t *buf, size_t blen, int flags, void *userp) { - struct connectdata *conn = (struct connectdata *)userp; - struct http_conn *c = &conn->proto.httpc; - struct Curl_easy *data = get_transfer(c); + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t written; CURLcode result = CURLE_OK; + size_t buflen = Curl_dyn_len(&ctx->outbuf); (void)h2; (void)flags; + DEBUGASSERT(data); - if(!c->send_underlying) - /* called before setup properly! */ - return NGHTTP2_ERR_CALLBACK_FAILURE; - - written = ((Curl_send*)c->send_underlying)(data, FIRSTSOCKET, - mem, length, &result); + if(blen < 1024 && (buflen + blen + 1 < ctx->outbuf.toobig)) { + result = Curl_dyn_addn(&ctx->outbuf, buf, blen); + if(result) { + failf(data, "Failed to add data to output buffer"); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return blen; + } + if(buflen) { + /* not adding, flush buffer */ + result = flush_output(cf, data); + if(result) { + if(result == CURLE_AGAIN) { + return NGHTTP2_ERR_WOULDBLOCK; + } + failf(data, "Failed sending HTTP2 data"); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + DEBUGF(LOG_CF(data, cf, "h2 conn send %zu bytes", blen)); + written = Curl_conn_cf_send(cf->next, data, buf, blen, &result); if(result == CURLE_AGAIN) { return NGHTTP2_ERR_WOULDBLOCK; } @@ -467,26 +590,33 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) /* * This specific transfer on this connection has been "drained". */ -static void drained_transfer(struct Curl_easy *data, - struct http_conn *httpc) +static void drained_transfer(struct Curl_cfilter *cf, + struct Curl_easy *data) { - DEBUGASSERT(httpc->drain_total >= data->state.drain); - httpc->drain_total -= data->state.drain; - data->state.drain = 0; + if(data->state.drain) { + struct cf_h2_ctx *ctx = cf->ctx; + DEBUGASSERT(ctx->drain_total > 0); + ctx->drain_total--; + data->state.drain = 0; + } } /* * Mark this transfer to get "drained". */ -static void drain_this(struct Curl_easy *data, - struct http_conn *httpc) +static void drain_this(struct Curl_cfilter *cf, + struct Curl_easy *data) { - data->state.drain++; - httpc->drain_total++; - DEBUGASSERT(httpc->drain_total >= data->state.drain); + if(!data->state.drain) { + struct cf_h2_ctx *ctx = cf->ctx; + data->state.drain = 1; + ctx->drain_total++; + DEBUGASSERT(ctx->drain_total > 0); + } } -static struct Curl_easy *duphandle(struct Curl_easy *data) +static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, + struct Curl_easy *data) { struct Curl_easy *second = curl_easy_duphandle(data); if(second) { @@ -497,9 +627,8 @@ static struct Curl_easy *duphandle(struct Curl_easy *data) } else { second->req.p.http = http; - Curl_dyn_init(&http->header_recvbuf, DYN_H2_HEADERS); - Curl_http2_setup_req(second); - second->state.stream_weight = data->state.stream_weight; + http2_data_setup(cf, second); + second->state.priority.weight = data->state.priority.weight; } } return second; @@ -559,22 +688,23 @@ static int set_transfer_url(struct Curl_easy *data, return 0; } -static int push_promise(struct Curl_easy *data, - struct connectdata *conn, +static int push_promise(struct Curl_cfilter *cf, + struct Curl_easy *data, const nghttp2_push_promise *frame) { + struct cf_h2_ctx *ctx = cf->ctx; int rv; /* one of the CURL_PUSH_* defines */ - H2BUGF(infof(data, "PUSH_PROMISE received, stream %u", - frame->promised_stream_id)); + + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] PUSH_PROMISE received", + frame->promised_stream_id)); if(data->multi->push_cb) { struct HTTP *stream; struct HTTP *newstream; struct curl_pushheaders heads; CURLMcode rc; - struct http_conn *httpc; size_t i; /* clone the parent */ - struct Curl_easy *newhandle = duphandle(data); + struct Curl_easy *newhandle = h2_duphandle(cf, data); if(!newhandle) { infof(data, "failed to duplicate handle"); rv = CURL_PUSH_DENY; /* FAIL HARD */ @@ -584,7 +714,7 @@ static int push_promise(struct Curl_easy *data, heads.data = data; heads.frame = frame; /* ask the application */ - H2BUGF(infof(data, "Got PUSH_PROMISE, ask application")); + DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ask application")); stream = data->req.p.http; if(!stream) { @@ -630,7 +760,7 @@ static int push_promise(struct Curl_easy *data, /* approved, add to the multi handle and immediately switch to PERFORM state with the given connection !*/ - rc = Curl_multi_add_perform(data->multi, newhandle, conn); + rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn); if(rc) { infof(data, "failed to add handle to multi"); http2_stream_free(newhandle->req.p.http); @@ -640,8 +770,7 @@ static int push_promise(struct Curl_easy *data, goto fail; } - httpc = &conn->proto.httpc; - rv = nghttp2_session_set_stream_user_data(httpc->h2, + rv = nghttp2_session_set_stream_user_data(ctx->h2, frame->promised_stream_id, newhandle); if(rv) { @@ -655,84 +784,82 @@ static int push_promise(struct Curl_easy *data, Curl_dyn_init(&newstream->trailer_recvbuf, DYN_H2_TRAILERS); } else { - H2BUGF(infof(data, "Got PUSH_PROMISE, ignore it")); + DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ignore it")); rv = CURL_PUSH_DENY; } fail: return rv; } -/* - * multi_connchanged() is called to tell that there is a connection in - * this multi handle that has changed state (multiplexing become possible, the - * number of allowed streams changed or similar), and a subsequent use of this - * multi handle should move CONNECT_PEND handles back to CONNECT to have them - * retry. - */ -static void multi_connchanged(struct Curl_multi *multi) -{ - multi->recheckstate = TRUE; -} - static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { - struct connectdata *conn = (struct connectdata *)userp; - struct http_conn *httpc = &conn->proto.httpc; + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s = NULL; struct HTTP *stream = NULL; - struct Curl_easy *data = get_transfer(httpc); + struct Curl_easy *data = CF_DATA_CURRENT(cf); int rv; size_t left, ncopy; int32_t stream_id = frame->hd.stream_id; CURLcode result; + DEBUGASSERT(data); if(!stream_id) { /* stream ID zero is for connection-oriented stuff */ - if(frame->hd.type == NGHTTP2_SETTINGS) { - uint32_t max_conn = httpc->settings.max_concurrent_streams; - H2BUGF(infof(data, "Got SETTINGS")); - httpc->settings.max_concurrent_streams = - nghttp2_session_get_remote_settings( + DEBUGASSERT(data); + switch(frame->hd.type) { + case NGHTTP2_SETTINGS: { + uint32_t max_conn = ctx->max_concurrent_streams; + DEBUGF(LOG_CF(data, cf, "recv frame SETTINGS")); + ctx->max_concurrent_streams = nghttp2_session_get_remote_settings( session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); - httpc->settings.enable_push = - nghttp2_session_get_remote_settings( + ctx->enable_push = nghttp2_session_get_remote_settings( session, NGHTTP2_SETTINGS_ENABLE_PUSH); - H2BUGF(infof(data, "MAX_CONCURRENT_STREAMS == %d", - httpc->settings.max_concurrent_streams)); - H2BUGF(infof(data, "ENABLE_PUSH == %s", - httpc->settings.enable_push?"TRUE":"false")); - if(max_conn != httpc->settings.max_concurrent_streams) { + DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS == %d", + ctx->max_concurrent_streams)); + DEBUGF(LOG_CF(data, cf, "ENABLE_PUSH == %s", + ctx->enable_push ? "TRUE" : "false")); + if(data && max_conn != ctx->max_concurrent_streams) { /* only signal change if the value actually changed */ - infof(data, - "Connection state changed (MAX_CONCURRENT_STREAMS == %u)!", - httpc->settings.max_concurrent_streams); + DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS now %u", + ctx->max_concurrent_streams)); multi_connchanged(data->multi); } + break; + } + case NGHTTP2_GOAWAY: + if(data) { + infof(data, "recveived GOAWAY, error=%d, last_stream=%u", + frame->goaway.error_code, frame->goaway.last_stream_id); + multi_connchanged(data->multi); + } + break; + case NGHTTP2_WINDOW_UPDATE: + DEBUGF(LOG_CF(data, cf, "recv frame WINDOW_UPDATE")); + break; + default: + DEBUGF(LOG_CF(data, cf, "recv frame %x on 0", frame->hd.type)); } return 0; } data_s = nghttp2_session_get_stream_user_data(session, stream_id); if(!data_s) { - H2BUGF(infof(data, - "No Curl_easy associated with stream: %u", - stream_id)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] No Curl_easy associated", + stream_id)); return 0; } stream = data_s->req.p.http; if(!stream) { - H2BUGF(infof(data_s, "No proto pointer for stream: %u", - stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] No proto pointer", stream_id)); return NGHTTP2_ERR_CALLBACK_FAILURE; } - H2BUGF(infof(data_s, "on_frame_recv() header %x stream %u", - frame->hd.type, stream_id)); - switch(frame->hd.type) { case NGHTTP2_DATA: /* If body started on this stream, then receiving DATA is illegal. */ + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame DATA", stream_id)); if(!stream->bodystarted) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_PROTOCOL_ERROR); @@ -741,8 +868,17 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, return NGHTTP2_ERR_CALLBACK_FAILURE; } } + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + /* Stream has ended. If there is pending data, ensure that read + will occur to consume it. */ + if(!data->state.drain && stream->memlen) { + drain_this(cf, data_s); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + } break; case NGHTTP2_HEADERS: + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame HEADERS", stream_id)); if(stream->bodystarted) { /* Only valid HEADERS after body started is trailer HEADERS. We buffer them in on_header callback. */ @@ -776,19 +912,18 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, stream->nread_header_recvbuf += ncopy; DEBUGASSERT(stream->mem); - H2BUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p", - ncopy, stream_id, stream->mem)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu header bytes, at %p", + stream_id, ncopy, (void *)stream->mem)); stream->len -= ncopy; stream->memlen += ncopy; - drain_this(data_s, httpc); - /* if we receive data for another handle, wake that up */ - if(get_transfer(httpc) != data_s) - Curl_expire(data_s, 0, EXPIRE_RUN_NOW); + drain_this(cf, data_s); + Curl_expire(data_s, 0, EXPIRE_RUN_NOW); break; case NGHTTP2_PUSH_PROMISE: - rv = push_promise(data_s, conn, &frame->push_promise); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv PUSH_PROMISE", stream_id)); + rv = push_promise(cf, data_s, &frame->push_promise); if(rv) { /* deny! */ int h2; DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); @@ -798,14 +933,18 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, if(nghttp2_is_fatal(h2)) return NGHTTP2_ERR_CALLBACK_FAILURE; else if(rv == CURL_PUSH_ERROROUT) { - DEBUGF(infof(data_s, "Fail the parent stream (too)")); + DEBUGF(LOG_CF(data_s, cf, "Fail the parent stream (too)")); return NGHTTP2_ERR_CALLBACK_FAILURE; } } break; + case NGHTTP2_RST_STREAM: + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv RST", stream_id)); + stream->reset = TRUE; + break; default: - H2BUGF(infof(data_s, "Got frame type %x for stream %u", - frame->hd.type, stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame %x", + stream_id, frame->hd.type)); break; } return 0; @@ -815,15 +954,15 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *mem, size_t len, void *userp) { + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct HTTP *stream; struct Curl_easy *data_s; size_t nread; - struct connectdata *conn = (struct connectdata *)userp; - struct http_conn *httpc = &conn->proto.httpc; - (void)session; (void)flags; DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ + DEBUGASSERT(CF_DATA_CURRENT(cf)); /* get the stream from the hash based on Stream ID */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); @@ -831,8 +970,8 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, /* Receiving a Stream ID not in the hash should not happen - unless we have aborted a transfer artificially and there were more data in the pipeline. Silently ignore. */ - H2BUGF(fprintf(stderr, "Data for stream %u but it doesn't exist\n", - stream_id)); + DEBUGF(LOG_CF(CF_DATA_CURRENT(cf), cf, "[h2sid=%u] Data for unknown", + stream_id)); return 0; } @@ -846,36 +985,38 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, stream->len -= nread; stream->memlen += nread; - drain_this(data_s, &conn->proto.httpc); - /* if we receive data for another handle, wake that up */ - if(get_transfer(httpc) != data_s) + if(CF_DATA_CURRENT(cf) != data_s) { + drain_this(cf, data_s); Curl_expire(data_s, 0, EXPIRE_RUN_NOW); + } - H2BUGF(infof(data_s, "%zu data received for stream %u " - "(%zu left in buffer %p, total %zu)", - nread, stream_id, - stream->len, stream->mem, - stream->memlen)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu DATA recvd, " + "(buffer now holds %zu, %zu still free in %p)", + stream_id, nread, + stream->memlen, stream->len, (void *)stream->mem)); if(nread < len) { stream->pausedata = mem + nread; stream->pauselen = len - nread; - H2BUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - %zu bytes out of buffer" - ", stream %u", - len - nread, stream_id)); - data_s->conn->proto.httpc.pause_stream_id = stream_id; - + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu not recvd -> NGHTTP2_ERR_PAUSE", + stream_id, len - nread)); + ctx->pause_stream_id = stream_id; + drain_this(cf, data_s); return NGHTTP2_ERR_PAUSE; } +#if 0 /* pause execution of nghttp2 if we received data for another handle in order to process them first. */ - if(get_transfer(httpc) != data_s) { - data_s->conn->proto.httpc.pause_stream_id = stream_id; - + if(CF_DATA_CURRENT(cf) != data_s) { + ctx->pause_stream_id = stream_id; + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] not call_data -> NGHTTP2_ERR_PAUSE", + stream_id)); + drain_this(cf, data_s); return NGHTTP2_ERR_PAUSE; } +#endif return 0; } @@ -883,15 +1024,15 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, static int on_stream_close(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *userp) { + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s; struct HTTP *stream; - struct connectdata *conn = (struct connectdata *)userp; int rv; (void)session; (void)stream_id; if(stream_id) { - struct http_conn *httpc; /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ data_s = nghttp2_session_get_stream_user_data(session, stream_id); @@ -900,16 +1041,17 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, decided to reject stream (e.g., PUSH_PROMISE). */ return 0; } - H2BUGF(infof(data_s, "on_stream_close(), %s (err %d), stream %u", - nghttp2_http2_strerror(error_code), error_code, stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] on_stream_close(), %s (err %d)", + stream_id, nghttp2_http2_strerror(error_code), error_code)); stream = data_s->req.p.http; if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; stream->closed = TRUE; - httpc = &conn->proto.httpc; - drain_this(data_s, httpc); - Curl_expire(data_s, 0, EXPIRE_RUN_NOW); + if(CF_DATA_CURRENT(cf) != data_s) { + drain_this(cf, data_s); + Curl_expire(data_s, 0, EXPIRE_RUN_NOW); + } stream->error = error_code; /* remove the entry from the hash as the stream is now gone */ @@ -919,12 +1061,12 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, stream_id); DEBUGASSERT(0); } - if(stream_id == httpc->pause_stream_id) { - H2BUGF(infof(data_s, "Stopped the pause stream")); - httpc->pause_stream_id = 0; + if(stream_id == ctx->pause_stream_id) { + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed the pause stream", + stream_id)); + ctx->pause_stream_id = 0; } - H2BUGF(infof(data_s, "Removed stream %u hash", stream_id)); - stream->stream_id = 0; /* cleared */ + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed, cleared", stream_id)); } return 0; } @@ -932,16 +1074,17 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, static int on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { + struct Curl_cfilter *cf = userp; struct HTTP *stream; struct Curl_easy *data_s = NULL; - (void)userp; + (void)cf; data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if(!data_s) { return 0; } - H2BUGF(infof(data_s, "on_begin_headers() was called")); + DEBUGF(LOG_CF(data_s, cf, "on_begin_headers() was called")); if(frame->hd.type != NGHTTP2_HEADERS) { return 0; @@ -989,11 +1132,10 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, uint8_t flags, void *userp) { + struct Curl_cfilter *cf = userp; struct HTTP *stream; struct Curl_easy *data_s; int32_t stream_id = frame->hd.stream_id; - struct connectdata *conn = (struct connectdata *)userp; - struct http_conn *httpc = &conn->proto.httpc; CURLcode result; (void)flags; @@ -1020,13 +1162,14 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(!strcmp(H2H3_PSEUDO_AUTHORITY, (const char *)name)) { /* pseudo headers are lower case */ int rc = 0; - char *check = aprintf("%s:%d", conn->host.name, conn->remote_port); + char *check = aprintf("%s:%d", cf->conn->host.name, + cf->conn->remote_port); if(!check) /* no memory */ return NGHTTP2_ERR_CALLBACK_FAILURE; if(!strcasecompare(check, (const char *)value) && - ((conn->remote_port != conn->given->defport) || - !strcasecompare(conn->host.name, (const char *)value))) { + ((cf->conn->remote_port != cf->conn->given->defport) || + !strcasecompare(cf->conn->host.name, (const char *)value))) { /* This is push is not for the same authority that was asked for in * the URL. RFC 7540 section 8.2 says: "A client MUST treat a * PUSH_PROMISE for which the server is not authoritative as a stream @@ -1075,11 +1218,13 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(stream->bodystarted) { /* This is a trailer */ - H2BUGF(infof(data_s, "h2 trailer: %.*s: %.*s", namelen, name, valuelen, - value)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] trailer: %.*s: %.*s", + stream->stream_id, + (int)namelen, name, + (int)valuelen, value)); result = Curl_dyn_addf(&stream->trailer_recvbuf, - "%.*s: %.*s\r\n", namelen, name, - valuelen, value); + "%.*s: %.*s\r\n", (int)namelen, name, + (int)valuelen, value); if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1110,11 +1255,11 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ - if(get_transfer(httpc) != data_s) + if(CF_DATA_CURRENT(cf) != data_s) Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - H2BUGF(infof(data_s, "h2 status: HTTP/2 %03d (easy %p)", - stream->status_code, data_s)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] status: HTTP/2 %03d", + stream->stream_id, stream->status_code)); return 0; } @@ -1134,11 +1279,13 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ - if(get_transfer(httpc) != data_s) + if(CF_DATA_CURRENT(cf) != data_s) Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - H2BUGF(infof(data_s, "h2 header: %.*s: %.*s", namelen, name, valuelen, - value)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] header: %.*s: %.*s", + stream->stream_id, + (int)namelen, name, + (int)valuelen, value)); return 0; /* 0 is successful */ } @@ -1150,12 +1297,13 @@ static ssize_t data_source_read_callback(nghttp2_session *session, nghttp2_data_source *source, void *userp) { + struct Curl_cfilter *cf = userp; struct Curl_easy *data_s; struct HTTP *stream = NULL; size_t nread; (void)source; - (void)userp; + (void)cf; if(stream_id) { /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ @@ -1186,9 +1334,8 @@ static ssize_t data_source_read_callback(nghttp2_session *session, else if(nread == 0) return NGHTTP2_ERR_DEFERRED; - H2BUGF(infof(data_s, "data_source_read_callback: " - "returns %zu bytes stream %u", - nread, stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] data_source_read_callback: " + "returns %zu bytes", stream_id, nread)); return nread; } @@ -1207,149 +1354,58 @@ static int error_callback(nghttp2_session *session, } #endif -static void populate_settings(struct Curl_easy *data, - struct http_conn *httpc) +static void http2_data_done(struct Curl_cfilter *cf, + struct Curl_easy *data, bool premature) { - nghttp2_settings_entry *iv = httpc->local_settings; - - iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; - iv[0].value = Curl_multi_max_concurrent_streams(data->multi); - - iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; - iv[1].value = HTTP2_HUGE_WINDOW_SIZE; - - iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; - iv[2].value = data->multi->push_cb != NULL; - - httpc->local_settings_num = 3; -} - -void Curl_http2_done(struct Curl_easy *data, bool premature) -{ - struct HTTP *http = data->req.p.http; - struct http_conn *httpc = &data->conn->proto.httpc; + struct cf_h2_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; /* there might be allocated resources done before this got the 'h2' pointer setup */ - Curl_dyn_free(&http->header_recvbuf); - Curl_dyn_free(&http->trailer_recvbuf); - if(http->push_headers) { + Curl_dyn_free(&stream->header_recvbuf); + Curl_dyn_free(&stream->trailer_recvbuf); + if(stream->push_headers) { /* if they weren't used and then freed before */ - for(; http->push_headers_used > 0; --http->push_headers_used) { - free(http->push_headers[http->push_headers_used - 1]); + for(; stream->push_headers_used > 0; --stream->push_headers_used) { + free(stream->push_headers[stream->push_headers_used - 1]); } - free(http->push_headers); - http->push_headers = NULL; + free(stream->push_headers); + stream->push_headers = NULL; } - if(!(data->conn->handler->protocol&PROTO_FAMILY_HTTP) || - !httpc->h2) /* not HTTP/2 ? */ + if(!ctx || !ctx->h2) return; /* do this before the reset handling, as that might clear ->stream_id */ - if(http->stream_id == httpc->pause_stream_id) { - H2BUGF(infof(data, "DONE the pause stream (%u)", http->stream_id)); - httpc->pause_stream_id = 0; + if(stream->stream_id && stream->stream_id == ctx->pause_stream_id) { + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] DONE, the pause stream", + stream->stream_id)); + ctx->pause_stream_id = 0; } - if(premature || (!http->closed && http->stream_id)) { + + if(premature || (!stream->closed && stream->stream_id)) { /* RST_STREAM */ - set_transfer(httpc, data); /* set the transfer */ - H2BUGF(infof(data, "RST stream %u", http->stream_id)); - if(!nghttp2_submit_rst_stream(httpc->h2, NGHTTP2_FLAG_NONE, - http->stream_id, NGHTTP2_STREAM_CLOSED)) - (void)nghttp2_session_send(httpc->h2); + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] RST", stream->stream_id)); + if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->stream_id, NGHTTP2_STREAM_CLOSED)) + (void)nghttp2_session_send(ctx->h2); } if(data->state.drain) - drained_transfer(data, httpc); + drained_transfer(cf, data); /* -1 means unassigned and 0 means cleared */ - if(http->stream_id > 0) { - int rv = nghttp2_session_set_stream_user_data(httpc->h2, - http->stream_id, 0); + if(nghttp2_session_get_stream_user_data(ctx->h2, stream->stream_id)) { + int rv = nghttp2_session_set_stream_user_data(ctx->h2, + stream->stream_id, 0); if(rv) { infof(data, "http/2: failed to clear user_data for stream %u", - http->stream_id); + stream->stream_id); DEBUGASSERT(0); } - set_transfer(httpc, NULL); - http->stream_id = 0; } } -static int client_new(struct connectdata *conn, - nghttp2_session_callbacks *callbacks) -{ -#if NGHTTP2_VERSION_NUM < 0x013200 - /* before 1.50.0 */ - return nghttp2_session_client_new(&conn->proto.httpc.h2, callbacks, conn); -#else - nghttp2_option *o; - int rc = nghttp2_option_new(&o); - if(rc) - return rc; - /* turn off RFC 9113 leading and trailing white spaces validation against - HTTP field value. */ - nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); - rc = nghttp2_session_client_new2(&conn->proto.httpc.h2, callbacks, conn, - o); - nghttp2_option_del(o); - return rc; -#endif -} - -/* - * Initialize nghttp2 for a Curl connection - */ -static CURLcode http2_init(struct Curl_easy *data, struct connectdata *conn) -{ - if(!conn->proto.httpc.h2) { - int rc; - nghttp2_session_callbacks *callbacks; - - conn->proto.httpc.inbuf = malloc(H2_BUFSIZE); - if(!conn->proto.httpc.inbuf) - return CURLE_OUT_OF_MEMORY; - - rc = nghttp2_session_callbacks_new(&callbacks); - - if(rc) { - failf(data, "Couldn't initialize nghttp2 callbacks"); - return CURLE_OUT_OF_MEMORY; /* most likely at least */ - } - - /* nghttp2_send_callback */ - nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); - /* nghttp2_on_frame_recv_callback */ - nghttp2_session_callbacks_set_on_frame_recv_callback - (callbacks, on_frame_recv); - /* nghttp2_on_data_chunk_recv_callback */ - nghttp2_session_callbacks_set_on_data_chunk_recv_callback - (callbacks, on_data_chunk_recv); - /* nghttp2_on_stream_close_callback */ - nghttp2_session_callbacks_set_on_stream_close_callback - (callbacks, on_stream_close); - /* nghttp2_on_begin_headers_callback */ - nghttp2_session_callbacks_set_on_begin_headers_callback - (callbacks, on_begin_headers); - /* nghttp2_on_header_callback */ - nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header); - - nghttp2_session_callbacks_set_error_callback(callbacks, error_callback); - - /* The nghttp2 session is not yet setup, do it */ - rc = client_new(conn, callbacks); - - nghttp2_session_callbacks_del(callbacks); - - if(rc) { - failf(data, "Couldn't initialize nghttp2"); - return CURLE_OUT_OF_MEMORY; /* most likely at least */ - } - } - return CURLE_OK; -} - /* * Append headers to ask for an HTTP1.1 to HTTP2 upgrade. */ @@ -1357,26 +1413,18 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, struct Curl_easy *data) { CURLcode result; - ssize_t binlen; char *base64; size_t blen; - struct connectdata *conn = data->conn; struct SingleRequest *k = &data->req; - uint8_t *binsettings = conn->proto.httpc.binsettings; - struct http_conn *httpc = &conn->proto.httpc; - - populate_settings(data, httpc); + uint8_t binsettings[H2_BINSETTINGS_LEN]; + size_t binlen; /* length of the binsettings data */ - /* this returns number of bytes it wrote */ - binlen = nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN, - httpc->local_settings, - httpc->local_settings_num); + binlen = populate_binsettings(binsettings, data); if(binlen <= 0) { failf(data, "nghttp2 unexpectedly failed on pack_settings_payload"); Curl_dyn_free(req); return CURLE_FAILED_INIT; } - conn->proto.httpc.binlen = binlen; result = Curl_base64url_encode((const char *)binsettings, binlen, &base64, &blen); @@ -1400,10 +1448,10 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, /* * Returns nonzero if current HTTP/2 session should be closed. */ -static int should_close_session(struct http_conn *httpc) +static int should_close_session(struct cf_h2_ctx *ctx) { - return httpc->drain_total == 0 && !nghttp2_session_want_read(httpc->h2) && - !nghttp2_session_want_write(httpc->h2); + return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) && + !nghttp2_session_want_write(ctx->h2); } /* @@ -1412,65 +1460,65 @@ static int should_close_session(struct http_conn *httpc) * This function returns 0 if it succeeds, or -1 and error code will * be assigned to *err. */ -static int h2_process_pending_input(struct Curl_easy *data, - struct http_conn *httpc, +static int h2_process_pending_input(struct Curl_cfilter *cf, + struct Curl_easy *data, CURLcode *err) { + struct cf_h2_ctx *ctx = cf->ctx; ssize_t nread; - char *inbuf; ssize_t rv; - nread = httpc->inbuflen - httpc->nread_inbuf; - inbuf = httpc->inbuf + httpc->nread_inbuf; + nread = ctx->inbuflen - ctx->nread_inbuf; + if(nread) { + char *inbuf = ctx->inbuf + ctx->nread_inbuf; - set_transfer(httpc, data); /* set the transfer */ - rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread); - if(rv < 0) { - failf(data, - "h2_process_pending_input: nghttp2_session_mem_recv() returned " - "%zd:%s", rv, nghttp2_strerror((int)rv)); - *err = CURLE_RECV_ERROR; - return -1; - } + rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)inbuf, nread); + if(rv < 0) { + failf(data, + "h2_process_pending_input: nghttp2_session_mem_recv() returned " + "%zd:%s", rv, nghttp2_strerror((int)rv)); + *err = CURLE_RECV_ERROR; + return -1; + } - if(nread == rv) { - H2BUGF(infof(data, - "h2_process_pending_input: All data in connection buffer " - "processed")); - httpc->inbuflen = 0; - httpc->nread_inbuf = 0; - } - else { - httpc->nread_inbuf += rv; - H2BUGF(infof(data, - "h2_process_pending_input: %zu bytes left in connection " - "buffer", - httpc->inbuflen - httpc->nread_inbuf)); + if(nread == rv) { + DEBUGF(LOG_CF(data, cf, "all data in connection buffer processed")); + ctx->inbuflen = 0; + ctx->nread_inbuf = 0; + } + else { + ctx->nread_inbuf += rv; + DEBUGF(LOG_CF(data, cf, "h2_process_pending_input: %zu bytes left " + "in connection buffer", + ctx->inbuflen - ctx->nread_inbuf)); + } } - rv = h2_session_send(data, httpc->h2); + rv = h2_session_send(cf, data); if(rv) { *err = CURLE_SEND_ERROR; return -1; } - if(nghttp2_session_check_request_allowed(httpc->h2) == 0) { + if(nghttp2_session_check_request_allowed(ctx->h2) == 0) { /* No more requests are allowed in the current session, so the connection may not be reused. This is set when a GOAWAY frame has been received or when the limit of stream identifiers has been reached. */ - connclose(data->conn, "http/2: No new requests allowed"); + connclose(cf->conn, "http/2: No new requests allowed"); } - if(should_close_session(httpc)) { + if(should_close_session(ctx)) { struct HTTP *stream = data->req.p.http; - H2BUGF(infof(data, + DEBUGF(LOG_CF(data, cf, "h2_process_pending_input: nothing to do in this session")); - if(stream->error) + if(stream->reset) + *err = CURLE_PARTIAL_FILE; + else if(stream->error) *err = CURLE_HTTP2; else { /* not an error per se, but should still close the connection */ - connclose(data->conn, "GOAWAY received"); + connclose(cf->conn, "GOAWAY received"); *err = CURLE_OK; } return -1; @@ -1478,79 +1526,72 @@ static int h2_process_pending_input(struct Curl_easy *data, return 0; } -/* - * Called from transfer.c:done_sending when we stop uploading. - */ -CURLcode Curl_http2_done_sending(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode http2_data_done_send(struct Curl_cfilter *cf, + struct Curl_easy *data) { + struct cf_h2_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; + struct HTTP *stream = data->req.p.http; - if((conn->handler == &Curl_handler_http2_ssl) || - (conn->handler == &Curl_handler_http2)) { - /* make sure this is only attempted for HTTP/2 transfers */ - struct HTTP *stream = data->req.p.http; - struct http_conn *httpc = &conn->proto.httpc; - nghttp2_session *h2 = httpc->h2; - - if(stream->upload_left) { - /* If the stream still thinks there's data left to upload. */ + if(!ctx || !ctx->h2) + goto out; - stream->upload_left = 0; /* DONE! */ + if(stream->upload_left) { + /* If the stream still thinks there's data left to upload. */ + stream->upload_left = 0; /* DONE! */ - /* resume sending here to trigger the callback to get called again so - that it can signal EOF to nghttp2 */ - (void)nghttp2_session_resume_data(h2, stream->stream_id); - (void)h2_process_pending_input(data, httpc, &result); - } + /* resume sending here to trigger the callback to get called again so + that it can signal EOF to nghttp2 */ + (void)nghttp2_session_resume_data(ctx->h2, stream->stream_id); + (void)h2_process_pending_input(cf, data, &result); + } - /* If nghttp2 still has pending frames unsent */ - if(nghttp2_session_want_write(h2)) { - struct SingleRequest *k = &data->req; - int rv; + /* If nghttp2 still has pending frames unsent */ + if(nghttp2_session_want_write(ctx->h2)) { + struct SingleRequest *k = &data->req; + int rv; - H2BUGF(infof(data, "HTTP/2 still wants to send data (easy %p)", data)); + DEBUGF(LOG_CF(data, cf, "HTTP/2 still wants to send data")); - /* and attempt to send the pending frames */ - rv = h2_session_send(data, h2); - if(rv) - result = CURLE_SEND_ERROR; + /* and attempt to send the pending frames */ + rv = h2_session_send(cf, data); + if(rv) + result = CURLE_SEND_ERROR; - if(nghttp2_session_want_write(h2)) { - /* re-set KEEP_SEND to make sure we are called again */ - k->keepon |= KEEP_SEND; - } + if(nghttp2_session_want_write(ctx->h2)) { + /* re-set KEEP_SEND to make sure we are called again */ + k->keepon |= KEEP_SEND; } } + +out: return result; } -static ssize_t http2_handle_stream_close(struct connectdata *conn, +static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, struct Curl_easy *data, struct HTTP *stream, CURLcode *err) { - struct http_conn *httpc = &conn->proto.httpc; + struct cf_h2_ctx *ctx = cf->ctx; - if(httpc->pause_stream_id == stream->stream_id) { - httpc->pause_stream_id = 0; + if(ctx->pause_stream_id == stream->stream_id) { + ctx->pause_stream_id = 0; } - drained_transfer(data, httpc); + drained_transfer(cf, data); - if(httpc->pause_stream_id == 0) { - if(h2_process_pending_input(data, httpc, err) != 0) { + if(ctx->pause_stream_id == 0) { + if(h2_process_pending_input(cf, data, err) != 0) { return -1; } } - DEBUGASSERT(data->state.drain == 0); - /* Reset to FALSE to prevent infinite loop in readwrite_data function. */ stream->closed = FALSE; if(stream->error == NGHTTP2_REFUSED_STREAM) { - H2BUGF(infof(data, "REFUSED_STREAM (%u), try again on a new connection", - stream->stream_id)); - connclose(conn, "REFUSED_STREAM"); /* don't use this anymore */ + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] REFUSED_STREAM, try again on a new " + "connection", stream->stream_id)); + connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */ data->state.refused_stream = TRUE; *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ return -1; @@ -1597,10 +1638,24 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn, stream->close_handled = TRUE; - H2BUGF(infof(data, "http2_recv returns 0, http2_handle_stream_close")); + DEBUGF(LOG_CF(data, cf, "http2_recv returns 0, http2_handle_stream_close")); return 0; } +static int sweight_wanted(const struct Curl_easy *data) +{ + /* 0 weight is not set by user and we take the nghttp2 default one */ + return data->set.priority.weight? + data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT; +} + +static int sweight_in_effect(const struct Curl_easy *data) +{ + /* 0 weight is not set by user and we take the nghttp2 default one */ + return data->state.priority.weight? + data->state.priority.weight : NGHTTP2_DEFAULT_WEIGHT; +} + /* * h2_pri_spec() fills in the pri_spec struct, used by nghttp2 to send weight * and dependency to the peer. It also stores the updated values in the state @@ -1610,14 +1665,14 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn, static void h2_pri_spec(struct Curl_easy *data, nghttp2_priority_spec *pri_spec) { - struct HTTP *depstream = (data->set.stream_depends_on? - data->set.stream_depends_on->req.p.http:NULL); + struct Curl_data_priority *prio = &data->set.priority; + struct HTTP *depstream = (prio->parent? + prio->parent->req.p.http:NULL); int32_t depstream_id = depstream? depstream->stream_id:0; - nghttp2_priority_spec_init(pri_spec, depstream_id, data->set.stream_weight, - data->set.stream_depends_e); - data->state.stream_weight = data->set.stream_weight; - data->state.stream_depends_e = data->set.stream_depends_e; - data->state.stream_depends_on = data->set.stream_depends_on; + nghttp2_priority_spec_init(pri_spec, depstream_id, + sweight_wanted(data), + data->set.priority.exclusive); + data->state.priority = *prio; } /* @@ -1625,53 +1680,61 @@ static void h2_pri_spec(struct Curl_easy *data, * dependency settings and if so it submits a PRIORITY frame with the updated * info. */ -static int h2_session_send(struct Curl_easy *data, - nghttp2_session *h2) +static CURLcode h2_session_send(struct Curl_cfilter *cf, + struct Curl_easy *data) { + struct cf_h2_ctx *ctx = cf->ctx; struct HTTP *stream = data->req.p.http; - struct http_conn *httpc = &data->conn->proto.httpc; - set_transfer(httpc, data); - if((data->set.stream_weight != data->state.stream_weight) || - (data->set.stream_depends_e != data->state.stream_depends_e) || - (data->set.stream_depends_on != data->state.stream_depends_on) ) { + int rv = 0; + + if((sweight_wanted(data) != sweight_in_effect(data)) || + (data->set.priority.exclusive != data->state.priority.exclusive) || + (data->set.priority.parent != data->state.priority.parent) ) { /* send new weight and/or dependency */ nghttp2_priority_spec pri_spec; - int rv; h2_pri_spec(data, &pri_spec); - - H2BUGF(infof(data, "Queuing PRIORITY on stream %u (easy %p)", - stream->stream_id, data)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Queuing PRIORITY", + stream->stream_id)); DEBUGASSERT(stream->stream_id != -1); - rv = nghttp2_submit_priority(h2, NGHTTP2_FLAG_NONE, stream->stream_id, - &pri_spec); + rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, + stream->stream_id, &pri_spec); if(rv) - return rv; + goto out; } - return nghttp2_session_send(h2); + rv = nghttp2_session_send(ctx->h2); +out: + if(nghttp2_is_fatal(rv)) { + DEBUGF(LOG_CF(data, cf, "nghttp2_session_send error (%s)%d", + nghttp2_strerror(rv), rv)); + return CURLE_SEND_ERROR; + } + return flush_output(cf, data); } -static ssize_t http2_recv(struct Curl_easy *data, int sockindex, - char *mem, size_t len, CURLcode *err) +static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) { - ssize_t nread; - struct connectdata *conn = data->conn; - struct http_conn *httpc = &conn->proto.httpc; + struct cf_h2_ctx *ctx = cf->ctx; struct HTTP *stream = data->req.p.http; + ssize_t nread = -1; + struct cf_call_data save; - (void)sockindex; /* we always do HTTP2 on sockindex 0 */ + CF_DATA_SAVE(save, cf, data); - if(should_close_session(httpc)) { - H2BUGF(infof(data, - "http2_recv: nothing to do in this session")); - if(conn->bits.close) { + if(should_close_session(ctx)) { + DEBUGF(LOG_CF(data, cf, "http2_recv: nothing to do in this session")); + if(cf->conn->bits.close) { /* already marked for closure, return OK and we're done */ + drained_transfer(cf, data); *err = CURLE_OK; - return 0; + nread = 0; + goto out; } *err = CURLE_HTTP2; - return -1; + nread = -1; + goto out; } /* Nullify here because we call nghttp2_session_send() and they @@ -1690,74 +1753,67 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex, size_t left = Curl_dyn_len(&stream->header_recvbuf) - stream->nread_header_recvbuf; size_t ncopy = CURLMIN(len, left); - memcpy(mem, Curl_dyn_ptr(&stream->header_recvbuf) + + memcpy(buf, Curl_dyn_ptr(&stream->header_recvbuf) + stream->nread_header_recvbuf, ncopy); stream->nread_header_recvbuf += ncopy; - H2BUGF(infof(data, "http2_recv: Got %d bytes from header_recvbuf", - (int)ncopy)); - return ncopy; + DEBUGF(LOG_CF(data, cf, "recv: Got %d bytes from header_recvbuf", + (int)ncopy)); + nread = ncopy; + goto out; } - H2BUGF(infof(data, "http2_recv: easy %p (stream %u) win %u/%u", - data, stream->stream_id, - nghttp2_session_get_local_window_size(httpc->h2), - nghttp2_session_get_stream_local_window_size(httpc->h2, - stream->stream_id) + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: win %u/%u", + stream->stream_id, + nghttp2_session_get_local_window_size(ctx->h2), + nghttp2_session_get_stream_local_window_size(ctx->h2, + stream->stream_id) )); - if((data->state.drain) && stream->memlen) { - H2BUGF(infof(data, "http2_recv: DRAIN %zu bytes stream %u (%p => %p)", - stream->memlen, stream->stream_id, - stream->mem, mem)); - if(mem != stream->mem) { + if(stream->memlen) { + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: DRAIN %zu bytes (%p => %p)", + stream->stream_id, stream->memlen, + (void *)stream->mem, (void *)buf)); + if(buf != stream->mem) { /* if we didn't get the same buffer this time, we must move the data to the beginning */ - memmove(mem, stream->mem, stream->memlen); + memmove(buf, stream->mem, stream->memlen); stream->len = len - stream->memlen; - stream->mem = mem; + stream->mem = buf; } - if(httpc->pause_stream_id == stream->stream_id && !stream->pausedata) { + + if(ctx->pause_stream_id == stream->stream_id && !stream->pausedata) { /* We have paused nghttp2, but we have no pause data (see on_data_chunk_recv). */ - httpc->pause_stream_id = 0; - if(h2_process_pending_input(data, httpc, err) != 0) { - return -1; + ctx->pause_stream_id = 0; + if(h2_process_pending_input(cf, data, err) != 0) { + nread = -1; + goto out; } } } else if(stream->pausedata) { - DEBUGASSERT(httpc->pause_stream_id == stream->stream_id); + DEBUGASSERT(ctx->pause_stream_id == stream->stream_id); nread = CURLMIN(len, stream->pauselen); - memcpy(mem, stream->pausedata, nread); + memcpy(buf, stream->pausedata, nread); stream->pausedata += nread; stream->pauselen -= nread; + drain_this(cf, data); if(stream->pauselen == 0) { - H2BUGF(infof(data, "Unpaused by stream %u", stream->stream_id)); - DEBUGASSERT(httpc->pause_stream_id == stream->stream_id); - httpc->pause_stream_id = 0; + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Unpaused", stream->stream_id)); + DEBUGASSERT(ctx->pause_stream_id == stream->stream_id); + ctx->pause_stream_id = 0; stream->pausedata = NULL; stream->pauselen = 0; - - /* When NGHTTP2_ERR_PAUSE is returned from - data_source_read_callback, we might not process DATA frame - fully. Calling nghttp2_session_mem_recv() again will - continue to process DATA frame, but if there is no incoming - frames, then we have to call it again with 0-length data. - Without this, on_stream_close callback will not be called, - and stream could be hanged. */ - if(h2_process_pending_input(data, httpc, err) != 0) { - return -1; - } } - H2BUGF(infof(data, "http2_recv: returns unpaused %zd bytes on stream %u", - nread, stream->stream_id)); - return nread; + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: returns unpaused %zd bytes", + stream->stream_id, nread)); + goto out; } - else if(httpc->pause_stream_id) { + else if(ctx->pause_stream_id) { /* If a stream paused nghttp2_session_mem_recv previously, and has not processed all data, it still refers to the buffer in nghttp2_session. If we call nghttp2_session_mem_recv(), we may @@ -1766,36 +1822,56 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex, socket is not read. But it seems that usually streams are notified with its drain property, and socket is read again quickly. */ - if(stream->closed) + if(stream->closed) { /* closed overrides paused */ - return 0; - H2BUGF(infof(data, "stream %u is paused, pause id: %u", - stream->stream_id, httpc->pause_stream_id)); + drained_transfer(cf, data); + nread = 0; + goto out; + } + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] is paused, pause h2sid: %u", + stream->stream_id, ctx->pause_stream_id)); *err = CURLE_AGAIN; - return -1; + nread = -1; + goto out; } else { - /* remember where to store incoming data for this stream and how big the - buffer is */ - stream->mem = mem; + /* We have nothing buffered for `data` and no other stream paused + * the processing of incoming data, we can therefore read new data + * from the network. + * If DATA is coming for this stream, we want to store it ad the + * `buf` passed in right away - saving us a copy. + */ + stream->mem = buf; stream->len = len; stream->memlen = 0; - if(httpc->inbuflen == 0) { - nread = ((Curl_recv *)httpc->recv_underlying)( - data, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, err); + if(ctx->inbuflen > 0) { + DEBUGF(LOG_CF(data, cf, "Use data left in connection buffer, nread=%zd", + ctx->inbuflen - ctx->nread_inbuf)); + if(h2_process_pending_input(cf, data, err)) + return -1; + } - if(nread == -1) { + while(stream->memlen == 0 /* have no data for this stream */ + && !ctx->pause_stream_id /* we are not paused either */ + && ctx->inbuflen == 0) { /* and out input buffer is empty */ + /* Receive data from the "lower" filters */ + nread = Curl_conn_cf_recv(cf->next, data, ctx->inbuf, H2_BUFSIZE, err); + if(nread < 0) { if(*err != CURLE_AGAIN) failf(data, "Failed receiving HTTP2 data"); - else if(stream->closed) + else if(stream->closed) { /* received when the stream was already closed! */ - return http2_handle_stream_close(conn, data, stream, err); + nread = http2_handle_stream_close(cf, data, stream, err); + goto out; + } - return -1; + /* nothing to read from the lower layers, clear drain */ + drained_transfer(cf, data); + nread = -1; + goto out; } - - if(nread == 0) { + else if(nread == 0) { if(!stream->closed) { /* This will happen when the server or proxy server is SIGKILLed during data transfer. We should emit an error since our data @@ -1803,107 +1879,133 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex, failf(data, "HTTP/2 stream %u was not closed cleanly before" " end of the underlying stream", stream->stream_id); - *err = CURLE_HTTP2_STREAM; - return -1; + drained_transfer(cf, data); + *err = CURLE_PARTIAL_FILE; + nread = -1; + goto out; } - H2BUGF(infof(data, "end of stream")); + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] end of stream", + stream->stream_id)); *err = CURLE_OK; - return 0; + nread = 0; + goto out; } - H2BUGF(infof(data, "nread=%zd", nread)); - - httpc->inbuflen = nread; - - DEBUGASSERT(httpc->nread_inbuf == 0); - } - else { - nread = httpc->inbuflen - httpc->nread_inbuf; - (void)nread; /* silence warning, used in debug */ - H2BUGF(infof(data, "Use data left in connection buffer, nread=%zd", - nread)); + DEBUGF(LOG_CF(data, cf, "read %zd from connection", nread)); + ctx->inbuflen = nread; + DEBUGASSERT(ctx->nread_inbuf == 0); + if(h2_process_pending_input(cf, data, err)) + return -1; } - if(h2_process_pending_input(data, httpc, err)) - return -1; } + if(stream->memlen) { ssize_t retlen = stream->memlen; - H2BUGF(infof(data, "http2_recv: returns %zd for stream %u", - retlen, stream->stream_id)); + + /* TODO: all this buffer handling is very brittle */ + stream->len += stream->memlen; stream->memlen = 0; - if(httpc->pause_stream_id == stream->stream_id) { + if(ctx->pause_stream_id == stream->stream_id) { /* data for this stream is returned now, but this stream caused a pause already so we need it called again asap */ - H2BUGF(infof(data, "Data returned for PAUSED stream %u", - stream->stream_id)); - } - else if(!stream->closed) { - drained_transfer(data, httpc); + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Data returned for PAUSED stream", + stream->stream_id)); + drain_this(cf, data); + Curl_expire(data, 0, EXPIRE_RUN_NOW); } - else + else if(stream->closed) { + if(stream->reset || stream->error) { + nread = http2_handle_stream_close(cf, data, stream, err); + goto out; + } /* this stream is closed, trigger a another read ASAP to detect that */ + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] is closed now, run again", + stream->stream_id)); + drain_this(cf, data); Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + else { + drained_transfer(cf, data); + } - return retlen; + *err = CURLE_OK; + nread = retlen; + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_h2_recv -> %zd", + stream->stream_id, nread)); + goto out; + } + + if(stream->closed) { + nread = http2_handle_stream_close(cf, data, stream, err); + goto out; + } + + if(!data->state.drain && Curl_conn_cf_data_pending(cf->next, data)) { + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] pending data, set drain", + stream->stream_id)); + drain_this(cf, data); } - if(stream->closed) - return http2_handle_stream_close(conn, data, stream, err); *err = CURLE_AGAIN; - H2BUGF(infof(data, "http2_recv returns AGAIN for stream %u", - stream->stream_id)); - return -1; + nread = -1; + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv -> AGAIN", + stream->stream_id)); +out: + CF_DATA_RESTORE(cf, save); + return nread; } -static ssize_t http2_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *err) +static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) { /* * Currently, we send request in this function, but this function is also * used to send request body. It would be nice to add dedicated function for * request. */ + struct cf_h2_ctx *ctx = cf->ctx; int rv; - struct connectdata *conn = data->conn; - struct http_conn *httpc = &conn->proto.httpc; struct HTTP *stream = data->req.p.http; nghttp2_nv *nva = NULL; size_t nheader; nghttp2_data_provider data_prd; int32_t stream_id; - nghttp2_session *h2 = httpc->h2; nghttp2_priority_spec pri_spec; CURLcode result; struct h2h3req *hreq; + struct cf_call_data save; - (void)sockindex; - - H2BUGF(infof(data, "http2_send len=%zu", len)); + CF_DATA_SAVE(save, cf, data); + DEBUGF(LOG_CF(data, cf, "send len=%zu", len)); if(stream->stream_id != -1) { if(stream->close_handled) { infof(data, "stream %u closed", stream->stream_id); *err = CURLE_HTTP2_STREAM; - return -1; + len = -1; + goto out; } else if(stream->closed) { - return http2_handle_stream_close(conn, data, stream, err); + len = http2_handle_stream_close(cf, data, stream, err); + goto out; } /* If stream_id != -1, we have dispatched request HEADERS, and now are going to send or sending request body in DATA frame */ - stream->upload_mem = mem; + stream->upload_mem = buf; stream->upload_len = len; - rv = nghttp2_session_resume_data(h2, stream->stream_id); + rv = nghttp2_session_resume_data(ctx->h2, stream->stream_id); if(nghttp2_is_fatal(rv)) { *err = CURLE_SEND_ERROR; - return -1; + len = -1; + goto out; } - rv = h2_session_send(data, h2); - if(nghttp2_is_fatal(rv)) { - *err = CURLE_SEND_ERROR; - return -1; + result = h2_session_send(cf, data); + if(result) { + *err = result; + len = -1; + goto out; } len -= stream->upload_len; @@ -1912,10 +2014,11 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex, stream->upload_mem = NULL; stream->upload_len = 0; - if(should_close_session(httpc)) { - H2BUGF(infof(data, "http2_send: nothing to do in this session")); + if(should_close_session(ctx)) { + DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session")); *err = CURLE_HTTP2; - return -1; + len = -1; + goto out; } if(stream->upload_left) { @@ -1923,15 +2026,15 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex, following API will make nghttp2_session_want_write() return nonzero if remote window allows it, which then libcurl checks socket is writable or not. See http2_perform_getsock(). */ - nghttp2_session_resume_data(h2, stream->stream_id); + nghttp2_session_resume_data(ctx->h2, stream->stream_id); } #ifdef DEBUG_HTTP2 if(!len) { infof(data, "http2_send: easy %p (stream %u) win %u/%u", data, stream->stream_id, - nghttp2_session_get_remote_window_size(httpc->h2), - nghttp2_session_get_stream_remote_window_size(httpc->h2, + nghttp2_session_get_remote_window_size(ctx->h2), + nghttp2_session_get_stream_remote_window_size(ctx->h2, stream->stream_id) ); @@ -1939,13 +2042,14 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex, infof(data, "http2_send returns %zu for stream %u", len, stream->stream_id); #endif - return len; + goto out; } - result = Curl_pseudo_headers(data, mem, len, &hreq); + result = Curl_pseudo_headers(data, buf, len, NULL, &hreq); if(result) { *err = result; - return -1; + len = -1; + goto out; } nheader = hreq->entries; @@ -1953,7 +2057,8 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex, if(!nva) { Curl_pseudo_free(hreq); *err = CURLE_OUT_OF_MEMORY; - return -1; + len = -1; + goto out; } else { unsigned int i; @@ -1969,8 +2074,8 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex, h2_pri_spec(data, &pri_spec); - H2BUGF(infof(data, "http2_send request allowed %d (easy handle %p)", - nghttp2_session_check_request_allowed(h2), (void *)data)); + DEBUGF(LOG_CF(data, cf, "send request allowed %d (easy handle %p)", + nghttp2_session_check_request_allowed(ctx->h2), (void *)data)); switch(data->state.httpreq) { case HTTPREQ_POST: @@ -1985,42 +2090,40 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex, data_prd.read_callback = data_source_read_callback; data_prd.source.ptr = NULL; - stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader, + stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, &data_prd, data); break; default: - stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader, + stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, NULL, data); } Curl_safefree(nva); if(stream_id < 0) { - H2BUGF(infof(data, - "http2_send() nghttp2_submit_request error (%s)%u", - nghttp2_strerror(stream_id), stream_id)); + DEBUGF(LOG_CF(data, cf, "send: nghttp2_submit_request error (%s)%u", + nghttp2_strerror(stream_id), stream_id)); *err = CURLE_SEND_ERROR; - return -1; + len = -1; + goto out; } infof(data, "Using Stream ID: %u (easy handle %p)", stream_id, (void *)data); stream->stream_id = stream_id; - rv = h2_session_send(data, h2); - if(rv) { - H2BUGF(infof(data, - "http2_send() nghttp2_session_send error (%s)%d", - nghttp2_strerror(rv), rv)); - - *err = CURLE_SEND_ERROR; - return -1; + result = h2_session_send(cf, data); + if(result) { + *err = result; + len = -1; + goto out; } - if(should_close_session(httpc)) { - H2BUGF(infof(data, "http2_send: nothing to do in this session")); + if(should_close_session(ctx)) { + DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session")); *err = CURLE_HTTP2; - return -1; + len = -1; + goto out; } /* If whole HEADERS frame was sent off to the underlying socket, the nghttp2 @@ -2030,169 +2133,126 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex, results that no writable socket check is performed. To workaround this, we issue nghttp2_session_resume_data() here to bring back DATA transmission from deferred state. */ - nghttp2_session_resume_data(h2, stream->stream_id); + nghttp2_session_resume_data(ctx->h2, stream->stream_id); +out: + CF_DATA_RESTORE(cf, save); return len; } -CURLcode Curl_http2_setup(struct Curl_easy *data, - struct connectdata *conn) +static int cf_h2_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *sock) { - CURLcode result; - struct http_conn *httpc = &conn->proto.httpc; + struct cf_h2_ctx *ctx = cf->ctx; + struct SingleRequest *k = &data->req; struct HTTP *stream = data->req.p.http; + int bitmap = GETSOCK_BLANK; + struct cf_call_data save; - DEBUGASSERT(data->state.buffer); + CF_DATA_SAVE(save, cf, data); + sock[0] = Curl_conn_cf_get_socket(cf, data); - stream->stream_id = -1; + if(!(k->keepon & KEEP_RECV_PAUSE)) + /* Unless paused - in an HTTP/2 connection we can basically always get a + frame so we should always be ready for one */ + bitmap |= GETSOCK_READSOCK(0); - Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS); - Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS); + /* we're (still uploading OR the HTTP/2 layer wants to send data) AND + there's a window to send data in */ + if((((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) || + nghttp2_session_want_write(ctx->h2)) && + (nghttp2_session_get_remote_window_size(ctx->h2) && + nghttp2_session_get_stream_remote_window_size(ctx->h2, + stream->stream_id))) + bitmap |= GETSOCK_WRITESOCK(0); - stream->upload_left = 0; - stream->upload_mem = NULL; - stream->upload_len = 0; - stream->mem = data->state.buffer; - stream->len = data->set.buffer_size; + CF_DATA_RESTORE(cf, save); + return bitmap; +} - multi_connchanged(data->multi); - /* below this point only connection related inits are done, which only needs - to be done once per connection */ - if((conn->handler == &Curl_handler_http2_ssl) || - (conn->handler == &Curl_handler_http2)) - return CURLE_OK; /* already done */ +static CURLcode cf_h2_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_h2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; - if(conn->handler->flags & PROTOPT_SSL) - conn->handler = &Curl_handler_http2_ssl; - else - conn->handler = &Curl_handler_http2; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } - result = http2_init(data, conn); - if(result) { - Curl_dyn_free(&stream->header_recvbuf); - return result; + /* Connect the lower filters first */ + if(!cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; } - infof(data, "Using HTTP2, server supports multiplexing"); + *done = FALSE; - conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->httpversion = 20; - conn->bundle->multiuse = BUNDLE_MULTIPLEX; + CF_DATA_SAVE(save, cf, data); + if(!ctx->h2) { + result = cf_h2_ctx_init(cf, data, FALSE); + if(result) + goto out; + } - httpc->inbuflen = 0; - httpc->nread_inbuf = 0; + if(-1 == h2_process_pending_input(cf, data, &result)) { + result = CURLE_HTTP2; + goto out; + } - httpc->pause_stream_id = 0; - httpc->drain_total = 0; + *done = TRUE; + cf->connected = TRUE; + result = CURLE_OK; - return CURLE_OK; +out: + CF_DATA_RESTORE(cf, save); + return result; } -CURLcode Curl_http2_switched(struct Curl_easy *data, - const char *mem, size_t nread) +static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - CURLcode result; - struct connectdata *conn = data->conn; - struct http_conn *httpc = &conn->proto.httpc; - int rv; - struct HTTP *stream = data->req.p.http; - - result = Curl_http2_setup(data, conn); - if(result) - return result; - - httpc->recv_underlying = conn->recv[FIRSTSOCKET]; - httpc->send_underlying = conn->send[FIRSTSOCKET]; - conn->recv[FIRSTSOCKET] = http2_recv; - conn->send[FIRSTSOCKET] = http2_send; - - if(data->req.upgr101 == UPGR101_RECEIVED) { - /* stream 1 is opened implicitly on upgrade */ - stream->stream_id = 1; - /* queue SETTINGS frame (again) */ - rv = nghttp2_session_upgrade2(httpc->h2, httpc->binsettings, httpc->binlen, - data->state.httpreq == HTTPREQ_HEAD, NULL); - if(rv) { - failf(data, "nghttp2_session_upgrade2() failed: %s(%d)", - nghttp2_strerror(rv), rv); - return CURLE_HTTP2; - } + struct cf_h2_ctx *ctx = cf->ctx; - rv = nghttp2_session_set_stream_user_data(httpc->h2, - stream->stream_id, - data); - if(rv) { - infof(data, "http/2: failed to set user_data for stream %u", - stream->stream_id); - DEBUGASSERT(0); - } - } - else { - populate_settings(data, httpc); + if(ctx) { + struct cf_call_data save; - /* stream ID is unknown at this point */ - stream->stream_id = -1; - rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE, - httpc->local_settings, - httpc->local_settings_num); - if(rv) { - failf(data, "nghttp2_submit_settings() failed: %s(%d)", - nghttp2_strerror(rv), rv); - return CURLE_HTTP2; - } + CF_DATA_SAVE(save, cf, data); + cf_h2_ctx_clear(ctx); + CF_DATA_RESTORE(cf, save); } +} - rv = nghttp2_session_set_local_window_size(httpc->h2, NGHTTP2_FLAG_NONE, 0, - HTTP2_HUGE_WINDOW_SIZE); - if(rv) { - failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", - nghttp2_strerror(rv), rv); - return CURLE_HTTP2; - } +static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; - /* we are going to copy mem to httpc->inbuf. This is required since - mem is part of buffer pointed by stream->mem, and callbacks - called by nghttp2_session_mem_recv() will write stream specific - data into stream->mem, overwriting data already there. */ - if(H2_BUFSIZE < nread) { - failf(data, "connection buffer size is too small to store data following " - "HTTP Upgrade response header: buflen=%d, datalen=%zu", - H2_BUFSIZE, nread); - return CURLE_HTTP2; + (void)data; + if(ctx) { + cf_h2_ctx_free(ctx); + cf->ctx = NULL; } - - infof(data, "Copying HTTP/2 data in stream buffer to connection buffer" - " after upgrade: len=%zu", - nread); - - if(nread) - memcpy(httpc->inbuf, mem, nread); - - httpc->inbuflen = nread; - - DEBUGASSERT(httpc->nread_inbuf == 0); - - if(-1 == h2_process_pending_input(data, httpc, &result)) - return CURLE_HTTP2; - - return CURLE_OK; } -CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause) +static CURLcode http2_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) { + struct cf_h2_ctx *ctx = cf->ctx; + DEBUGASSERT(data); - DEBUGASSERT(data->conn); - /* if it isn't HTTP/2, we're done */ - if(!(data->conn->handler->protocol & PROTO_FAMILY_HTTP) || - !data->conn->proto.httpc.h2) - return CURLE_OK; #ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE - else { + if(ctx && ctx->h2) { struct HTTP *stream = data->req.p.http; - struct http_conn *httpc = &data->conn->proto.httpc; uint32_t window = !pause * HTTP2_HUGE_WINDOW_SIZE; - int rv = nghttp2_session_set_local_window_size(httpc->h2, + CURLcode result; + + int rv = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, stream->stream_id, window); @@ -2203,9 +2263,9 @@ CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause) } /* make sure the window update gets sent */ - rv = h2_session_send(data, httpc->h2); - if(rv) - return CURLE_SEND_ERROR; + result = h2_session_send(cf, data); + if(result) + return result; DEBUGF(infof(data, "Set HTTP/2 window size to %u for stream %u", window, stream->stream_id)); @@ -2214,7 +2274,7 @@ CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause) { /* read out the stream local window again */ uint32_t window2 = - nghttp2_session_get_stream_local_window_size(httpc->h2, + nghttp2_session_get_stream_local_window_size(ctx->h2, stream->stream_id); DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u", window2, stream->stream_id)); @@ -2225,86 +2285,323 @@ CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause) return CURLE_OK; } -CURLcode Curl_http2_add_child(struct Curl_easy *parent, - struct Curl_easy *child, - bool exclusive) +static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) { - if(parent) { - struct Curl_http2_dep **tail; - struct Curl_http2_dep *dep = calloc(1, sizeof(struct Curl_http2_dep)); - if(!dep) - return CURLE_OUT_OF_MEMORY; - dep->data = child; - - if(parent->set.stream_dependents && exclusive) { - struct Curl_http2_dep *node = parent->set.stream_dependents; - while(node) { - node->data->set.stream_depends_on = child; - node = node->next; - } + CURLcode result = CURLE_OK; + struct cf_call_data save; - tail = &child->set.stream_dependents; - while(*tail) - tail = &(*tail)->next; + (void)arg2; - DEBUGASSERT(!*tail); - *tail = parent->set.stream_dependents; - parent->set.stream_dependents = 0; - } + CF_DATA_SAVE(save, cf, data); + switch(event) { + case CF_CTRL_DATA_SETUP: { + result = http2_data_setup(cf, data); + break; + } + case CF_CTRL_DATA_PAUSE: { + result = http2_data_pause(cf, data, (arg1 != 0)); + break; + } + case CF_CTRL_DATA_DONE_SEND: { + result = http2_data_done_send(cf, data); + break; + } + case CF_CTRL_DATA_DONE: { + http2_data_done(cf, data, arg1 != 0); + break; + } + default: + break; + } + CF_DATA_RESTORE(cf, save); + return result; +} - tail = &parent->set.stream_dependents; - while(*tail) { - (*tail)->data->set.stream_depends_e = FALSE; - tail = &(*tail)->next; - } +static bool cf_h2_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + if(ctx && ctx->inbuflen > 0 && ctx->nread_inbuf > ctx->inbuflen) + return TRUE; + return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} - DEBUGASSERT(!*tail); - *tail = dep; +static bool cf_h2_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + CURLcode result; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + result = (ctx && ctx->h2 && !http2_connisdead(cf, data)); + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode cf_h2_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + result = http2_send_ping(cf, data); + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode cf_h2_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct cf_call_data save; + size_t effective_max; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: + DEBUGASSERT(pres1); + + CF_DATA_SAVE(save, cf, data); + if(nghttp2_session_check_request_allowed(ctx->h2) == 0) { + /* the limit is what we have in use right now */ + effective_max = CONN_INUSE(cf->conn); + } + else { + effective_max = ctx->max_concurrent_streams; + } + *pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max; + CF_DATA_RESTORE(cf, save); + return CURLE_OK; + default: + break; } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} - child->set.stream_depends_on = parent; - child->set.stream_depends_e = exclusive; - return CURLE_OK; +struct Curl_cftype Curl_cft_nghttp2 = { + "HTTP/2", + CF_TYPE_MULTIPLEX, + CURL_LOG_DEFAULT, + cf_h2_destroy, + cf_h2_connect, + cf_h2_close, + Curl_cf_def_get_host, + cf_h2_get_select_socks, + cf_h2_data_pending, + cf_h2_send, + cf_h2_recv, + cf_h2_cntrl, + cf_h2_is_alive, + cf_h2_keep_alive, + cf_h2_query, +}; + +static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = NULL; + struct cf_h2_ctx *ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; + + DEBUGASSERT(data->conn); + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) + goto out; + + result = Curl_cf_create(&cf, &Curl_cft_nghttp2, ctx); + if(result) + goto out; + + Curl_conn_cf_add(data, conn, sockindex, cf); + result = CURLE_OK; + +out: + if(result) + cf_h2_ctx_free(ctx); + *pcf = result? NULL : cf; + return result; } -void Curl_http2_remove_child(struct Curl_easy *parent, struct Curl_easy *child) +static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct Curl_http2_dep *last = 0; - struct Curl_http2_dep *data = parent->set.stream_dependents; - DEBUGASSERT(child->set.stream_depends_on == parent); + struct Curl_cfilter *cf_h2 = NULL; + struct cf_h2_ctx *ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; - while(data && data->data != child) { - last = data; - data = data->next; + (void)data; + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) + goto out; + + result = Curl_cf_create(&cf_h2, &Curl_cft_nghttp2, ctx); + if(result) + goto out; + + Curl_conn_cf_insert_after(cf, cf_h2); + result = CURLE_OK; + +out: + if(result) + cf_h2_ctx_free(ctx); + return result; +} + +bool Curl_cf_is_http2(struct Curl_cfilter *cf, const struct Curl_easy *data) +{ + (void)data; + for(; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_nghttp2) + return TRUE; + if(cf->cft->flags & CF_TYPE_IP_CONNECT) + return FALSE; } + return FALSE; +} - DEBUGASSERT(data); +bool Curl_conn_is_http2(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + return conn? Curl_cf_is_http2(conn->cfilter[sockindex], data) : FALSE; +} - if(data) { - if(last) { - last->next = data->next; - } - else { - parent->set.stream_dependents = data->next; +bool Curl_http2_may_switch(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + (void)sockindex; + if(data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + /* We don't support HTTP/2 proxies yet. Also it's debatable + whether or not this setting should apply to HTTP/2 proxies. */ + infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); + return FALSE; } - free(data); +#endif + return TRUE; } + return FALSE; +} + +CURLcode Curl_http2_switch(struct Curl_easy *data, + struct connectdata *conn, int sockindex) +{ + struct Curl_cfilter *cf; + CURLcode result; + + DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); + DEBUGF(infof(data, DMSGI(data, sockindex, "switching to HTTP/2"))); + + result = http2_cfilter_add(&cf, data, conn, sockindex); + if(result) + return result; - child->set.stream_depends_on = 0; - child->set.stream_depends_e = FALSE; + result = cf_h2_ctx_init(cf, data, FALSE); + if(result) + return result; + + conn->httpversion = 20; /* we know we're on HTTP/2 now */ + conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + conn->bundle->multiuse = BUNDLE_MULTIPLEX; + multi_connchanged(data->multi); + + if(cf->next) { + bool done; + return Curl_conn_cf_connect(cf, data, FALSE, &done); + } + return CURLE_OK; +} + +CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct Curl_cfilter *cf_h2; + CURLcode result; + + DEBUGASSERT(!Curl_cf_is_http2(cf, data)); + + result = http2_cfilter_insert_after(cf, data); + if(result) + return result; + + cf_h2 = cf->next; + result = cf_h2_ctx_init(cf_h2, data, FALSE); + if(result) + return result; + + cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */ + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + multi_connchanged(data->multi); + + if(cf_h2->next) { + bool done; + return Curl_conn_cf_connect(cf_h2, data, FALSE, &done); + } + return CURLE_OK; } -void Curl_http2_cleanup_dependencies(struct Curl_easy *data) +CURLcode Curl_http2_upgrade(struct Curl_easy *data, + struct connectdata *conn, int sockindex, + const char *mem, size_t nread) { - while(data->set.stream_dependents) { - struct Curl_easy *tmp = data->set.stream_dependents->data; - Curl_http2_remove_child(data, tmp); - if(data->set.stream_depends_on) - Curl_http2_add_child(data->set.stream_depends_on, tmp, FALSE); + struct Curl_cfilter *cf; + struct cf_h2_ctx *ctx; + CURLcode result; + + DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); + DEBUGF(infof(data, DMSGI(data, sockindex, "upgrading to HTTP/2"))); + DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED); + + result = http2_cfilter_add(&cf, data, conn, sockindex); + if(result) + return result; + + DEBUGASSERT(cf->cft == &Curl_cft_nghttp2); + ctx = cf->ctx; + + result = cf_h2_ctx_init(cf, data, TRUE); + if(result) + return result; + + if(nread) { + /* we are going to copy mem to httpc->inbuf. This is required since + mem is part of buffer pointed by stream->mem, and callbacks + called by nghttp2_session_mem_recv() will write stream specific + data into stream->mem, overwriting data already there. */ + if(H2_BUFSIZE < nread) { + failf(data, "connection buffer size is too small to store data " + "following HTTP Upgrade response header: buflen=%d, datalen=%zu", + H2_BUFSIZE, nread); + return CURLE_HTTP2; + } + + infof(data, "Copying HTTP/2 data in stream buffer to connection buffer" + " after upgrade: len=%zu", nread); + DEBUGASSERT(ctx->nread_inbuf == 0); + memcpy(ctx->inbuf, mem, nread); + ctx->inbuflen = nread; } - if(data->set.stream_depends_on) - Curl_http2_remove_child(data->set.stream_depends_on, data); + conn->httpversion = 20; /* we know we're on HTTP/2 now */ + conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + conn->bundle->multiuse = BUNDLE_MULTIPLEX; + multi_connchanged(data->multi); + + if(cf->next) { + bool done; + return Curl_conn_cf_connect(cf, data, FALSE, &done); + } + return CURLE_OK; } /* Only call this function for a transfer that already got an HTTP/2 @@ -2312,7 +2609,7 @@ void Curl_http2_cleanup_dependencies(struct Curl_easy *data) bool Curl_h2_http_1_1_error(struct Curl_easy *data) { struct HTTP *stream = data->req.p.http; - return (stream->error == NGHTTP2_HTTP_1_1_REQUIRED); + return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED); } #else /* !USE_NGHTTP2 */ diff --git a/Utilities/cmcurl/lib/http2.h b/Utilities/cmcurl/lib/http2.h index 966bf75da0..f78fbf04e0 100644 --- a/Utilities/cmcurl/lib/http2.h +++ b/Utilities/cmcurl/lib/http2.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -40,44 +40,41 @@ void Curl_http2_ver(char *p, size_t len); const char *Curl_http2_strerror(uint32_t err); -CURLcode Curl_http2_init(struct connectdata *conn); -void Curl_http2_init_state(struct UrlState *state); -void Curl_http2_init_userset(struct UserDefined *set); CURLcode Curl_http2_request_upgrade(struct dynbuf *req, struct Curl_easy *data); -CURLcode Curl_http2_setup(struct Curl_easy *data, struct connectdata *conn); -CURLcode Curl_http2_switched(struct Curl_easy *data, - const char *ptr, size_t nread); -/* called from http_setup_conn */ -void Curl_http2_setup_conn(struct connectdata *conn); -void Curl_http2_setup_req(struct Curl_easy *data); -void Curl_http2_done(struct Curl_easy *data, bool premature); -CURLcode Curl_http2_done_sending(struct Curl_easy *data, - struct connectdata *conn); -CURLcode Curl_http2_add_child(struct Curl_easy *parent, - struct Curl_easy *child, - bool exclusive); -void Curl_http2_remove_child(struct Curl_easy *parent, - struct Curl_easy *child); -void Curl_http2_cleanup_dependencies(struct Curl_easy *data); -CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause); /* returns true if the HTTP/2 stream error was HTTP_1_1_REQUIRED */ bool Curl_h2_http_1_1_error(struct Curl_easy *data); + +bool Curl_conn_is_http2(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); +bool Curl_cf_is_http2(struct Curl_cfilter *cf, const struct Curl_easy *data); + +bool Curl_http2_may_switch(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + +CURLcode Curl_http2_switch(struct Curl_easy *data, + struct connectdata *conn, int sockindex); + +CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data); + +CURLcode Curl_http2_upgrade(struct Curl_easy *data, + struct connectdata *conn, int sockindex, + const char *ptr, size_t nread); + +extern struct Curl_cftype Curl_cft_nghttp2; + #else /* USE_NGHTTP2 */ + +#define Curl_cf_is_http2(a,b) FALSE +#define Curl_conn_is_http2(a,b,c) FALSE +#define Curl_http2_may_switch(a,b,c) FALSE + #define Curl_http2_request_upgrade(x,y) CURLE_UNSUPPORTED_PROTOCOL -#define Curl_http2_setup(x,y) CURLE_UNSUPPORTED_PROTOCOL -#define Curl_http2_switched(x,y,z) CURLE_UNSUPPORTED_PROTOCOL -#define Curl_http2_setup_conn(x) Curl_nop_stmt -#define Curl_http2_setup_req(x) -#define Curl_http2_init_state(x) -#define Curl_http2_init_userset(x) -#define Curl_http2_done(x,y) -#define Curl_http2_done_sending(x,y) (void)y -#define Curl_http2_add_child(x, y, z) -#define Curl_http2_remove_child(x, y) -#define Curl_http2_cleanup_dependencies(x) -#define Curl_http2_stream_pause(x, y) +#define Curl_http2_switch(a,b,c) CURLE_UNSUPPORTED_PROTOCOL +#define Curl_http2_upgrade(a,b,c,d,e) CURLE_UNSUPPORTED_PROTOCOL #define Curl_h2_http_1_1_error(x) 0 #endif diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.c b/Utilities/cmcurl/lib/http_aws_sigv4.c index 8c6d1c9dc8..f8ce1695e8 100644 --- a/Utilities/cmcurl/lib/http_aws_sigv4.c +++ b/Utilities/cmcurl/lib/http_aws_sigv4.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -48,9 +48,9 @@ do { \ ret = Curl_hmacit(Curl_HMAC_SHA256, \ (unsigned char *)k, \ - (unsigned int)kl, \ + kl, \ (unsigned char *)d, \ - (unsigned int)dl, o); \ + dl, o); \ if(ret) { \ goto fail; \ } \ diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.h b/Utilities/cmcurl/lib/http_aws_sigv4.h index 85755e937b..57cc5706eb 100644 --- a/Utilities/cmcurl/lib/http_aws_sigv4.h +++ b/Utilities/cmcurl/lib/http_aws_sigv4.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/http_chunks.c b/Utilities/cmcurl/lib/http_chunks.c index c344e6d39a..bda00d3833 100644 --- a/Utilities/cmcurl/lib/http_chunks.c +++ b/Utilities/cmcurl/lib/http_chunks.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/http_chunks.h b/Utilities/cmcurl/lib/http_chunks.h index 2cf5507c21..ed50713162 100644 --- a/Utilities/cmcurl/lib/http_chunks.h +++ b/Utilities/cmcurl/lib/http_chunks.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/http_digest.c b/Utilities/cmcurl/lib/http_digest.c index f015f78d92..8daad99e32 100644 --- a/Utilities/cmcurl/lib/http_digest.c +++ b/Utilities/cmcurl/lib/http_digest.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/http_digest.h b/Utilities/cmcurl/lib/http_digest.h index eea90b7439..7d5cfc1bfd 100644 --- a/Utilities/cmcurl/lib/http_digest.h +++ b/Utilities/cmcurl/lib/http_digest.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/http_negotiate.c b/Utilities/cmcurl/lib/http_negotiate.c index 5909f85b0d..153e3d4ab8 100644 --- a/Utilities/cmcurl/lib/http_negotiate.c +++ b/Utilities/cmcurl/lib/http_negotiate.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/http_negotiate.h b/Utilities/cmcurl/lib/http_negotiate.h index 6e2096c697..76d8356132 100644 --- a/Utilities/cmcurl/lib/http_negotiate.h +++ b/Utilities/cmcurl/lib/http_negotiate.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/http_ntlm.c b/Utilities/cmcurl/lib/http_ntlm.c index a19cc0d589..b845ddf37f 100644 --- a/Utilities/cmcurl/lib/http_ntlm.c +++ b/Utilities/cmcurl/lib/http_ntlm.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/http_ntlm.h b/Utilities/cmcurl/lib/http_ntlm.h index cec63b82c3..f37572baec 100644 --- a/Utilities/cmcurl/lib/http_ntlm.h +++ b/Utilities/cmcurl/lib/http_ntlm.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/http_proxy.c b/Utilities/cmcurl/lib/http_proxy.c index e30730acab..fdd092d034 100644 --- a/Utilities/cmcurl/lib/http_proxy.c +++ b/Utilities/cmcurl/lib/http_proxy.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -26,7 +26,7 @@ #include "http_proxy.h" -#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) +#if !defined(CURL_DISABLE_PROXY) #include <curl/curl.h> #ifdef USE_HYPER @@ -49,6 +49,9 @@ #include "curl_memory.h" #include "memdebug.h" + +#if !defined(CURL_DISABLE_HTTP) + typedef enum { TUNNEL_INIT, /* init/default/no tunnel state */ TUNNEL_CONNECT, /* CONNECT request is being send */ @@ -63,8 +66,7 @@ struct tunnel_state { int sockindex; const char *hostname; int remote_port; - struct HTTP http_proxy; - struct HTTP *prot_save; + struct HTTP CONNECT; struct dynbuf rcvbuf; struct dynbuf req; size_t nsend; @@ -149,17 +151,6 @@ static CURLcode tunnel_init(struct tunnel_state **pts, Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST); - /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the - * member conn->proto.http; we want [protocol] through HTTP and we have - * to change the member temporarily for connecting to the HTTP - * proxy. After Curl_proxyCONNECT we have to set back the member to the - * original pointer - * - * This function might be called several times in the multi interface case - * if the proxy's CONNECT response is not instant. - */ - ts->prot_save = data->req.p.http; - data->req.p.http = &ts->http_proxy; *pts = ts; connkeep(conn, "HTTP proxy CONNECT"); return tunnel_reinit(ts, conn, data); @@ -183,34 +174,39 @@ static void tunnel_go_state(struct Curl_cfilter *cf, /* entering this one */ switch(new_state) { case TUNNEL_INIT: + DEBUGF(LOG_CF(data, cf, "new tunnel state 'init'")); tunnel_reinit(ts, cf->conn, data); break; case TUNNEL_CONNECT: + DEBUGF(LOG_CF(data, cf, "new tunnel state 'connect'")); ts->tunnel_state = TUNNEL_CONNECT; ts->keepon = KEEPON_CONNECT; Curl_dyn_reset(&ts->rcvbuf); break; case TUNNEL_RECEIVE: + DEBUGF(LOG_CF(data, cf, "new tunnel state 'receive'")); ts->tunnel_state = TUNNEL_RECEIVE; break; case TUNNEL_RESPONSE: + DEBUGF(LOG_CF(data, cf, "new tunnel state 'response'")); ts->tunnel_state = TUNNEL_RESPONSE; break; case TUNNEL_ESTABLISHED: + DEBUGF(LOG_CF(data, cf, "new tunnel state 'established'")); infof(data, "CONNECT phase completed"); data->state.authproxy.done = TRUE; data->state.authproxy.multipass = FALSE; /* FALLTHROUGH */ case TUNNEL_FAILED: + DEBUGF(LOG_CF(data, cf, "new tunnel state 'failed'")); ts->tunnel_state = new_state; Curl_dyn_reset(&ts->rcvbuf); Curl_dyn_reset(&ts->req); /* restore the protocol pointer */ - data->req.p.http = ts->prot_save; data->info.httpcode = 0; /* clear it as it might've been used for the proxy */ /* If a proxy-authorization header was used for the proxy, then we should @@ -271,10 +267,11 @@ static CURLcode CONNECT_host(struct Curl_easy *data, } #ifndef USE_HYPER -static CURLcode start_CONNECT(struct Curl_easy *data, - struct connectdata *conn, +static CURLcode start_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, struct tunnel_state *ts) { + struct connectdata *conn = cf->conn; char *hostheader = NULL; char *host = NULL; const char *httpv; @@ -338,7 +335,8 @@ static CURLcode start_CONNECT(struct Curl_easy *data, goto out; /* Send the connect request to the proxy */ - result = Curl_buffer_send(&ts->req, data, &data->info.request_size, 0, + result = Curl_buffer_send(&ts->req, data, &ts->CONNECT, + &data->info.request_size, 0, ts->sockindex); ts->headerlines = 0; @@ -356,7 +354,7 @@ static CURLcode send_CONNECT(struct Curl_easy *data, bool *done) { struct SingleRequest *k = &data->req; - struct HTTP *http = data->req.p.http; + struct HTTP *http = &ts->CONNECT; CURLcode result = CURLE_OK; if(http->sending != HTTPSEND_REQUEST) @@ -377,7 +375,7 @@ static CURLcode send_CONNECT(struct Curl_easy *data, result = Curl_write(data, conn->writesockfd, /* socket to send to */ k->upload_fromhere, /* buffer pointer */ - ts->nsend, /* buffer size */ + ts->nsend, /* buffer size */ &bytes_written); /* actually sent */ if(result) goto out; @@ -398,13 +396,15 @@ out: return result; } -static CURLcode on_resp_header(struct Curl_easy *data, +static CURLcode on_resp_header(struct Curl_cfilter *cf, + struct Curl_easy *data, struct tunnel_state *ts, const char *header) { CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; int subversion = 0; + (void)cf; if((checkprefix("WWW-Authenticate:", header) && (401 == k->httpcode)) || @@ -416,8 +416,7 @@ static CURLcode on_resp_header(struct Curl_easy *data, if(!auth) return CURLE_OUT_OF_MEMORY; - DEBUGF(infof(data, "CONNECT: fwd auth header '%s'", - header)); + DEBUGF(LOG_CF(data, cf, "CONNECT: fwd auth header '%s'", header)); result = Curl_http_input_auth(data, proxy, auth); free(auth); @@ -471,14 +470,14 @@ static CURLcode on_resp_header(struct Curl_easy *data, return result; } -static CURLcode recv_CONNECT_resp(struct Curl_easy *data, - struct connectdata *conn, +static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, struct tunnel_state *ts, bool *done) { CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - curl_socket_t tunnelsocket = conn->sock[ts->sockindex]; + curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data); char *linep; size_t perline; int error; @@ -634,7 +633,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_easy *data, /* without content-length or chunked encoding, we can't keep the connection alive since the close is the end signal so we bail out at once instead */ - DEBUGF(infof(data, "CONNECT: no content-length or chunked")); + DEBUGF(LOG_CF(data, cf, "CONNECT: no content-length or chunked")); ts->keepon = KEEPON_DONE; } } @@ -647,7 +646,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_easy *data, continue; } - result = on_resp_header(data, ts, linep); + result = on_resp_header(cf, data, ts, linep); if(result) return result; @@ -667,12 +666,13 @@ static CURLcode recv_CONNECT_resp(struct Curl_easy *data, #else /* USE_HYPER */ /* The Hyper version of CONNECT */ -static CURLcode start_CONNECT(struct Curl_easy *data, - struct connectdata *conn, +static CURLcode start_CONNECT(struct Curl_cfilter *cf, + struct Curl_easy *data, struct tunnel_state *ts) { + struct connectdata *conn = cf->conn; struct hyptransfer *h = &data->hyp; - curl_socket_t tunnelsocket = conn->sock[ts->sockindex]; + curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data); hyper_io *io = NULL; hyper_request *req = NULL; hyper_headers *headers = NULL; @@ -914,8 +914,8 @@ error: return result; } -static CURLcode recv_CONNECT_resp(struct Curl_easy *data, - struct connectdata *conn, +static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, struct tunnel_state *ts, bool *done) { @@ -925,7 +925,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_easy *data, (void)ts; *done = FALSE; - result = Curl_hyper_stream(data, conn, &didwhat, done, + result = Curl_hyper_stream(data, cf->conn, &didwhat, done, CURL_CSELECT_IN | CURL_CSELECT_OUT); if(result || !*done) return result; @@ -972,7 +972,8 @@ static CURLcode CONNECT(struct Curl_cfilter *cf, switch(ts->tunnel_state) { case TUNNEL_INIT: /* Prepare the CONNECT request and make a first attempt to send. */ - result = start_CONNECT(data, cf->conn, ts); + DEBUGF(LOG_CF(data, cf, "CONNECT start")); + result = start_CONNECT(cf, data, ts); if(result) goto out; tunnel_go_state(cf, ts, TUNNEL_CONNECT, data); @@ -980,6 +981,7 @@ static CURLcode CONNECT(struct Curl_cfilter *cf, case TUNNEL_CONNECT: /* see that the request is completely sent */ + DEBUGF(LOG_CF(data, cf, "CONNECT send")); result = send_CONNECT(data, cf->conn, ts, &done); if(result || !done) goto out; @@ -988,7 +990,8 @@ static CURLcode CONNECT(struct Curl_cfilter *cf, case TUNNEL_RECEIVE: /* read what is there */ - result = recv_CONNECT_resp(data, cf->conn, ts, &done); + DEBUGF(LOG_CF(data, cf, "CONNECT receive")); + result = recv_CONNECT_resp(cf, data, ts, &done); if(Curl_pgrsUpdate(data)) { result = CURLE_ABORTED_BY_CALLBACK; goto out; @@ -1001,24 +1004,29 @@ static CURLcode CONNECT(struct Curl_cfilter *cf, /* FALLTHROUGH */ case TUNNEL_RESPONSE: + DEBUGF(LOG_CF(data, cf, "CONNECT response")); if(data->req.newurl) { /* not the "final" response, we need to do a follow up request. * If the other side indicated a connection close, or if someone - * else told us to close this connection, do so now. */ + * else told us to close this connection, do so now. + */ if(ts->close_connection || conn->bits.close) { - /* Close the filter chain and trigger connect, non-blocking - * again, so the process is ongoing. This will - * a) the close resets our tunnel state - * b) the connect makes sure that there will be a socket - * to select on again. - * We return and expect to be called again. */ + /* Close this filter and the sub-chain, re-connect the + * sub-chain and continue. Closing this filter will + * reset our tunnel state. To avoid recursion, we return + * and expect to be called again. + */ + DEBUGF(LOG_CF(data, cf, "CONNECT need to close+open")); infof(data, "Connect me again please"); - Curl_conn_close(data, cf->sockindex); - result = cf->next->cft->connect(cf->next, data, FALSE, &done); + Curl_conn_cf_close(cf, data); + connkeep(conn, "HTTP proxy CONNECT"); + result = Curl_conn_cf_connect(cf->next, data, FALSE, &done); goto out; } - /* staying on this connection, reset state */ - tunnel_go_state(cf, ts, TUNNEL_INIT, data); + else { + /* staying on this connection, reset state */ + tunnel_go_state(cf, ts, TUNNEL_INIT, data); + } } break; @@ -1063,10 +1071,12 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, return CURLE_OK; } + DEBUGF(LOG_CF(data, cf, "connect")); result = cf->next->cft->connect(cf->next, data, blocking, done); if(result || !*done) return result; + DEBUGF(LOG_CF(data, cf, "subchain is connected")); /* TODO: can we do blocking? */ /* We want "seamless" operations through HTTP proxy tunnel */ @@ -1117,22 +1127,21 @@ static int http_proxy_cf_get_select_socks(struct Curl_cfilter *cf, curl_socket_t *socks) { struct tunnel_state *ts = cf->ctx; - struct connectdata *conn = cf->conn; int fds; - DEBUGASSERT(conn); fds = cf->next->cft->get_select_socks(cf->next, data, socks); if(!fds && cf->next->connected && !cf->connected) { /* If we are not connected, but the filter "below" is * and not waiting on something, we are tunneling. */ - socks[0] = conn->sock[cf->sockindex]; + socks[0] = Curl_conn_cf_get_socket(cf, data); if(ts) { /* when we've sent a CONNECT to a proxy, we should rather either wait for the socket to become readable to be able to get the response headers or if we're still sending the request, wait for write. */ - if(ts->http_proxy.sending == HTTPSEND_REQUEST) + if(ts->CONNECT.sending == HTTPSEND_REQUEST) { return GETSOCK_WRITESOCK(0); + } return GETSOCK_READSOCK(0); } return GETSOCK_WRITESOCK(0); @@ -1140,24 +1149,18 @@ static int http_proxy_cf_get_select_socks(struct Curl_cfilter *cf, return fds; } -static void http_proxy_cf_detach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - if(cf->ctx) { - tunnel_free(cf, data); - } -} - static void http_proxy_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - http_proxy_cf_detach_data(cf, data); + DEBUGF(LOG_CF(data, cf, "destroy")); + tunnel_free(cf, data); } static void http_proxy_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) { DEBUGASSERT(cf->next); + DEBUGF(LOG_CF(data, cf, "close")); cf->connected = FALSE; cf->next->cft->close(cf->next, data); if(cf->ctx) { @@ -1166,11 +1169,11 @@ static void http_proxy_cf_close(struct Curl_cfilter *cf, } -static const struct Curl_cftype cft_http_proxy = { +struct Curl_cftype Curl_cft_http_proxy = { "HTTP-PROXY", CF_TYPE_IP_CONNECT, + 0, http_proxy_cf_destroy, - Curl_cf_def_setup, http_proxy_cf_connect, http_proxy_cf_close, http_proxy_cf_get_host, @@ -1178,8 +1181,10 @@ static const struct Curl_cftype cft_http_proxy = { Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, - Curl_cf_def_attach_data, - http_proxy_cf_detach_data, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, }; CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data, @@ -1189,31 +1194,73 @@ CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data, struct Curl_cfilter *cf; CURLcode result; - result = Curl_cf_create(&cf, &cft_http_proxy, NULL); + result = Curl_cf_create(&cf, &Curl_cft_http_proxy, NULL); if(!result) Curl_conn_cf_add(data, conn, sockindex, cf); return result; } +CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + (void)data; + result = Curl_cf_create(&cf, &Curl_cft_http_proxy, NULL); + if(!result) + Curl_conn_cf_insert_after(cf_at, cf); + return result; +} + +#endif /* ! CURL_DISABLE_HTTP */ -static CURLcode send_haproxy_header(struct Curl_cfilter*cf, - struct Curl_easy *data) + +typedef enum { + HAPROXY_INIT, /* init/default/no tunnel state */ + HAPROXY_SEND, /* data_out being sent */ + HAPROXY_DONE /* all work done */ +} haproxy_state; + +struct cf_haproxy_ctx { + int state; + struct dynbuf data_out; +}; + +static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx) { - struct dynbuf req; + DEBUGASSERT(ctx); + ctx->state = HAPROXY_INIT; + Curl_dyn_reset(&ctx->data_out); +} + +static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx) +{ + if(ctx) { + Curl_dyn_free(&ctx->data_out); + free(ctx); + } +} + +static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf, + struct Curl_easy *data) +{ + struct cf_haproxy_ctx *ctx = cf->ctx; CURLcode result; const char *tcp_version; - Curl_dyn_init(&req, DYN_HAXPROXY); + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->state == HAPROXY_INIT); #ifdef USE_UNIX_SOCKETS if(cf->conn->unix_domain_socket) /* the buffer is large enough to hold this! */ - result = Curl_dyn_addn(&req, STRCONST("PROXY UNKNOWN\r\n")); + result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n")); else { #endif /* USE_UNIX_SOCKETS */ /* Emit the correct prefix for IPv6 */ tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4"; - result = Curl_dyn_addf(&req, "PROXY %s %s %s %i %i\r\n", + result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", tcp_version, data->info.conn_local_ip, data->info.conn_primary_ip, @@ -1223,19 +1270,18 @@ static CURLcode send_haproxy_header(struct Curl_cfilter*cf, #ifdef USE_UNIX_SOCKETS } #endif /* USE_UNIX_SOCKETS */ - - if(!result) - result = Curl_buffer_send(&req, data, &data->info.request_size, - 0, FIRSTSOCKET); return result; } -static CURLcode haproxy_cf_connect(struct Curl_cfilter *cf, +static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf, struct Curl_easy *data, bool blocking, bool *done) { + struct cf_haproxy_ctx *ctx = cf->ctx; CURLcode result; + size_t len; + DEBUGASSERT(ctx); if(cf->connected) { *done = TRUE; return CURLE_OK; @@ -1245,28 +1291,120 @@ static CURLcode haproxy_cf_connect(struct Curl_cfilter *cf, if(result || !*done) return result; - result = send_haproxy_header(cf, data); - *done = (!result); + switch(ctx->state) { + case HAPROXY_INIT: + result = cf_haproxy_date_out_set(cf, data); + if(result) + goto out; + ctx->state = HAPROXY_SEND; + /* FALLTHROUGH */ + case HAPROXY_SEND: + len = Curl_dyn_len(&ctx->data_out); + if(len > 0) { + ssize_t written = Curl_conn_send(data, cf->sockindex, + Curl_dyn_ptr(&ctx->data_out), + len, &result); + if(written < 0) + goto out; + Curl_dyn_tail(&ctx->data_out, len - (size_t)written); + if(Curl_dyn_len(&ctx->data_out) > 0) { + result = CURLE_OK; + goto out; + } + } + ctx->state = HAPROXY_DONE; + /* FALLTHROUGH */ + default: + Curl_dyn_free(&ctx->data_out); + break; + } + +out: + *done = (!result) && (ctx->state == HAPROXY_DONE); cf->connected = *done; return result; } -static const struct Curl_cftype cft_haproxy = { +static void cf_haproxy_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)data; + DEBUGF(LOG_CF(data, cf, "destroy")); + cf_haproxy_ctx_free(cf->ctx); +} + +static void cf_haproxy_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + DEBUGF(LOG_CF(data, cf, "close")); + cf->connected = FALSE; + cf_haproxy_ctx_reset(cf->ctx); + if(cf->next) + cf->next->cft->close(cf->next, data); +} + +static int cf_haproxy_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *socks) +{ + int fds; + + fds = cf->next->cft->get_select_socks(cf->next, data, socks); + if(!fds && cf->next->connected && !cf->connected) { + /* If we are not connected, but the filter "below" is + * and not waiting on something, we are sending. */ + socks[0] = Curl_conn_cf_get_socket(cf, data); + return GETSOCK_WRITESOCK(0); + } + return fds; +} + + +struct Curl_cftype Curl_cft_haproxy = { "HAPROXY", 0, - Curl_cf_def_destroy_this, - Curl_cf_def_setup, - haproxy_cf_connect, - Curl_cf_def_close, + 0, + cf_haproxy_destroy, + cf_haproxy_connect, + cf_haproxy_close, Curl_cf_def_get_host, - Curl_cf_def_get_select_socks, + cf_haproxy_get_select_socks, Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, - Curl_cf_def_attach_data, - Curl_cf_def_detach_data, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, }; +static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf = NULL; + struct cf_haproxy_ctx *ctx; + CURLcode result; + + (void)data; + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + ctx->state = HAPROXY_INIT; + Curl_dyn_init(&ctx->data_out, DYN_HAXPROXY); + + result = Curl_cf_create(&cf, &Curl_cft_haproxy, ctx); + if(result) + goto out; + ctx = NULL; + +out: + cf_haproxy_ctx_free(ctx); + *pcf = result? NULL : cf; + return result; +} + CURLcode Curl_conn_haproxy_add(struct Curl_easy *data, struct connectdata *conn, int sockindex) @@ -1274,10 +1412,28 @@ CURLcode Curl_conn_haproxy_add(struct Curl_easy *data, struct Curl_cfilter *cf; CURLcode result; - result = Curl_cf_create(&cf, &cft_haproxy, NULL); - if(!result) - Curl_conn_cf_add(data, conn, sockindex, cf); + result = cf_haproxy_create(&cf, data); + if(result) + goto out; + Curl_conn_cf_add(data, conn, sockindex, cf); + +out: + return result; +} + +CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + result = cf_haproxy_create(&cf, data); + if(result) + goto out; + Curl_conn_cf_insert_after(cf_at, cf); + +out: return result; } -#endif /* !CURL_DISABLE_PROXY &6 ! CURL_DISABLE_HTTP */ +#endif /* !CURL_DISABLE_PROXY */ diff --git a/Utilities/cmcurl/lib/http_proxy.h b/Utilities/cmcurl/lib/http_proxy.h index dfdc0e72ba..f573da273c 100644 --- a/Utilities/cmcurl/lib/http_proxy.h +++ b/Utilities/cmcurl/lib/http_proxy.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -27,8 +27,9 @@ #include "curl_setup.h" #include "urldata.h" -#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) +#if !defined(CURL_DISABLE_PROXY) +#if !defined(CURL_DISABLE_HTTP) /* Default proxy timeout in milliseconds */ #define PROXY_TIMEOUT (3600*1000) @@ -36,10 +37,22 @@ CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data, struct connectdata *conn, int sockindex); +CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); + +extern struct Curl_cftype Curl_cft_http_proxy; + +#endif /* !CURL_DISABLE_HTTP */ + CURLcode Curl_conn_haproxy_add(struct Curl_easy *data, struct connectdata *conn, int sockindex); -#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ +CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); + +extern struct Curl_cftype Curl_cft_haproxy; + +#endif /* !CURL_DISABLE_PROXY */ #endif /* HEADER_CURL_HTTP_PROXY_H */ diff --git a/Utilities/cmcurl/lib/idn.c b/Utilities/cmcurl/lib/idn.c index 6255221ae1..abba895c45 100644 --- a/Utilities/cmcurl/lib/idn.c +++ b/Utilities/cmcurl/lib/idn.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -116,7 +116,7 @@ bool Curl_is_ASCII_name(const char *hostname) * Curl_idn_decode() returns an allocated IDN decoded string if it was * possible. NULL on error. */ -static char *Curl_idn_decode(const char *input) +static char *idn_decode(const char *input) { char *decoded = NULL; #ifdef USE_LIBIDN2 @@ -144,24 +144,29 @@ static char *Curl_idn_decode(const char *input) return decoded; } +char *Curl_idn_decode(const char *input) +{ + char *d = idn_decode(input); +#ifdef USE_LIBIDN2 + if(d) { + char *c = strdup(d); + idn2_free(d); + d = c; + } +#endif + return d; +} + /* * Frees data allocated by idnconvert_hostname() */ void Curl_free_idnconverted_hostname(struct hostname *host) { -#if defined(USE_LIBIDN2) if(host->encalloc) { - idn2_free(host->encalloc); /* must be freed with idn2_free() since this was - allocated by libidn */ + /* must be freed with idn2_free() if allocated by libidn */ + Curl_idn_free(host->encalloc); host->encalloc = NULL; } -#elif defined(USE_WIN32_IDN) - free(host->encalloc); /* must be freed with free() since this was - allocated by Curl_win32_idn_to_ascii */ - host->encalloc = NULL; -#else - (void)host; -#endif } #endif /* USE_IDN */ @@ -177,7 +182,7 @@ CURLcode Curl_idnconvert_hostname(struct hostname *host) #ifdef USE_IDN /* Check name for non-ASCII and convert hostname if we can */ if(!Curl_is_ASCII_name(host->name)) { - char *decoded = Curl_idn_decode(host->name); + char *decoded = idn_decode(host->name); if(decoded) { /* successful */ host->encalloc = decoded; @@ -190,4 +195,3 @@ CURLcode Curl_idnconvert_hostname(struct hostname *host) #endif return CURLE_OK; } - diff --git a/Utilities/cmcurl/lib/idn.h b/Utilities/cmcurl/lib/idn.h index 2d04efcdff..6c0bbb7109 100644 --- a/Utilities/cmcurl/lib/idn.h +++ b/Utilities/cmcurl/lib/idn.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -32,7 +32,15 @@ CURLcode Curl_idnconvert_hostname(struct hostname *host); #if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) #define USE_IDN void Curl_free_idnconverted_hostname(struct hostname *host); +char *Curl_idn_decode(const char *input); +#ifdef USE_LIBIDN2 +#define Curl_idn_free(x) idn2_free(x) +#else +#define Curl_idn_free(x) free(x) +#endif + #else #define Curl_free_idnconverted_hostname(x) +#define Curl_idn_decode(x) NULL #endif #endif /* HEADER_CURL_IDN_H */ diff --git a/Utilities/cmcurl/lib/if2ip.c b/Utilities/cmcurl/lib/if2ip.c index c29194878f..6bf0ce16f7 100644 --- a/Utilities/cmcurl/lib/if2ip.c +++ b/Utilities/cmcurl/lib/if2ip.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/if2ip.h b/Utilities/cmcurl/lib/if2ip.h index 5d15459e98..1f973505c0 100644 --- a/Utilities/cmcurl/lib/if2ip.h +++ b/Utilities/cmcurl/lib/if2ip.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2020, 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/imap.c b/Utilities/cmcurl/lib/imap.c index bd4c6f2203..c2f675d4b2 100644 --- a/Utilities/cmcurl/lib/imap.c +++ b/Utilities/cmcurl/lib/imap.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -475,15 +475,17 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, /* Start the SSL connection */ struct imap_conn *imapc = &conn->proto.imapc; CURLcode result; + bool ssldone = FALSE; - if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) { + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); if(result) goto out; } - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &imapc->ssldone); + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); if(!result) { + imapc->ssldone = ssldone; if(imapc->state != IMAP_UPGRADETLS) state(data, IMAP_UPGRADETLS); @@ -952,7 +954,7 @@ static CURLcode imap_state_capability_resp(struct Curl_easy *data, line += wordlen; } } - else if(data->set.use_ssl && !Curl_conn_is_ssl(data, FIRSTSOCKET)) { + else if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) { /* PREAUTH is not compatible with STARTTLS. */ if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) { /* Switch to TLS connection now */ @@ -1386,8 +1388,10 @@ static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done) struct imap_conn *imapc = &conn->proto.imapc; if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) { - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &imapc->ssldone); - if(result || !imapc->ssldone) + bool ssldone = FALSE; + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + imapc->ssldone = ssldone; + if(result || !ssldone) return result; } @@ -1774,7 +1778,7 @@ static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) /* Calculate the tag based on the connection ID and command ID */ msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d", 'A' + curlx_sltosi(data->conn->connection_id % 26), - (++imapc->cmdid)%1000); + ++imapc->cmdid); /* start with a blank buffer */ Curl_dyn_reset(&imapc->dyn); diff --git a/Utilities/cmcurl/lib/imap.h b/Utilities/cmcurl/lib/imap.h index 43cc1e98fc..784ee97e55 100644 --- a/Utilities/cmcurl/lib/imap.h +++ b/Utilities/cmcurl/lib/imap.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2009 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -72,19 +72,19 @@ struct IMAP { struct */ struct imap_conn { struct pingpong pp; - imapstate state; /* Always use imap.c:state() to change state! */ - bool ssldone; /* Is connect() over SSL done? */ - bool preauth; /* Is this connection PREAUTH? */ struct SASL sasl; /* SASL-related parameters */ - unsigned int preftype; /* Preferred authentication type */ - unsigned int cmdid; /* Last used command ID */ - char resptag[5]; /* Response tag to wait for */ - bool tls_supported; /* StartTLS capability supported by server */ - bool login_disabled; /* LOGIN command disabled by server */ - bool ir_supported; /* Initial response supported by server */ + struct dynbuf dyn; /* for the IMAP commands */ char *mailbox; /* The last selected mailbox */ char *mailbox_uidvalidity; /* UIDVALIDITY parsed from select response */ - struct dynbuf dyn; /* for the IMAP commands */ + imapstate state; /* Always use imap.c:state() to change state! */ + char resptag[5]; /* Response tag to wait for */ + unsigned char preftype; /* Preferred authentication type */ + unsigned char cmdid; /* Last used command ID */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(preauth); /* Is this connection PREAUTH? */ + BIT(tls_supported); /* StartTLS capability supported by server */ + BIT(login_disabled); /* LOGIN command disabled by server */ + BIT(ir_supported); /* Initial response supported by server */ }; extern const struct Curl_handler Curl_handler_imap; @@ -96,6 +96,6 @@ extern const struct Curl_handler Curl_handler_imaps; /* Authentication type values */ #define IMAP_TYPE_NONE 0 -#define IMAP_TYPE_ANY ~0U +#define IMAP_TYPE_ANY (IMAP_TYPE_CLEARTEXT|IMAP_TYPE_SASL) #endif /* HEADER_CURL_IMAP_H */ diff --git a/Utilities/cmcurl/lib/inet_ntop.h b/Utilities/cmcurl/lib/inet_ntop.h index 18fbd8ba3a..7c3ead4341 100644 --- a/Utilities/cmcurl/lib/inet_ntop.h +++ b/Utilities/cmcurl/lib/inet_ntop.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/inet_pton.c b/Utilities/cmcurl/lib/inet_pton.c index 47fb77834d..f2e17b8743 100644 --- a/Utilities/cmcurl/lib/inet_pton.c +++ b/Utilities/cmcurl/lib/inet_pton.c @@ -1,6 +1,6 @@ /* This is from the BIND 4.9.4 release, modified to compile by itself */ -/* Copyright (c) 2003 - 2022 by Internet Software Consortium. +/* Copyright (c) Internet Software Consortium. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/Utilities/cmcurl/lib/inet_pton.h b/Utilities/cmcurl/lib/inet_pton.h index 92ae93ea1f..82fde7e2eb 100644 --- a/Utilities/cmcurl/lib/inet_pton.h +++ b/Utilities/cmcurl/lib/inet_pton.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/krb5.c b/Utilities/cmcurl/lib/krb5.c index 08a682533d..3cd64e110f 100644 --- a/Utilities/cmcurl/lib/krb5.c +++ b/Utilities/cmcurl/lib/krb5.c @@ -2,7 +2,7 @@ * * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). - * Copyright (c) 2004 - 2022 Daniel Stenberg + * Copyright (C) Daniel Stenberg * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause @@ -46,6 +46,8 @@ #endif #include "urldata.h" +#include "cfilters.h" +#include "cf-socket.h" #include "curl_base64.h" #include "ftp.h" #include "curl_gssapi.h" @@ -207,8 +209,8 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) gss_ctx_id_t *context = app_data; struct gss_channel_bindings_struct chan; size_t base64_sz = 0; - struct sockaddr_in **remote_addr = - (struct sockaddr_in **)&conn->ip_addr->ai_addr; + struct sockaddr_in *remote_addr = + (struct sockaddr_in *)(void *)&conn->remote_addr->sa_addr; char *stringp; if(getsockname(conn->sock[FIRSTSOCKET], @@ -220,7 +222,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn) chan.initiator_address.value = &conn->local_addr.sin_addr.s_addr; chan.acceptor_addrtype = GSS_C_AF_INET; chan.acceptor_address.length = l - 4; - chan.acceptor_address.value = &(*remote_addr)->sin_addr.s_addr; + chan.acceptor_address.value = &remote_addr->sin_addr.s_addr; chan.application_data.length = 0; chan.application_data.value = NULL; @@ -454,15 +456,15 @@ static int ftp_send_command(struct Curl_easy *data, const char *message, ...) /* Read |len| from the socket |fd| and store it in |to|. Return a CURLcode saying whether an error occurred or CURLE_OK if |len| was read. */ static CURLcode -socket_read(struct Curl_easy *data, curl_socket_t fd, void *to, size_t len) +socket_read(struct Curl_easy *data, int sockindex, void *to, size_t len) { char *to_p = to; CURLcode result; ssize_t nread = 0; while(len > 0) { - result = Curl_read_plain(data, fd, to_p, len, &nread); - if(!result) { + nread = Curl_conn_recv(data, sockindex, to_p, len, &result); + if(nread > 0) { len -= nread; to_p += nread; } @@ -480,7 +482,7 @@ socket_read(struct Curl_easy *data, curl_socket_t fd, void *to, size_t len) CURLcode saying whether an error occurred or CURLE_OK if |len| was written. */ static CURLcode -socket_write(struct Curl_easy *data, curl_socket_t fd, const void *to, +socket_write(struct Curl_easy *data, int sockindex, const void *to, size_t len) { const char *to_p = to; @@ -488,8 +490,8 @@ socket_write(struct Curl_easy *data, curl_socket_t fd, const void *to, ssize_t written; while(len > 0) { - result = Curl_write_plain(data, fd, to_p, len, &written); - if(!result) { + written = Curl_conn_send(data, sockindex, to_p, len, &result); + if(written > 0) { len -= written; to_p += written; } @@ -502,7 +504,7 @@ socket_write(struct Curl_easy *data, curl_socket_t fd, const void *to, return CURLE_OK; } -static CURLcode read_data(struct Curl_easy *data, curl_socket_t fd, +static CURLcode read_data(struct Curl_easy *data, int sockindex, struct krb5buffer *buf) { struct connectdata *conn = data->conn; @@ -510,7 +512,7 @@ static CURLcode read_data(struct Curl_easy *data, curl_socket_t fd, CURLcode result; int nread; - result = socket_read(data, fd, &len, sizeof(len)); + result = socket_read(data, sockindex, &len, sizeof(len)); if(result) return result; @@ -525,7 +527,7 @@ static CURLcode read_data(struct Curl_easy *data, curl_socket_t fd, if(!len || !buf->data) return CURLE_OUT_OF_MEMORY; - result = socket_read(data, fd, buf->data, len); + result = socket_read(data, sockindex, buf->data, len); if(result) return result; nread = conn->mech->decode(conn->app_data, buf->data, len, @@ -554,13 +556,12 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex, size_t bytes_read; size_t total_read = 0; struct connectdata *conn = data->conn; - curl_socket_t fd = conn->sock[sockindex]; *err = CURLE_OK; /* Handle clear text response. */ if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) - return Curl_recv_plain(data, sockindex, buffer, len, err); + return Curl_conn_recv(data, sockindex, buffer, len, err); if(conn->in_buffer.eof_flag) { conn->in_buffer.eof_flag = 0; @@ -573,7 +574,7 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex, buffer += bytes_read; while(len > 0) { - if(read_data(data, fd, &conn->in_buffer)) + if(read_data(data, sockindex, &conn->in_buffer)) return -1; if(conn->in_buffer.size == 0) { if(bytes_read > 0) diff --git a/Utilities/cmcurl/lib/ldap.c b/Utilities/cmcurl/lib/ldap.c index 92006d6936..5e53f4c511 100644 --- a/Utilities/cmcurl/lib/ldap.c +++ b/Utilities/cmcurl/lib/ldap.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/libcurl.rc b/Utilities/cmcurl/lib/libcurl.rc index 23134a7102..daa2d62d8a 100644 --- a/Utilities/cmcurl/lib/libcurl.rc +++ b/Utilities/cmcurl/lib/libcurl.rc @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/llist.c b/Utilities/cmcurl/lib/llist.c index fa2d366cb4..5b6b0336da 100644 --- a/Utilities/cmcurl/lib/llist.c +++ b/Utilities/cmcurl/lib/llist.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/llist.h b/Utilities/cmcurl/lib/llist.h index 2fcb91ca50..320580e33c 100644 --- a/Utilities/cmcurl/lib/llist.h +++ b/Utilities/cmcurl/lib/llist.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/md4.c b/Utilities/cmcurl/lib/md4.c index c13b080d69..318e9da6ae 100644 --- a/Utilities/cmcurl/lib/md4.c +++ b/Utilities/cmcurl/lib/md4.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -86,11 +86,7 @@ #include "memdebug.h" -#if defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4) - -#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4) - -#elif defined(USE_GNUTLS) +#if defined(USE_GNUTLS) typedef struct md4_ctx MD4_CTX; @@ -109,6 +105,10 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) md4_digest(ctx, MD4_DIGEST_SIZE, result); } +#elif defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4) + +#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4) + #elif defined(AN_APPLE_OS) typedef CC_MD4_CTX MD4_CTX; diff --git a/Utilities/cmcurl/lib/md5.c b/Utilities/cmcurl/lib/md5.c index 9610e0c778..f57ef391cc 100644 --- a/Utilities/cmcurl/lib/md5.c +++ b/Utilities/cmcurl/lib/md5.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/memdebug.c b/Utilities/cmcurl/lib/memdebug.c index 15fb491559..d6952a07a1 100644 --- a/Utilities/cmcurl/lib/memdebug.c +++ b/Utilities/cmcurl/lib/memdebug.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/memdebug.h b/Utilities/cmcurl/lib/memdebug.h index 7fc90e83a0..c9eb5dc37c 100644 --- a/Utilities/cmcurl/lib/memdebug.h +++ b/Utilities/cmcurl/lib/memdebug.h @@ -8,7 +8,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/mime.c b/Utilities/cmcurl/lib/mime.c index e3f2821dbe..83846c57dd 100644 --- a/Utilities/cmcurl/lib/mime.c +++ b/Utilities/cmcurl/lib/mime.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/mime.h b/Utilities/cmcurl/lib/mime.h index b9ea0f1011..04adf2d247 100644 --- a/Utilities/cmcurl/lib/mime.h +++ b/Utilities/cmcurl/lib/mime.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/mprintf.c b/Utilities/cmcurl/lib/mprintf.c index 8a7c17a7ff..5de935b1fe 100644 --- a/Utilities/cmcurl/lib/mprintf.c +++ b/Utilities/cmcurl/lib/mprintf.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1999 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/mqtt.c b/Utilities/cmcurl/lib/mqtt.c index 8ba826f4fb..0b54bc0f83 100644 --- a/Utilities/cmcurl/lib/mqtt.c +++ b/Utilities/cmcurl/lib/mqtt.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2019, Björn Stenberg, <bjorn@haxx.se> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Björn Stenberg, <bjorn@haxx.se> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/mqtt.h b/Utilities/cmcurl/lib/mqtt.h index c400d9b14e..63961366fc 100644 --- a/Utilities/cmcurl/lib/mqtt.h +++ b/Utilities/cmcurl/lib/mqtt.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2019 - 2022, Björn Stenberg, <bjorn@haxx.se> + * Copyright (C) Björn Stenberg, <bjorn@haxx.se> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/multi.c b/Utilities/cmcurl/lib/multi.c index b96ee7c7ea..f020a0b64a 100644 --- a/Utilities/cmcurl/lib/multi.c +++ b/Utilities/cmcurl/lib/multi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -655,6 +655,9 @@ static CURLcode multi_done(struct Curl_easy *data, result = CURLE_ABORTED_BY_CALLBACK; } + /* Inform connection filters that this transfer is done */ + Curl_conn_ev_data_done(data, premature); + process_pending_handles(data->multi); /* connection / multiplex */ CONNCACHE_LOCK(data); @@ -709,12 +712,12 @@ static CURLcode multi_done(struct Curl_easy *data, conn->proxy_negotiate_state == GSS_AUTHRECV) #endif ) || conn->bits.close - || (premature && !(conn->handler->flags & PROTOPT_STREAM))) { + || (premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) { DEBUGF(infof(data, "multi_done, not re-using connection=%ld, forbid=%d" - ", close=%d, premature=%d, stream=%d", + ", close=%d, premature=%d, conn_multiplex=%d", conn->connection_id, data->set.reuse_forbid, conn->bits.close, premature, - (conn->handler->flags & PROTOPT_STREAM))); + Curl_conn_is_multiplex(conn, FIRSTSOCKET))); connclose(conn, "disconnecting"); Curl_conncache_remove_conn(data, conn, FALSE); CONNCACHE_UNLOCK(data); @@ -954,7 +957,7 @@ void Curl_detach_connection(struct Curl_easy *data) { struct connectdata *conn = data->conn; if(conn) { - Curl_conn_detach_data(conn, data); + Curl_conn_ev_data_detach(conn, data); Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL); } data->conn = NULL; @@ -973,9 +976,9 @@ void Curl_attach_connection(struct Curl_easy *data, data->conn = conn; Curl_llist_insert_next(&conn->easyq, conn->easyq.tail, data, &data->conn_queue); - Curl_conn_attach_data(conn, data); if(conn->handler->attach) conn->handler->attach(data, conn); + Curl_conn_ev_data_attach(conn, data); } static int domore_getsock(struct Curl_easy *data, @@ -1002,11 +1005,7 @@ static int protocol_getsock(struct Curl_easy *data, { if(conn->handler->proto_getsock) return conn->handler->proto_getsock(data, conn, socks); - /* Backup getsock logic. Since there is a live socket in use, we must wait - for it or it will be removed from watching when the multi_socket API is - used. */ - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_READSOCK(0) | GETSOCK_WRITESOCK(0); + return Curl_conn_get_select_socks(data, FIRSTSOCKET, socks); } /* returns bitmapped flags for this handle and its sockets. The 'socks[]' @@ -1111,6 +1110,22 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, return CURLM_OK; } +#ifdef USE_WINSOCK +/* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets can't + * be reset this way because an empty datagram would be sent. #9203 + * + * "On Windows the internal state of FD_WRITE as returned from + * WSAEnumNetworkEvents is only reset after successful send()." + */ +static void reset_socket_fdwrite(curl_socket_t s) +{ + int t; + int l = (int)sizeof(t); + if(!getsockopt(s, SOL_SOCKET, SO_TYPE, (char *)&t, &l) && t == SOCK_STREAM) + send(s, NULL, 0, 0); +} +#endif + #define NUM_POLLS_ON_STACK 10 static CURLMcode multi_wait(struct Curl_multi *multi, @@ -1232,7 +1247,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, s = sockbunch[i]; #ifdef USE_WINSOCK mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; - send(s, NULL, 0, 0); /* reset FD_WRITE */ + reset_socket_fdwrite(s); #endif ufds[nfds].fd = s; ufds[nfds].events = POLLOUT; @@ -1266,7 +1281,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, mask |= FD_OOB; if(extra_fds[i].events & CURL_WAIT_POLLOUT) { mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; - send(extra_fds[i].fd, NULL, 0, 0); /* reset FD_WRITE */ + reset_socket_fdwrite(extra_fds[i].fd); } if(WSAEventSelect(extra_fds[i].fd, multi->wsa_event, mask) != 0) { if(ufds_malloc) @@ -1862,6 +1877,15 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, multistate(data, MSTATE_COMPLETED); } +#ifdef DEBUGBUILD + if(!multi->warned) { + infof(data, "!!! WARNING !!!"); + infof(data, "This is a debug build of libcurl, " + "do not use in production."); + multi->warned = true; + } +#endif + do { /* A "stream" here is a logical stream if the protocol can handle that (HTTP/2), or the full connection for older protocols */ @@ -3248,7 +3272,7 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, multi->push_userp = va_arg(param, void *); break; case CURLMOPT_PIPELINING: - multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX; + multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX ? 1 : 0; break; case CURLMOPT_TIMERFUNCTION: multi->timer_cb = va_arg(param, curl_multi_timer_callback); diff --git a/Utilities/cmcurl/lib/multihandle.h b/Utilities/cmcurl/lib/multihandle.h index 5a83656d51..6cda65d442 100644 --- a/Utilities/cmcurl/lib/multihandle.h +++ b/Utilities/cmcurl/lib/multihandle.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -162,14 +162,17 @@ struct Curl_multi { #define IPV6_DEAD 1 #define IPV6_WORKS 2 unsigned char ipv6_up; /* IPV6_* defined */ - bool multiplexing; /* multiplexing wanted */ - bool recheckstate; /* see Curl_multi_connchanged */ - bool in_callback; /* true while executing a callback */ + BIT(multiplexing); /* multiplexing wanted */ + BIT(recheckstate); /* see Curl_multi_connchanged */ + BIT(in_callback); /* true while executing a callback */ #ifdef USE_OPENSSL - bool ssl_seeded; + BIT(ssl_seeded); #endif - bool dead; /* a callback returned error, everything needs to crash and + BIT(dead); /* a callback returned error, everything needs to crash and burn */ +#ifdef DEBUGBUILD + BIT(warned); /* true after user warned of DEBUGBUILD */ +#endif }; #endif /* HEADER_CURL_MULTIHANDLE_H */ diff --git a/Utilities/cmcurl/lib/multiif.h b/Utilities/cmcurl/lib/multiif.h index 0cb9d4f7f2..cae02cb08c 100644 --- a/Utilities/cmcurl/lib/multiif.h +++ b/Utilities/cmcurl/lib/multiif.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/netrc.c b/Utilities/cmcurl/lib/netrc.c index 4461b8492f..aa1b80ab2f 100644 --- a/Utilities/cmcurl/lib/netrc.c +++ b/Utilities/cmcurl/lib/netrc.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/netrc.h b/Utilities/cmcurl/lib/netrc.h index 53d0056721..9f2815f3bb 100644 --- a/Utilities/cmcurl/lib/netrc.h +++ b/Utilities/cmcurl/lib/netrc.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/nonblock.c b/Utilities/cmcurl/lib/nonblock.c index 8447b6f423..f4eb656128 100644 --- a/Utilities/cmcurl/lib/nonblock.c +++ b/Utilities/cmcurl/lib/nonblock.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/nonblock.h b/Utilities/cmcurl/lib/nonblock.h index a42f443a49..4a1a6151f2 100644 --- a/Utilities/cmcurl/lib/nonblock.h +++ b/Utilities/cmcurl/lib/nonblock.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/noproxy.c b/Utilities/cmcurl/lib/noproxy.c index 9b13fe8956..f1c1ed2c63 100644 --- a/Utilities/cmcurl/lib/noproxy.c +++ b/Utilities/cmcurl/lib/noproxy.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -119,8 +119,10 @@ enum nametype { * Checks if the host is in the noproxy list. returns TRUE if it matches and * therefore the proxy should NOT be used. ****************************************************************/ -bool Curl_check_noproxy(const char *name, const char *no_proxy) +bool Curl_check_noproxy(const char *name, const char *no_proxy, + bool *spacesep) { + *spacesep = FALSE; /* * If we don't have a hostname at all, like for example with a FILE * transfer, we have nothing to interrogate the noproxy list with. @@ -244,6 +246,15 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy) if(match) return TRUE; } /* if(tokenlen) */ + /* pass blanks after pattern */ + while(ISBLANK(*p)) + p++; + /* if not a comma! */ + if(*p && (*p != ',')) { + *spacesep = TRUE; + continue; + } + /* pass any number of commas */ while(*p == ',') p++; } /* while(*p) */ diff --git a/Utilities/cmcurl/lib/noproxy.h b/Utilities/cmcurl/lib/noproxy.h index 8800a21276..a3a6807722 100644 --- a/Utilities/cmcurl/lib/noproxy.h +++ b/Utilities/cmcurl/lib/noproxy.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -37,7 +37,8 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6, unsigned int bits); #endif -bool Curl_check_noproxy(const char *name, const char *no_proxy); +bool Curl_check_noproxy(const char *name, const char *no_proxy, + bool *spacesep); #endif diff --git a/Utilities/cmcurl/lib/openldap.c b/Utilities/cmcurl/lib/openldap.c index ab81e57c26..b9feeda058 100644 --- a/Utilities/cmcurl/lib/openldap.c +++ b/Utilities/cmcurl/lib/openldap.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2010, Howard Chu, <hyc@openldap.org> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Howard Chu, <hyc@openldap.org> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/parsedate.c b/Utilities/cmcurl/lib/parsedate.c index 5ed88195fb..82ac1d8928 100644 --- a/Utilities/cmcurl/lib/parsedate.c +++ b/Utilities/cmcurl/lib/parsedate.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/parsedate.h b/Utilities/cmcurl/lib/parsedate.h index 4e4347754d..84c37f1675 100644 --- a/Utilities/cmcurl/lib/parsedate.h +++ b/Utilities/cmcurl/lib/parsedate.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/pingpong.c b/Utilities/cmcurl/lib/pingpong.c index 9b9558048a..2f4aa1c346 100644 --- a/Utilities/cmcurl/lib/pingpong.c +++ b/Utilities/cmcurl/lib/pingpong.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/pingpong.h b/Utilities/cmcurl/lib/pingpong.h index cefae073a6..80d3f7718c 100644 --- a/Utilities/cmcurl/lib/pingpong.h +++ b/Utilities/cmcurl/lib/pingpong.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/pop3.c b/Utilities/cmcurl/lib/pop3.c index ce17f2ac72..36707e5352 100644 --- a/Utilities/cmcurl/lib/pop3.c +++ b/Utilities/cmcurl/lib/pop3.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -370,16 +370,18 @@ static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data, /* Start the SSL connection */ struct pop3_conn *pop3c = &conn->proto.pop3c; CURLcode result; + bool ssldone = FALSE; - if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) { + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); if(result) goto out; } - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &pop3c->ssldone); + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); if(!result) { + pop3c->ssldone = ssldone; if(pop3c->state != POP3_UPGRADETLS) state(data, POP3_UPGRADETLS); @@ -769,7 +771,7 @@ static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code, if(pop3code != '+') pop3c->authtypes |= POP3_TYPE_CLEARTEXT; - if(!data->set.use_ssl || Curl_conn_is_ssl(data, FIRSTSOCKET)) + if(!data->set.use_ssl || Curl_conn_is_ssl(conn, FIRSTSOCKET)) result = pop3_perform_authentication(data, conn); else if(pop3code == '+' && pop3c->tls_supported) /* Switch to TLS connection now */ @@ -1056,7 +1058,9 @@ static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done) struct pop3_conn *pop3c = &conn->proto.pop3c; if((conn->handler->flags & PROTOPT_SSL) && !pop3c->ssldone) { - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &pop3c->ssldone); + bool ssldone = FALSE; + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + pop3c->ssldone = ssldone; if(result || !pop3c->ssldone) return result; } diff --git a/Utilities/cmcurl/lib/pop3.h b/Utilities/cmcurl/lib/pop3.h index bb0645f9bf..83f0f831e6 100644 --- a/Utilities/cmcurl/lib/pop3.h +++ b/Utilities/cmcurl/lib/pop3.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2009 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -62,16 +62,16 @@ struct POP3 { struct pop3_conn { struct pingpong pp; pop3state state; /* Always use pop3.c:state() to change state! */ - bool ssldone; /* Is connect() over SSL done? */ - bool tls_supported; /* StartTLS capability supported by server */ size_t eob; /* Number of bytes of the EOB (End Of Body) that have been received so far */ size_t strip; /* Number of bytes from the start to ignore as non-body */ struct SASL sasl; /* SASL-related storage */ - unsigned int authtypes; /* Accepted authentication types */ - unsigned int preftype; /* Preferred authentication type */ char *apoptimestamp; /* APOP timestamp from the server greeting */ + unsigned char authtypes; /* Accepted authentication types */ + unsigned char preftype; /* Preferred authentication type */ + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(tls_supported); /* StartTLS capability supported by server */ }; extern const struct Curl_handler Curl_handler_pop3; @@ -84,7 +84,7 @@ extern const struct Curl_handler Curl_handler_pop3s; /* Authentication type values */ #define POP3_TYPE_NONE 0 -#define POP3_TYPE_ANY ~0U +#define POP3_TYPE_ANY (POP3_TYPE_CLEARTEXT|POP3_TYPE_APOP|POP3_TYPE_SASL) /* This is the 5-bytes End-Of-Body marker for POP3 */ #define POP3_EOB "\x0d\x0a\x2e\x0d\x0a" diff --git a/Utilities/cmcurl/lib/progress.c b/Utilities/cmcurl/lib/progress.c index 4a1e1daa81..a2228888e6 100644 --- a/Utilities/cmcurl/lib/progress.c +++ b/Utilities/cmcurl/lib/progress.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -166,14 +166,11 @@ void Curl_pgrsResetTransferSizes(struct Curl_easy *data) /* * - * Curl_pgrsTime(). Store the current time at the given label. This fetches a - * fresh "now" and returns it. - * - * @unittest: 1399 + * Curl_pgrsTimeWas(). Store the timestamp time at the given label. */ -struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) +void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, + struct curltime timestamp) { - struct curltime now = Curl_now(); timediff_t *delta = NULL; switch(timer) { @@ -183,15 +180,15 @@ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) break; case TIMER_STARTOP: /* This is set at the start of a transfer */ - data->progress.t_startop = now; + data->progress.t_startop = timestamp; break; case TIMER_STARTSINGLE: /* This is set at the start of each single fetch */ - data->progress.t_startsingle = now; + data->progress.t_startsingle = timestamp; data->progress.is_t_startransfer_set = false; break; case TIMER_STARTACCEPT: - data->progress.t_acceptdata = now; + data->progress.t_acceptdata = timestamp; break; case TIMER_NAMELOOKUP: delta = &data->progress.t_nslookup; @@ -214,7 +211,7 @@ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) * changing the t_starttransfer time. */ if(data->progress.is_t_startransfer_set) { - return now; + return; } else { data->progress.is_t_startransfer_set = true; @@ -224,15 +221,30 @@ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) /* this is the normal end-of-transfer thing */ break; case TIMER_REDIRECT: - data->progress.t_redirect = Curl_timediff_us(now, data->progress.start); + data->progress.t_redirect = Curl_timediff_us(timestamp, + data->progress.start); break; } if(delta) { - timediff_t us = Curl_timediff_us(now, data->progress.t_startsingle); + timediff_t us = Curl_timediff_us(timestamp, data->progress.t_startsingle); if(us < 1) us = 1; /* make sure at least one microsecond passed */ *delta += us; } +} + +/* + * + * Curl_pgrsTime(). Store the current time at the given label. This fetches a + * fresh "now" and returns it. + * + * @unittest: 1399 + */ +struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer) +{ + struct curltime now = Curl_now(); + + Curl_pgrsTimeWas(data, timer, now); return now; } diff --git a/Utilities/cmcurl/lib/progress.h b/Utilities/cmcurl/lib/progress.h index a129315147..0049cd04be 100644 --- a/Utilities/cmcurl/lib/progress.h +++ b/Utilities/cmcurl/lib/progress.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -57,6 +57,13 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize, curl_off_t limit, struct curltime start, struct curltime now); +/** + * Update progress timer with the elapsed time from its start to `timestamp`. + * This allows updating timers later and is used by happy eyeballing, where + * we only want to record the winner's times. + */ +void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, + struct curltime timestamp); #define PGRS_HIDE (1<<4) #define PGRS_UL_SIZE_KNOWN (1<<5) diff --git a/Utilities/cmcurl/lib/psl.c b/Utilities/cmcurl/lib/psl.c index 60c98a4ca4..626a203a6d 100644 --- a/Utilities/cmcurl/lib/psl.c +++ b/Utilities/cmcurl/lib/psl.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/psl.h b/Utilities/cmcurl/lib/psl.h index 34f0a5cae8..23cfa921c4 100644 --- a/Utilities/cmcurl/lib/psl.h +++ b/Utilities/cmcurl/lib/psl.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/quic.h b/Utilities/cmcurl/lib/quic.h deleted file mode 100644 index b35774735e..0000000000 --- a/Utilities/cmcurl/lib/quic.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef HEADER_CURL_QUIC_H -#define HEADER_CURL_QUIC_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, 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" - -#ifdef ENABLE_QUIC -#ifdef USE_NGTCP2 -#include "vquic/ngtcp2.h" -#endif -#ifdef USE_QUICHE -#include "vquic/quiche.h" -#endif -#ifdef USE_MSH3 -#include "vquic/msh3.h" -#endif - -#include "urldata.h" - -/* functions provided by the specific backends */ -CURLcode Curl_quic_connect(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t sockfd, - int sockindex, - const struct sockaddr *addr, - socklen_t addrlen); -CURLcode Curl_quic_is_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *connected); -void Curl_quic_ver(char *p, size_t len); -CURLcode Curl_quic_done_sending(struct Curl_easy *data); -void Curl_quic_done(struct Curl_easy *data, bool premature); -bool Curl_quic_data_pending(const struct Curl_easy *data); -void Curl_quic_disconnect(struct Curl_easy *data, - struct connectdata *conn, int tempindex); -CURLcode Curl_quic_idle(struct Curl_easy *data); - -#else /* ENABLE_QUIC */ -#define Curl_quic_done_sending(x) -#define Curl_quic_done(x,y) -#define Curl_quic_data_pending(x) -#define Curl_quic_disconnect(x,y,z) -#endif /* !ENABLE_QUIC */ - -#endif /* HEADER_CURL_QUIC_H */ diff --git a/Utilities/cmcurl/lib/rand.c b/Utilities/cmcurl/lib/rand.c index a549624a86..4b6ac072e6 100644 --- a/Utilities/cmcurl/lib/rand.c +++ b/Utilities/cmcurl/lib/rand.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/rand.h b/Utilities/cmcurl/lib/rand.h index 30fc29615a..cbe05677a1 100644 --- a/Utilities/cmcurl/lib/rand.h +++ b/Utilities/cmcurl/lib/rand.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/rename.c b/Utilities/cmcurl/lib/rename.c index cfb3699fb7..97a66e947e 100644 --- a/Utilities/cmcurl/lib/rename.c +++ b/Utilities/cmcurl/lib/rename.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/rename.h b/Utilities/cmcurl/lib/rename.h index 9958e2cd23..04440820c5 100644 --- a/Utilities/cmcurl/lib/rename.h +++ b/Utilities/cmcurl/lib/rename.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/rtsp.c b/Utilities/cmcurl/lib/rtsp.c index 75e620d118..9d27929e84 100644 --- a/Utilities/cmcurl/lib/rtsp.c +++ b/Utilities/cmcurl/lib/rtsp.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -38,6 +38,7 @@ #include "strcase.h" #include "select.h" #include "connect.h" +#include "cfilters.h" #include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -134,36 +135,6 @@ static CURLcode rtsp_setup_connection(struct Curl_easy *data, /* - * The server may send us RTP data at any point, and RTSPREQ_RECEIVE does not - * want to block the application forever while receiving a stream. Therefore, - * we cannot assume that an RTSP socket is dead just because it is readable. - * - * Instead, if it is readable, run Curl_connalive() to peek at the socket - * and distinguish between closed and data. - */ -static bool rtsp_connisdead(struct Curl_easy *data, struct connectdata *check) -{ - int sval; - bool ret_val = TRUE; - - sval = SOCKET_READABLE(check->sock[FIRSTSOCKET], 0); - if(sval == 0) { - /* timeout */ - ret_val = FALSE; - } - else if(sval & CURL_CSELECT_ERR) { - /* socket is in an error state */ - ret_val = TRUE; - } - else if(sval & CURL_CSELECT_IN) { - /* readable with no error. could still be closed */ - ret_val = !Curl_connalive(data, check); - } - - return ret_val; -} - -/* * Function to check on various aspects of a connection. */ static unsigned int rtsp_conncheck(struct Curl_easy *data, @@ -174,7 +145,7 @@ static unsigned int rtsp_conncheck(struct Curl_easy *data, (void)data; if(checks_to_perform & CONNCHECK_ISDEAD) { - if(rtsp_connisdead(data, conn)) + if(!Curl_conn_is_alive(data, conn)) ret_val |= CONNRESULT_DEAD; } @@ -592,7 +563,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) } /* issue the request */ - result = Curl_buffer_send(&req_buffer, data, + result = Curl_buffer_send(&req_buffer, data, data->req.p.http, &data->info.request_size, 0, FIRSTSOCKET); if(result) { failf(data, "Failed sending RTSP request"); diff --git a/Utilities/cmcurl/lib/rtsp.h b/Utilities/cmcurl/lib/rtsp.h index fa6606ac92..6e55616b38 100644 --- a/Utilities/cmcurl/lib/rtsp.h +++ b/Utilities/cmcurl/lib/rtsp.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/select.c b/Utilities/cmcurl/lib/select.c index 2ac0746772..3b8d468bd8 100644 --- a/Utilities/cmcurl/lib/select.c +++ b/Utilities/cmcurl/lib/select.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/select.h b/Utilities/cmcurl/lib/select.h index f2cf8bbd9f..5b1ca23eb1 100644 --- a/Utilities/cmcurl/lib/select.h +++ b/Utilities/cmcurl/lib/select.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/sendf.c b/Utilities/cmcurl/lib/sendf.c index 63262409bb..2b08271687 100644 --- a/Utilities/cmcurl/lib/sendf.c +++ b/Utilities/cmcurl/lib/sendf.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -138,149 +138,6 @@ static size_t convert_lineends(struct Curl_easy *data, } #endif /* CURL_DO_LINEEND_CONV && !CURL_DISABLE_FTP */ -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND -bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex) -{ - struct postponed_data * const psnd = &(conn->postponed[sockindex]); - return psnd->buffer && psnd->allocated_size && - psnd->recv_size > psnd->recv_processed; -} - -static CURLcode pre_receive_plain(struct Curl_easy *data, - struct connectdata *conn, int num) -{ - const curl_socket_t sockfd = conn->sock[num]; - struct postponed_data * const psnd = &(conn->postponed[num]); - size_t bytestorecv = psnd->allocated_size - psnd->recv_size; - ssize_t recvedbytes; - - /* WinSock will destroy unread received data if send() is - failed. - To avoid lossage of received data, recv() must be - performed before every send() if any incoming data is - available. However, skip this, if buffer is already full. */ - if((conn->handler->protocol&PROTO_FAMILY_HTTP) != 0 && - conn->recv[num] == Curl_conn_recv && - (!psnd->buffer || bytestorecv)) { - const int readymask = Curl_socket_check(sockfd, CURL_SOCKET_BAD, - CURL_SOCKET_BAD, 0); - if(readymask != -1 && (readymask & CURL_CSELECT_IN) != 0) { - /* Have some incoming data */ - if(!psnd->buffer) { - /* Use buffer double default size for intermediate buffer */ - psnd->allocated_size = 2 * data->set.buffer_size; - psnd->buffer = malloc(psnd->allocated_size); - if(!psnd->buffer) - return CURLE_OUT_OF_MEMORY; - psnd->recv_size = 0; - psnd->recv_processed = 0; -#ifdef DEBUGBUILD - psnd->bindsock = sockfd; /* Used only for DEBUGASSERT */ -#endif /* DEBUGBUILD */ - bytestorecv = psnd->allocated_size; - } - - DEBUGASSERT(psnd->bindsock == sockfd); - recvedbytes = sread(sockfd, psnd->buffer + psnd->recv_size, - bytestorecv); - if(recvedbytes > 0) - psnd->recv_size += recvedbytes; - } - } - return CURLE_OK; -} - -static ssize_t get_pre_recved(struct connectdata *conn, int num, char *buf, - size_t len) -{ - struct postponed_data * const psnd = &(conn->postponed[num]); - size_t copysize; - if(!psnd->buffer) - return 0; - - DEBUGASSERT(psnd->allocated_size > 0); - DEBUGASSERT(psnd->recv_size <= psnd->allocated_size); - DEBUGASSERT(psnd->recv_processed <= psnd->recv_size); - /* Check and process data that already received and storied in internal - intermediate buffer */ - if(psnd->recv_size > psnd->recv_processed) { - DEBUGASSERT(psnd->bindsock == conn->sock[num]); - copysize = CURLMIN(len, psnd->recv_size - psnd->recv_processed); - memcpy(buf, psnd->buffer + psnd->recv_processed, copysize); - psnd->recv_processed += copysize; - } - else - copysize = 0; /* buffer was allocated, but nothing was received */ - - /* Free intermediate buffer if it has no unprocessed data */ - if(psnd->recv_processed == psnd->recv_size) { - free(psnd->buffer); - psnd->buffer = NULL; - psnd->allocated_size = 0; - psnd->recv_size = 0; - psnd->recv_processed = 0; -#ifdef DEBUGBUILD - psnd->bindsock = CURL_SOCKET_BAD; -#endif /* DEBUGBUILD */ - } - return (ssize_t)copysize; -} -#else /* ! USE_RECV_BEFORE_SEND_WORKAROUND */ -/* Use "do-nothing" macros instead of functions when workaround not used */ -bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex) -{ - (void)conn; - (void)sockindex; - return false; -} -#define pre_receive_plain(d,c,n) CURLE_OK -#define get_pre_recved(c,n,b,l) 0 -#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */ - -/* Curl_infof() is for info message along the way */ -#define MAXINFO 2048 - -void Curl_infof(struct Curl_easy *data, const char *fmt, ...) -{ - DEBUGASSERT(!strchr(fmt, '\n')); - if(data && data->set.verbose) { - va_list ap; - int len; - char buffer[MAXINFO + 2]; - va_start(ap, fmt); - len = mvsnprintf(buffer, MAXINFO, fmt, ap); - va_end(ap); - buffer[len++] = '\n'; - buffer[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, buffer, len); - } -} - -/* Curl_failf() is for messages stating why we failed. - * The message SHALL NOT include any LF or CR. - */ - -void Curl_failf(struct Curl_easy *data, const char *fmt, ...) -{ - DEBUGASSERT(!strchr(fmt, '\n')); - if(data->set.verbose || data->set.errorbuffer) { - va_list ap; - int len; - char error[CURL_ERROR_SIZE + 2]; - va_start(ap, fmt); - len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap); - - if(data->set.errorbuffer && !data->state.errorbuf) { - strcpy(data->set.errorbuffer, error); - data->state.errorbuf = TRUE; /* wrote error string */ - } - error[len++] = '\n'; - error[len] = '\0'; - Curl_debug(data, CURLINFO_TEXT, error, len); - va_end(ap); - } -} - /* * Curl_write() is an internal write function that sends data to the * server. Works with plain sockets, SCP, SSL or kerberos. @@ -301,7 +158,7 @@ CURLcode Curl_write(struct Curl_easy *data, DEBUGASSERT(data); DEBUGASSERT(data->conn); conn = data->conn; - num = (sockfd == conn->sock[SECONDARYSOCKET]); + num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]); #ifdef CURLDEBUG { @@ -338,153 +195,6 @@ CURLcode Curl_write(struct Curl_easy *data, } } -/* Curl_send_plain sends raw data without a size restriction on 'len'. */ -ssize_t Curl_send_plain(struct Curl_easy *data, int num, - const void *mem, size_t len, CURLcode *code) -{ - struct connectdata *conn; - curl_socket_t sockfd; - ssize_t bytes_written; - - DEBUGASSERT(data); - DEBUGASSERT(data->conn); - conn = data->conn; - sockfd = conn->sock[num]; - /* WinSock will destroy unread received data if send() is - failed. - To avoid lossage of received data, recv() must be - performed before every send() if any incoming data is - available. */ - if(pre_receive_plain(data, conn, num)) { - *code = CURLE_OUT_OF_MEMORY; - return -1; - } - -#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT) /* Linux */ - if(conn->bits.tcp_fastopen) { - bytes_written = sendto(sockfd, mem, len, MSG_FASTOPEN, - conn->ip_addr->ai_addr, conn->ip_addr->ai_addrlen); - conn->bits.tcp_fastopen = FALSE; - } - else -#endif - bytes_written = swrite(sockfd, mem, len); - - *code = CURLE_OK; - if(-1 == bytes_written) { - int err = SOCKERRNO; - - if( -#ifdef WSAEWOULDBLOCK - /* This is how Windows does it */ - (WSAEWOULDBLOCK == err) -#else - /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned - due to its inability to send off data without blocking. We therefore - treat both error codes the same here */ - (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) || - (EINPROGRESS == err) -#endif - ) { - /* this is just a case of EWOULDBLOCK */ - *code = CURLE_AGAIN; - } - else { - char buffer[STRERROR_LEN]; - failf(data, "Send failure: %s", - Curl_strerror(err, buffer, sizeof(buffer))); - data->state.os_errno = err; - *code = CURLE_SEND_ERROR; - } - } - return bytes_written; -} - -/* - * Curl_write_plain() is an internal write function that sends data to the - * server using plain sockets only. Otherwise meant to have the exact same - * proto as Curl_write(). - * - * This function wraps Curl_send_plain(). The only difference besides the - * prototype is '*written' (bytes written) is set to 0 on error. - * 'sockfd' must be one of the connection's two main sockets and the value of - * 'len' must not be changed. - */ -CURLcode Curl_write_plain(struct Curl_easy *data, - curl_socket_t sockfd, - const void *mem, - size_t len, - ssize_t *written) -{ - CURLcode result; - struct connectdata *conn = data->conn; - int num; - DEBUGASSERT(conn); - DEBUGASSERT(sockfd == conn->sock[FIRSTSOCKET] || - sockfd == conn->sock[SECONDARYSOCKET]); - if(sockfd != conn->sock[FIRSTSOCKET] && - sockfd != conn->sock[SECONDARYSOCKET]) - return CURLE_BAD_FUNCTION_ARGUMENT; - - num = (sockfd == conn->sock[SECONDARYSOCKET]); - - *written = Curl_send_plain(data, num, mem, len, &result); - if(*written == -1) - *written = 0; - - return result; -} - -/* Curl_recv_plain receives raw data without a size restriction on 'len'. */ -ssize_t Curl_recv_plain(struct Curl_easy *data, int num, char *buf, - size_t len, CURLcode *code) -{ - struct connectdata *conn; - curl_socket_t sockfd; - ssize_t nread; - DEBUGASSERT(data); - DEBUGASSERT(data->conn); - conn = data->conn; - sockfd = conn->sock[num]; - /* Check and return data that already received and storied in internal - intermediate buffer */ - nread = get_pre_recved(conn, num, buf, len); - if(nread > 0) { - *code = CURLE_OK; - return nread; - } - - nread = sread(sockfd, buf, len); - - *code = CURLE_OK; - if(-1 == nread) { - int err = SOCKERRNO; - - if( -#ifdef WSAEWOULDBLOCK - /* This is how Windows does it */ - (WSAEWOULDBLOCK == err) -#else - /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned - due to its inability to send off data without blocking. We therefore - treat both error codes the same here */ - (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) -#endif - ) { - /* this is just a case of EWOULDBLOCK */ - *code = CURLE_AGAIN; - } - else { - char buffer[STRERROR_LEN]; - failf(data, "Recv failure: %s", - Curl_strerror(err, buffer, sizeof(buffer))); - data->state.os_errno = err; - *code = CURLE_RECV_ERROR; - } - } - return nread; -} - static CURLcode pausewrite(struct Curl_easy *data, int type, /* what type of data */ const char *ptr, @@ -498,8 +208,7 @@ static CURLcode pausewrite(struct Curl_easy *data, unsigned int i; bool newtype = TRUE; - /* If this transfers over HTTP/2, pause the stream! */ - Curl_http2_stream_pause(data, TRUE); + Curl_conn_ev_data_pause(data, TRUE); if(s->tempcount) { for(i = 0; i< s->tempcount; i++) { @@ -678,41 +387,6 @@ CURLcode Curl_client_write(struct Curl_easy *data, } /* - * Curl_read_plain() is an internal read function that reads data from the - * server using plain sockets only. Otherwise meant to have the exact same - * proto as Curl_read(). - * - * This function wraps Curl_recv_plain(). The only difference besides the - * prototype is '*n' (bytes read) is set to 0 on error. - * 'sockfd' must be one of the connection's two main sockets and the value of - * 'sizerequested' must not be changed. - */ -CURLcode Curl_read_plain(struct Curl_easy *data, /* transfer */ - curl_socket_t sockfd, /* read from this socket */ - char *buf, /* store read data here */ - size_t sizerequested, /* max amount to read */ - ssize_t *n) /* amount bytes read */ -{ - CURLcode result; - struct connectdata *conn = data->conn; - int num; - DEBUGASSERT(conn); - DEBUGASSERT(sockfd == conn->sock[FIRSTSOCKET] || - sockfd == conn->sock[SECONDARYSOCKET]); - if(sockfd != conn->sock[FIRSTSOCKET] && - sockfd != conn->sock[SECONDARYSOCKET]) - return CURLE_BAD_FUNCTION_ARGUMENT; - - num = (sockfd == conn->sock[SECONDARYSOCKET]); - - *n = Curl_recv_plain(data, num, buf, sizerequested, &result); - if(*n == -1) - *n = 0; - - return result; -} - -/* * Internal read-from-socket function. This is meant to deal with plain * sockets, SSL sockets and kerberos sockets. * @@ -752,30 +426,3 @@ out: return result; } -/* return 0 on success */ -void Curl_debug(struct Curl_easy *data, curl_infotype type, - char *ptr, size_t size) -{ - if(data->set.verbose) { - static const char s_infotype[CURLINFO_END][3] = { - "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; - if(data->set.fdebug) { - bool inCallback = Curl_is_in_callback(data); - Curl_set_in_callback(data, true); - (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata); - Curl_set_in_callback(data, inCallback); - } - else { - switch(type) { - case CURLINFO_TEXT: - case CURLINFO_HEADER_OUT: - case CURLINFO_HEADER_IN: - fwrite(s_infotype[type], 2, 1, data->set.err); - fwrite(ptr, size, 1, data->set.err); - break; - default: /* nada */ - break; - } - } - } -} diff --git a/Utilities/cmcurl/lib/sendf.h b/Utilities/cmcurl/lib/sendf.h index 8af5c46085..d0c9275705 100644 --- a/Utilities/cmcurl/lib/sendf.h +++ b/Utilities/cmcurl/lib/sendf.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -26,26 +26,8 @@ #include "curl_setup.h" -void Curl_infof(struct Curl_easy *, const char *fmt, ...); -void Curl_failf(struct Curl_easy *, const char *fmt, ...); +#include "curl_log.h" -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - -#if defined(HAVE_VARIADIC_MACROS_C99) -#define infof(...) Curl_nop_stmt -#elif defined(HAVE_VARIADIC_MACROS_GCC) -#define infof(x...) Curl_nop_stmt -#else -#error "missing VARIADIC macro define, fix and rebuild!" -#endif - -#else /* CURL_DISABLE_VERBOSE_STRINGS */ - -#define infof Curl_infof - -#endif /* CURL_DISABLE_VERBOSE_STRINGS */ - -#define failf Curl_failf #define CLIENTWRITE_BODY (1<<0) #define CLIENTWRITE_HEADER (1<<1) @@ -58,20 +40,6 @@ void Curl_failf(struct Curl_easy *, const char *fmt, ...); CURLcode Curl_client_write(struct Curl_easy *data, int type, char *ptr, size_t len) WARN_UNUSED_RESULT; -bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex); - -/* internal read-function, does plain socket only */ -CURLcode Curl_read_plain(struct Curl_easy *data, - curl_socket_t sockfd, - char *buf, - size_t sizerequested, - ssize_t *n); - -ssize_t Curl_recv_plain(struct Curl_easy *data, int num, char *buf, - size_t len, CURLcode *code); -ssize_t Curl_send_plain(struct Curl_easy *data, int num, - const void *mem, size_t len, CURLcode *code); - /* internal read-function, does plain socket, SSL and krb4 */ CURLcode Curl_read(struct Curl_easy *data, curl_socket_t sockfd, char *buf, size_t buffersize, @@ -83,15 +51,4 @@ CURLcode Curl_write(struct Curl_easy *data, const void *mem, size_t len, ssize_t *written); -/* internal write-function, does plain sockets ONLY */ -CURLcode Curl_write_plain(struct Curl_easy *data, - curl_socket_t sockfd, - const void *mem, size_t len, - ssize_t *written); - -/* the function used to output verbose information */ -void Curl_debug(struct Curl_easy *data, curl_infotype type, - char *ptr, size_t size); - - #endif /* HEADER_CURL_SENDF_H */ diff --git a/Utilities/cmcurl/lib/setopt.c b/Utilities/cmcurl/lib/setopt.c index b77e95b4e3..604693ad94 100644 --- a/Utilities/cmcurl/lib/setopt.c +++ b/Utilities/cmcurl/lib/setopt.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -174,7 +174,7 @@ static CURLcode protocol2num(const char *str, curl_prot_t *val) *val |= h->protocol; } - } while(str++); + } while(str && str++); if(!*val) /* no protocol listed */ @@ -463,8 +463,8 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) version_max >= CURL_SSLVERSION_MAX_LAST) return CURLE_BAD_FUNCTION_ARGUMENT; - primary->version = version; - primary->version_max = version_max; + primary->version = (unsigned char)version; + primary->version_max = (unsigned int)version_max; } #else result = CURLE_NOT_BUILT_IN; @@ -732,13 +732,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->set.sep_headers = (bool)((arg & CURLHEADER_SEPARATE)? TRUE: FALSE); break; - case CURLOPT_HTTP200ALIASES: - /* - * Set a list of aliases for HTTP 200 in response header - */ - data->set.http200aliases = va_arg(param, struct curl_slist *); - break; - #if !defined(CURL_DISABLE_COOKIES) case CURLOPT_COOKIE: /* @@ -760,18 +753,18 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) return CURLE_BAD_FUNCTION_ARGUMENT; /* append the cookie file name to the list of file names, and deal with them later */ - cl = curl_slist_append(data->state.cookielist, argptr); + cl = curl_slist_append(data->set.cookielist, argptr); if(!cl) { - curl_slist_free_all(data->state.cookielist); - data->state.cookielist = NULL; + curl_slist_free_all(data->set.cookielist); + data->set.cookielist = NULL; return CURLE_OUT_OF_MEMORY; } - data->state.cookielist = cl; /* store the list for later use */ + data->set.cookielist = cl; /* store the list for later use */ } else { /* clear the list of cookie files */ - curl_slist_free_all(data->state.cookielist); - data->state.cookielist = NULL; + curl_slist_free_all(data->set.cookielist); + data->set.cookielist = NULL; if(!data->share || !data->share->cookies) { /* throw away all existing cookies if this isn't a shared cookie @@ -902,22 +895,38 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * the listed enums in curl/curl.h. */ arg = va_arg(param, long); - if(arg < CURL_HTTP_VERSION_NONE) - return CURLE_BAD_FUNCTION_ARGUMENT; + switch(arg) { + case CURL_HTTP_VERSION_NONE: +#ifdef USE_HTTP2 + /* TODO: this seems an undesirable quirk to force a behaviour on + * lower implementations that they should recognize independantly? */ + arg = CURL_HTTP_VERSION_2TLS; +#endif + /* accepted */ + break; + case CURL_HTTP_VERSION_1_0: + case CURL_HTTP_VERSION_1_1: + /* accepted */ + break; +#ifdef USE_HTTP2 + case CURL_HTTP_VERSION_2_0: + case CURL_HTTP_VERSION_2TLS: + case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: + /* accepted */ + break; +#endif #ifdef ENABLE_QUIC - if(arg == CURL_HTTP_VERSION_3) - ; - else + case CURL_HTTP_VERSION_3: + case CURL_HTTP_VERSION_3ONLY: + /* accepted */ + break; #endif -#ifndef USE_HTTP2 - if(arg >= CURL_HTTP_VERSION_2) - return CURLE_UNSUPPORTED_PROTOCOL; -#else - if(arg >= CURL_HTTP_VERSION_LAST) + default: + /* not accepted */ + if(arg < CURL_HTTP_VERSION_NONE) + return CURLE_BAD_FUNCTION_ARGUMENT; return CURLE_UNSUPPORTED_PROTOCOL; - if(arg == CURL_HTTP_VERSION_NONE) - arg = CURL_HTTP_VERSION_2TLS; -#endif + } data->set.httpwant = (unsigned char)arg; break; @@ -944,6 +953,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->set.http09_allowed = arg ? TRUE : FALSE; #endif break; + + case CURLOPT_HTTP200ALIASES: + /* + * Set a list of aliases for HTTP 200 in response header + */ + data->set.http200aliases = va_arg(param, struct curl_slist *); + break; #endif /* CURL_DISABLE_HTTP */ #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \ @@ -1309,6 +1325,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->set.krb = (data->set.str[STRING_KRB_LEVEL]) ? TRUE : FALSE; break; #endif +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) case CURLOPT_FTP_CREATE_MISSING_DIRS: /* * An FTP/SFTP option that modifies an upload to create missing @@ -1322,6 +1339,26 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) else data->set.ftp_create_missing_dirs = (unsigned char)arg; break; + + case CURLOPT_POSTQUOTE: + /* + * List of RAW FTP commands to use after a transfer + */ + data->set.postquote = va_arg(param, struct curl_slist *); + break; + case CURLOPT_PREQUOTE: + /* + * List of RAW FTP commands to use prior to RETR (Wesley Laxton) + */ + data->set.prequote = va_arg(param, struct curl_slist *); + break; + case CURLOPT_QUOTE: + /* + * List of RAW FTP commands to use before a transfer + */ + data->set.quote = va_arg(param, struct curl_slist *); + break; +#endif case CURLOPT_READDATA: /* * FILE pointer to read the file to be uploaded from. Or possibly @@ -1431,7 +1468,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) case CURLOPT_TIMEOUT_MS: uarg = va_arg(param, unsigned long); - if(uarg >= UINT_MAX) + if(uarg > UINT_MAX) uarg = UINT_MAX; data->set.timeout = (unsigned int)uarg; break; @@ -1449,7 +1486,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) case CURLOPT_CONNECTTIMEOUT_MS: uarg = va_arg(param, unsigned long); - if(uarg >= UINT_MAX) + if(uarg > UINT_MAX) uarg = UINT_MAX; data->set.connecttimeout = (unsigned int)uarg; break; @@ -1460,7 +1497,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) * The maximum time for curl to wait for FTP server connect */ uarg = va_arg(param, unsigned long); - if(uarg >= UINT_MAX) + if(uarg > UINT_MAX) uarg = UINT_MAX; data->set.accepttimeout = (unsigned int)uarg; break; @@ -1506,24 +1543,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) va_arg(param, char *)); break; - case CURLOPT_POSTQUOTE: - /* - * List of RAW FTP commands to use after a transfer - */ - data->set.postquote = va_arg(param, struct curl_slist *); - break; - case CURLOPT_PREQUOTE: - /* - * List of RAW FTP commands to use prior to RETR (Wesley Laxton) - */ - data->set.prequote = va_arg(param, struct curl_slist *); - break; - case CURLOPT_QUOTE: - /* - * List of RAW FTP commands to use before a transfer - */ - data->set.quote = va_arg(param, struct curl_slist *); - break; case CURLOPT_RESOLVE: /* * List of HOST:PORT:[addresses] strings to populate the DNS cache with @@ -1871,16 +1890,15 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) arg = va_arg(param, long); if((arg < 0) || (arg > 65535)) return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.localportrange = curlx_sltosi(arg); + data->set.localportrange = curlx_sltous(arg); break; case CURLOPT_GSSAPI_DELEGATION: /* * GSS-API credential delegation bitmask */ - arg = va_arg(param, long); - if(arg < CURLGSSAPI_DELEGATION_NONE) - return CURLE_BAD_FUNCTION_ARGUMENT; - data->set.gssapi_delegation = arg; + uarg = va_arg(param, unsigned long); + data->set.gssapi_delegation = (unsigned char)uarg& + (CURLGSSAPI_DELEGATION_POLICY_FLAG|CURLGSSAPI_DELEGATION_FLAG); break; case CURLOPT_SSL_VERIFYPEER: /* @@ -2260,9 +2278,14 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->cookies = NULL; #endif +#ifndef CURL_DISABLE_HSTS + if(data->share->hsts == data->hsts) + data->hsts = NULL; +#endif +#ifdef USE_SSL if(data->share->sslsession == data->state.session) data->state.session = NULL; - +#endif #ifdef USE_LIBPSL if(data->psl == &data->share->psl) data->psl = data->multi? &data->multi->psl: NULL; @@ -2296,10 +2319,19 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->cookies = data->share->cookies; } #endif /* CURL_DISABLE_HTTP */ +#ifndef CURL_DISABLE_HSTS + if(data->share->hsts) { + /* first free the private one if any */ + Curl_hsts_cleanup(&data->hsts); + data->hsts = data->share->hsts; + } +#endif /* CURL_DISABLE_HTTP */ +#ifdef USE_SSL if(data->share->sslsession) { data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions; data->state.session = data->share->sslsession; } +#endif #ifdef USE_LIBPSL if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL)) data->psl = &data->share->psl; @@ -2515,6 +2547,14 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) va_arg(param, char *)); break; + case CURLOPT_SSH_KNOWNHOSTS: + /* + * Store the file name to read known hosts from. + */ + result = Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], + va_arg(param, char *)); + break; +#ifdef USE_LIBSSH2 case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: /* * Option to allow for the SHA256 of the host public key to be checked @@ -2524,14 +2564,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) va_arg(param, char *)); break; - case CURLOPT_SSH_KNOWNHOSTS: - /* - * Store the file name to read known hosts from. - */ - result = Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], - va_arg(param, char *)); - break; -#ifdef USE_LIBSSH2 case CURLOPT_SSH_HOSTKEYFUNCTION: /* the callback to check the hostkey without the knownhost file */ data->set.ssh_hostkeyfunc = va_arg(param, curl_sshhostkeycallback); @@ -2544,6 +2576,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->set.ssh_hostkeyfunc_userp = va_arg(param, void *); break; #endif + case CURLOPT_SSH_KEYFUNCTION: /* setting to NULL is fine since the ssh.c functions themselves will then revert to use the internal default */ @@ -2590,7 +2623,8 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) return CURLE_BAD_FUNCTION_ARGUMENT; data->set.new_file_perms = (unsigned int)arg; break; - +#endif +#ifdef USE_SSH case CURLOPT_NEW_DIRECTORY_PERMS: /* * Uses these permissions instead of 0755 @@ -2825,52 +2859,33 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) case CURLOPT_TLSAUTH_USERNAME: result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME], va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME] && - !data->set.ssl.primary.authtype) - data->set.ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default to SRP */ break; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_USERNAME: result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_PROXY], va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME_PROXY] && - !data->set.proxy_ssl.primary.authtype) - data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default to - SRP */ break; #endif case CURLOPT_TLSAUTH_PASSWORD: result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD], va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME] && - !data->set.ssl.primary.authtype) - data->set.ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default */ break; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_PASSWORD: result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_PROXY], va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME_PROXY] && - !data->set.proxy_ssl.primary.authtype) - data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default */ break; #endif case CURLOPT_TLSAUTH_TYPE: argptr = va_arg(param, char *); - if(!argptr || - strncasecompare(argptr, "SRP", strlen("SRP"))) - data->set.ssl.primary.authtype = CURL_TLSAUTH_SRP; - else - data->set.ssl.primary.authtype = CURL_TLSAUTH_NONE; + if(argptr && !strncasecompare(argptr, "SRP", strlen("SRP"))) + return CURLE_BAD_FUNCTION_ARGUMENT; break; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_TLSAUTH_TYPE: argptr = va_arg(param, char *); - if(!argptr || - strncasecompare(argptr, "SRP", strlen("SRP"))) - data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_SRP; - else - data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_NONE; + if(argptr || !strncasecompare(argptr, "SRP", strlen("SRP"))) + return CURLE_BAD_FUNCTION_ARGUMENT; break; #endif #endif @@ -2956,29 +2971,23 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) data->set.pipewait = (0 != va_arg(param, long)) ? TRUE : FALSE; break; case CURLOPT_STREAM_WEIGHT: -#ifndef USE_NGHTTP2 - return CURLE_NOT_BUILT_IN; -#else +#if defined(USE_HTTP2) || defined(USE_HTTP3) arg = va_arg(param, long); if((arg >= 1) && (arg <= 256)) - data->set.stream_weight = (int)arg; + data->set.priority.weight = (int)arg; break; +#else + return CURLE_NOT_BUILT_IN; #endif case CURLOPT_STREAM_DEPENDS: case CURLOPT_STREAM_DEPENDS_E: { -#ifndef USE_NGHTTP2 - return CURLE_NOT_BUILT_IN; -#else struct Curl_easy *dep = va_arg(param, struct Curl_easy *); if(!dep || GOOD_EASY_HANDLE(dep)) { - if(data->set.stream_depends_on) { - Curl_http2_remove_child(data->set.stream_depends_on, data); - } - Curl_http2_add_child(dep, data, (option == CURLOPT_STREAM_DEPENDS_E)); + return Curl_data_priority_add_child(dep, data, + option == CURLOPT_STREAM_DEPENDS_E); } break; -#endif } case CURLOPT_CONNECT_TO: data->set.connect_to = va_arg(param, struct curl_slist *); @@ -2988,7 +2997,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) break; case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS: uarg = va_arg(param, unsigned long); - if(uarg >= UINT_MAX) + if(uarg > UINT_MAX) uarg = UINT_MAX; data->set.happy_eyeballs_timeout = (unsigned int)uarg; break; @@ -3049,19 +3058,39 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) case CURLOPT_HSTSWRITEDATA: data->set.hsts_write_userp = va_arg(param, void *); break; - case CURLOPT_HSTS: + case CURLOPT_HSTS: { + struct curl_slist *h; if(!data->hsts) { data->hsts = Curl_hsts_init(); if(!data->hsts) return CURLE_OUT_OF_MEMORY; } argptr = va_arg(param, char *); - result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr); - if(result) - return result; - if(argptr) - (void)Curl_hsts_loadfile(data, data->hsts, argptr); + if(argptr) { + result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr); + if(result) + return result; + /* this needs to build a list of file names to read from, so that it can + read them later, as we might get a shared HSTS handle to load them + into */ + h = curl_slist_append(data->set.hstslist, argptr); + if(!h) { + curl_slist_free_all(data->set.hstslist); + data->set.hstslist = NULL; + return CURLE_OUT_OF_MEMORY; + } + data->set.hstslist = h; /* store the list for later use */ + } + else { + /* clear the list of HSTS files */ + curl_slist_free_all(data->set.hstslist); + data->set.hstslist = NULL; + if(!data->share || !data->share->hsts) + /* throw away the HSTS cache unless shared */ + Curl_hsts_cleanup(&data->hsts); + } break; + } case CURLOPT_HSTS_CTRL: arg = va_arg(param, long); if(arg & CURLHSTS_ENABLE) { diff --git a/Utilities/cmcurl/lib/setopt.h b/Utilities/cmcurl/lib/setopt.h index ffc77a71dc..3c14a05e37 100644 --- a/Utilities/cmcurl/lib/setopt.h +++ b/Utilities/cmcurl/lib/setopt.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/setup-os400.h b/Utilities/cmcurl/lib/setup-os400.h index 7854397860..759583466c 100644 --- a/Utilities/cmcurl/lib/setup-os400.h +++ b/Utilities/cmcurl/lib/setup-os400.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -205,7 +205,7 @@ extern OM_uint32 Curl_gss_delete_sec_context_a(OM_uint32 * minor_status, extern int Curl_os400_connect(int sd, struct sockaddr *destaddr, int addrlen); extern int Curl_os400_bind(int sd, struct sockaddr *localaddr, int addrlen); extern int Curl_os400_sendto(int sd, char *buffer, int buflen, int flags, - struct sockaddr *dstaddr, int addrlen); + const struct sockaddr *dstaddr, int addrlen); extern int Curl_os400_recvfrom(int sd, char *buffer, int buflen, int flags, struct sockaddr *fromaddr, int *addrlen); extern int Curl_os400_getpeername(int sd, struct sockaddr *addr, int *addrlen); diff --git a/Utilities/cmcurl/lib/setup-vms.h b/Utilities/cmcurl/lib/setup-vms.h index b570683d78..46657b2cd4 100644 --- a/Utilities/cmcurl/lib/setup-vms.h +++ b/Utilities/cmcurl/lib/setup-vms.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/setup-win32.h b/Utilities/cmcurl/lib/setup-win32.h index bc5f8efc3c..13948389a4 100644 --- a/Utilities/cmcurl/lib/setup-win32.h +++ b/Utilities/cmcurl/lib/setup-win32.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/sha256.c b/Utilities/cmcurl/lib/sha256.c index c96a9fc61f..fdfd631ef9 100644 --- a/Utilities/cmcurl/lib/sha256.c +++ b/Utilities/cmcurl/lib/sha256.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017, Florin Petriuc, <petriuc.florin@gmail.com> - * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Florin Petriuc, <petriuc.florin@gmail.com> + * 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 diff --git a/Utilities/cmcurl/lib/share.c b/Utilities/cmcurl/lib/share.c index 1a083e72a0..c0a8d806f3 100644 --- a/Utilities/cmcurl/lib/share.c +++ b/Utilities/cmcurl/lib/share.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -29,9 +29,11 @@ #include "share.h" #include "psl.h" #include "vtls/vtls.h" -#include "curl_memory.h" +#include "hsts.h" -/* The last #include file should be: */ +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" #include "memdebug.h" struct Curl_share * @@ -89,6 +91,18 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) #endif break; + case CURL_LOCK_DATA_HSTS: +#ifndef CURL_DISABLE_HSTS + if(!share->hsts) { + share->hsts = Curl_hsts_init(); + if(!share->hsts) + res = CURLSHE_NOMEM; + } +#else /* CURL_DISABLE_HSTS */ + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + case CURL_LOCK_DATA_SSL_SESSION: #ifdef USE_SSL if(!share->sslsession) { @@ -141,6 +155,16 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) #endif break; + case CURL_LOCK_DATA_HSTS: +#ifndef CURL_DISABLE_HSTS + if(share->hsts) { + Curl_hsts_cleanup(&share->hsts); + } +#else /* CURL_DISABLE_HSTS */ + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + case CURL_LOCK_DATA_SSL_SESSION: #ifdef USE_SSL Curl_safefree(share->sslsession); @@ -207,6 +231,10 @@ curl_share_cleanup(struct Curl_share *share) Curl_cookie_cleanup(share->cookies); #endif +#ifndef CURL_DISABLE_HSTS + Curl_hsts_cleanup(&share->hsts); +#endif + #ifdef USE_SSL if(share->sslsession) { size_t i; diff --git a/Utilities/cmcurl/lib/share.h b/Utilities/cmcurl/lib/share.h index 32be41691a..7f55aac853 100644 --- a/Utilities/cmcurl/lib/share.h +++ b/Utilities/cmcurl/lib/share.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -59,10 +59,14 @@ struct Curl_share { #ifdef USE_LIBPSL struct PslCache psl; #endif - +#ifndef CURL_DISABLE_HSTS + struct hsts *hsts; +#endif +#ifdef USE_SSL struct Curl_ssl_session *sslsession; size_t max_ssl_sessions; long sessionage; +#endif }; CURLSHcode Curl_share_lock(struct Curl_easy *, curl_lock_data, diff --git a/Utilities/cmcurl/lib/sigpipe.h b/Utilities/cmcurl/lib/sigpipe.h index d12b31764d..14ab25b67c 100644 --- a/Utilities/cmcurl/lib/sigpipe.h +++ b/Utilities/cmcurl/lib/sigpipe.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/slist.c b/Utilities/cmcurl/lib/slist.c index 6c80722c77..366b247609 100644 --- a/Utilities/cmcurl/lib/slist.c +++ b/Utilities/cmcurl/lib/slist.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/slist.h b/Utilities/cmcurl/lib/slist.h index 4e5834c90a..9561fd0226 100644 --- a/Utilities/cmcurl/lib/slist.h +++ b/Utilities/cmcurl/lib/slist.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/smb.c b/Utilities/cmcurl/lib/smb.c index 48d5a2fe00..dc0abe784b 100644 --- a/Utilities/cmcurl/lib/smb.c +++ b/Utilities/cmcurl/lib/smb.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2016 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2014, Bill Nagel <wnagel@tycoint.com>, Exacq Technologies + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Bill Nagel <wnagel@tycoint.com>, Exacq Technologies * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -763,6 +763,11 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done) void *msg = NULL; const struct smb_nt_create_response *smb_m; + if(data->set.upload && (data->state.infilesize < 0)) { + failf(data, "SMB upload needs to know the size up front"); + return CURLE_SEND_ERROR; + } + /* Start the request */ if(req->state == SMB_REQUESTING) { result = smb_send_tree_connect(data); @@ -993,6 +998,7 @@ static CURLcode smb_parse_url_path(struct Curl_easy *data, /* The share must be present */ if(!slash) { Curl_safefree(smbc->share); + failf(data, "missing share in URL path for SMB"); return CURLE_URL_MALFORMAT; } diff --git a/Utilities/cmcurl/lib/smb.h b/Utilities/cmcurl/lib/smb.h index 919f3ac142..c35f3e9700 100644 --- a/Utilities/cmcurl/lib/smb.h +++ b/Utilities/cmcurl/lib/smb.h @@ -7,8 +7,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2018, Bill Nagel <wnagel@tycoint.com>, Exacq Technologies - * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Bill Nagel <wnagel@tycoint.com>, Exacq Technologies + * 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 diff --git a/Utilities/cmcurl/lib/smtp.c b/Utilities/cmcurl/lib/smtp.c index 6d0783f458..7a030308d4 100644 --- a/Utilities/cmcurl/lib/smtp.c +++ b/Utilities/cmcurl/lib/smtp.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -398,15 +398,17 @@ static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data) struct connectdata *conn = data->conn; struct smtp_conn *smtpc = &conn->proto.smtpc; CURLcode result; + bool ssldone = FALSE; - if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) { + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); if(result) goto out; } - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &smtpc->ssldone); + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); if(!result) { + smtpc->ssldone = ssldone; if(smtpc->state != SMTP_UPGRADETLS) state(data, SMTP_UPGRADETLS); @@ -891,7 +893,7 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, if(smtpcode/100 != 2 && smtpcode != 1) { if(data->set.use_ssl <= CURLUSESSL_TRY - || Curl_conn_is_ssl(data, FIRSTSOCKET)) + || Curl_conn_is_ssl(conn, FIRSTSOCKET)) result = smtp_perform_helo(data, conn); else { failf(data, "Remote access denied: %d", smtpcode); @@ -956,7 +958,7 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, } if(smtpcode != 1) { - if(data->set.use_ssl && !Curl_conn_is_ssl(data, FIRSTSOCKET)) { + if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) { /* We don't have a SSL/TLS connection yet, but SSL is requested */ if(smtpc->tls_supported) /* Switch to TLS connection now */ @@ -1288,7 +1290,9 @@ static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done) struct smtp_conn *smtpc = &conn->proto.smtpc; if((conn->handler->flags & PROTOPT_SSL) && !smtpc->ssldone) { - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &smtpc->ssldone); + bool ssldone = FALSE; + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + smtpc->ssldone = ssldone; if(result || !smtpc->ssldone) return result; } diff --git a/Utilities/cmcurl/lib/smtp.h b/Utilities/cmcurl/lib/smtp.h index 24c5589e43..7a04c21549 100644 --- a/Utilities/cmcurl/lib/smtp.h +++ b/Utilities/cmcurl/lib/smtp.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2009 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -57,28 +57,28 @@ struct SMTP { curl_pp_transfer transfer; char *custom; /* Custom Request */ struct curl_slist *rcpt; /* Recipient list */ - bool rcpt_had_ok; /* Whether any of RCPT TO commands (depends on - total number of recipients) succeeded so far */ - bool trailing_crlf; /* Specifies if the trailing CRLF is present */ int rcpt_last_error; /* The last error received for RCPT TO command */ size_t eob; /* Number of bytes of the EOB (End Of Body) that have been received so far */ + BIT(rcpt_had_ok); /* Whether any of RCPT TO commands (depends on + total number of recipients) succeeded so far */ + BIT(trailing_crlf); /* Specifies if the trailing CRLF is present */ }; /* smtp_conn is used for struct connection-oriented data in the connectdata struct */ struct smtp_conn { struct pingpong pp; + struct SASL sasl; /* SASL-related storage */ smtpstate state; /* Always use smtp.c:state() to change state! */ - bool ssldone; /* Is connect() over SSL done? */ char *domain; /* Client address/name to send in the EHLO */ - struct SASL sasl; /* SASL-related storage */ - bool tls_supported; /* StartTLS capability supported by server */ - bool size_supported; /* If server supports SIZE extension according to + BIT(ssldone); /* Is connect() over SSL done? */ + BIT(tls_supported); /* StartTLS capability supported by server */ + BIT(size_supported); /* If server supports SIZE extension according to RFC 1870 */ - bool utf8_supported; /* If server supports SMTPUTF8 extension according + BIT(utf8_supported); /* If server supports SMTPUTF8 extension according to RFC 6531 */ - bool auth_supported; /* AUTH capability supported by server */ + BIT(auth_supported); /* AUTH capability supported by server */ }; extern const struct Curl_handler Curl_handler_smtp; diff --git a/Utilities/cmcurl/lib/sockaddr.h b/Utilities/cmcurl/lib/sockaddr.h index 77ec833ee0..5a6bb207dc 100644 --- a/Utilities/cmcurl/lib/sockaddr.h +++ b/Utilities/cmcurl/lib/sockaddr.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/socketpair.c b/Utilities/cmcurl/lib/socketpair.c index 0f8798f087..b94c9843e6 100644 --- a/Utilities/cmcurl/lib/socketpair.c +++ b/Utilities/cmcurl/lib/socketpair.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -65,7 +65,7 @@ int Curl_socketpair(int domain, int type, int protocol, union { struct sockaddr_in inaddr; struct sockaddr addr; - } a, a2; + } a; curl_socket_t listener; curl_socklen_t addrlen = sizeof(a.inaddr); int reuse = 1; @@ -85,9 +85,22 @@ int Curl_socketpair(int domain, int type, int protocol, socks[0] = socks[1] = CURL_SOCKET_BAD; +#if defined(WIN32) || defined(__CYGWIN__) + /* don't set SO_REUSEADDR on Windows */ + (void)reuse; +#ifdef SO_EXCLUSIVEADDRUSE + { + int exclusive = 1; + if(setsockopt(listener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + (char *)&exclusive, (curl_socklen_t)sizeof(exclusive)) == -1) + goto error; + } +#endif +#else if(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, (curl_socklen_t)sizeof(reuse)) == -1) goto error; +#endif if(bind(listener, &a.addr, sizeof(a.inaddr)) == -1) goto error; if(getsockname(listener, &a.addr, &addrlen) == -1 || @@ -107,24 +120,59 @@ int Curl_socketpair(int domain, int type, int protocol, pfd[0].fd = listener; pfd[0].events = POLLIN; pfd[0].revents = 0; - (void)Curl_poll(pfd, 1, 10*1000); /* 10 seconds */ + (void)Curl_poll(pfd, 1, 1000); /* one second */ socks[1] = accept(listener, NULL, NULL); if(socks[1] == CURL_SOCKET_BAD) goto error; + else { + struct curltime check; + struct curltime start = Curl_now(); + char *p = (char *)✓ + size_t s = sizeof(check); - /* verify that nothing else connected */ - addrlen = sizeof(a.inaddr); - if(getsockname(socks[0], &a.addr, &addrlen) == -1 || - addrlen < (int)sizeof(a.inaddr)) - goto error; - addrlen = sizeof(a2.inaddr); - if(getpeername(socks[1], &a2.addr, &addrlen) == -1 || - addrlen < (int)sizeof(a2.inaddr)) - goto error; - if(a.inaddr.sin_family != a2.inaddr.sin_family || - a.inaddr.sin_addr.s_addr != a2.inaddr.sin_addr.s_addr || - a.inaddr.sin_port != a2.inaddr.sin_port) - goto error; + /* write data to the socket */ + swrite(socks[0], &start, sizeof(start)); + /* verify that we read the correct data */ + do { + ssize_t nread; + + pfd[0].fd = socks[1]; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + (void)Curl_poll(pfd, 1, 1000); /* one second */ + + nread = sread(socks[1], p, s); + if(nread == -1) { + int sockerr = SOCKERRNO; + /* Don't block forever */ + if(Curl_timediff(Curl_now(), start) > (60 * 1000)) + goto error; + if( +#ifdef WSAEWOULDBLOCK + /* This is how Windows does it */ + (WSAEWOULDBLOCK == sockerr) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it + returned due to its inability to send off data without + blocking. We therefore treat both error codes the same here */ + (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || + (EINTR == sockerr) || (EINPROGRESS == sockerr) +#endif + ) { + continue; + } + goto error; + } + s -= nread; + if(s) { + p += nread; + continue; + } + if(memcmp(&start, &check, sizeof(check))) + goto error; + break; + } while(1); + } sclose(listener); return 0; diff --git a/Utilities/cmcurl/lib/socketpair.h b/Utilities/cmcurl/lib/socketpair.h index de70df673a..306ab5dc4c 100644 --- a/Utilities/cmcurl/lib/socketpair.h +++ b/Utilities/cmcurl/lib/socketpair.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/socks.c b/Utilities/cmcurl/lib/socks.c index d491e089be..95c2b004c9 100644 --- a/Utilities/cmcurl/lib/socks.c +++ b/Utilities/cmcurl/lib/socks.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -89,8 +89,8 @@ struct socks_state { * * This is STUPID BLOCKING behavior. Only used by the SOCKS GSSAPI functions. */ -int Curl_blockread_all(struct Curl_easy *data, /* transfer */ - curl_socket_t sockfd, /* read from this socket */ +int Curl_blockread_all(struct Curl_cfilter *cf, + struct Curl_easy *data, /* transfer */ char *buf, /* store read data here */ ssize_t buffersize, /* max amount to read */ ssize_t *n) /* amount bytes read */ @@ -98,6 +98,8 @@ int Curl_blockread_all(struct Curl_easy *data, /* transfer */ ssize_t nread = 0; ssize_t allread = 0; int result; + CURLcode err = CURLE_OK; + *n = 0; for(;;) { timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); @@ -108,15 +110,19 @@ int Curl_blockread_all(struct Curl_easy *data, /* transfer */ } if(!timeout_ms) timeout_ms = TIMEDIFF_T_MAX; - if(SOCKET_READABLE(sockfd, timeout_ms) <= 0) { + if(SOCKET_READABLE(cf->conn->sock[cf->sockindex], timeout_ms) <= 0) { result = ~CURLE_OK; break; } - result = Curl_read_plain(data, sockfd, buf, buffersize, &nread); - if(CURLE_AGAIN == result) - continue; - if(result) - break; + nread = Curl_conn_cf_recv(cf->next, data, buf, buffersize, &err); + if(nread <= 0) { + result = err; + if(CURLE_AGAIN == err) + continue; + if(err) { + break; + } + } if(buffersize == nread) { allread += nread; @@ -192,6 +198,68 @@ static void socksstate(struct socks_state *sx, struct Curl_easy *data, #endif } +static CURLproxycode socks_state_send(struct Curl_cfilter *cf, + struct socks_state *sx, + struct Curl_easy *data, + CURLproxycode failcode, + const char *description) +{ + ssize_t nwritten; + CURLcode result; + + nwritten = Curl_conn_cf_send(cf->next, data, (char *)sx->outp, + sx->outstanding, &result); + if(nwritten <= 0) { + if(CURLE_AGAIN == result) { + return CURLPX_OK; + } + else if(CURLE_OK == result) { + /* connection closed */ + failf(data, "connection to proxy closed"); + return CURLPX_CLOSED; + } + failf(data, "Failed to send %s: %s", description, + curl_easy_strerror(result)); + return failcode; + } + DEBUGASSERT(sx->outstanding >= nwritten); + /* not done, remain in state */ + sx->outstanding -= nwritten; + sx->outp += nwritten; + return CURLPX_OK; +} + +static CURLproxycode socks_state_recv(struct Curl_cfilter *cf, + struct socks_state *sx, + struct Curl_easy *data, + CURLproxycode failcode, + const char *description) +{ + ssize_t nread; + CURLcode result; + + nread = Curl_conn_cf_recv(cf->next, data, (char *)sx->outp, + sx->outstanding, &result); + if(nread <= 0) { + if(CURLE_AGAIN == result) { + return CURLPX_OK; + } + else if(CURLE_OK == result) { + /* connection closed */ + failf(data, "connection to proxy closed"); + return CURLPX_CLOSED; + } + failf(data, "SOCKS4: Failed receiving %s: %s", description, + curl_easy_strerror(result)); + return failcode; + } + /* remain in reading state */ + DEBUGASSERT(sx->outstanding >= nread); + sx->outstanding -= nread; + sx->outp += nread; + return CURLPX_OK; +} + /* * This function logs in to a SOCKS4 proxy and sends the specifics to the final * destination server. @@ -212,10 +280,8 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE; unsigned char *socksreq = (unsigned char *)data->state.buffer; CURLcode result; - curl_socket_t sockfd = conn->sock[cf->sockindex]; + CURLproxycode presult; struct Curl_dns_entry *dns = NULL; - ssize_t actualread; - ssize_t written; /* make sure that the buffer is at least 600 bytes */ DEBUGASSERT(READBUFFER_MIN >= 600); @@ -250,7 +316,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, /* DNS resolve only for SOCKS4, not SOCKS4a */ if(!protocol4a) { enum resolve_t rc = - Curl_resolv(data, sx->hostname, sx->remote_port, FALSE, &dns); + Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns); if(rc == CURLRESOLV_ERROR) return CURLPX_RESOLVE_HOST; @@ -375,19 +441,14 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, /* FALLTHROUGH */ case CONNECT_REQ_SENDING: /* Send request */ - result = Curl_write_plain(data, sockfd, (char *)sx->outp, - sx->outstanding, &written); - if(result && (CURLE_AGAIN != result)) { - failf(data, "Failed to send SOCKS4 connect request."); - return CURLPX_SEND_CONNECT; - } - if(written != sx->outstanding) { - /* not done, remain in state */ - sx->outstanding -= written; - sx->outp += written; + presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, + "SOCKS4 connect request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in sending state */ return CURLPX_OK; } - /* done sending! */ sx->outstanding = 8; /* receive data size */ sx->outp = socksreq; @@ -396,22 +457,12 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf, /* FALLTHROUGH */ case CONNECT_SOCKS_READ: /* Receive response */ - result = Curl_read_plain(data, sockfd, (char *)sx->outp, - sx->outstanding, &actualread); - if(result && (CURLE_AGAIN != result)) { - failf(data, "SOCKS4: Failed receiving connect request ack: %s", - curl_easy_strerror(result)); - return CURLPX_RECV_CONNECT; - } - else if(!result && !actualread) { - /* connection closed */ - failf(data, "connection to proxy closed"); - return CURLPX_CLOSED; - } - else if(actualread != sx->outstanding) { + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, + "connect request ack"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { /* remain in reading state */ - sx->outstanding -= actualread; - sx->outp += actualread; return CURLPX_OK; } sxstate(sx, data, CONNECT_DONE); @@ -518,10 +569,8 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, unsigned char *socksreq = (unsigned char *)data->state.buffer; char dest[256] = "unknown"; /* printable hostname:port */ int idx; - ssize_t actualread; - ssize_t written; CURLcode result; - curl_socket_t sockfd = conn->sock[cf->sockindex]; + CURLproxycode presult; bool socks5_resolve_local = (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE; const size_t hostname_len = strlen(sx->hostname); @@ -567,30 +616,25 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, /* write the number of authentication methods */ socksreq[1] = (unsigned char) (idx - 2); - result = Curl_write_plain(data, sockfd, socksreq, idx, &written); - if(result && (CURLE_AGAIN != result)) { - failf(data, "Unable to send initial SOCKS5 request."); - return CURLPX_SEND_CONNECT; - } - if(written != idx) { - sxstate(sx, data, CONNECT_SOCKS_SEND); - sx->outstanding = idx - written; - sx->outp = &socksreq[written]; + sx->outp = socksreq; + sx->outstanding = idx; + presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, + "initial SOCKS5 request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in sending state */ return CURLPX_OK; } sxstate(sx, data, CONNECT_SOCKS_READ); goto CONNECT_SOCKS_READ_INIT; case CONNECT_SOCKS_SEND: - result = Curl_write_plain(data, sockfd, (char *)sx->outp, - sx->outstanding, &written); - if(result && (CURLE_AGAIN != result)) { - failf(data, "Unable to send initial SOCKS5 request."); - return CURLPX_SEND_CONNECT; - } - if(written != sx->outstanding) { - /* not done, remain in state */ - sx->outstanding -= written; - sx->outp += written; + presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT, + "initial SOCKS5 request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in sending state */ return CURLPX_OK; } /* FALLTHROUGH */ @@ -600,21 +644,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, sx->outp = socksreq; /* store it here */ /* FALLTHROUGH */ case CONNECT_SOCKS_READ: - result = Curl_read_plain(data, sockfd, (char *)sx->outp, - sx->outstanding, &actualread); - if(result && (CURLE_AGAIN != result)) { - failf(data, "Unable to receive initial SOCKS5 response."); - return CURLPX_RECV_CONNECT; - } - else if(!result && !actualread) { - /* connection closed */ - failf(data, "Connection to proxy closed"); - return CURLPX_CLOSED; - } - else if(actualread != sx->outstanding) { + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT, + "initial SOCKS5 response"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { /* remain in reading state */ - sx->outstanding -= actualread; - sx->outp += actualread; return CURLPX_OK; } else if(socksreq[0] != 5) { @@ -634,7 +669,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) else if(allow_gssapi && (socksreq[1] == 1)) { sxstate(sx, data, CONNECT_GSSAPI_INIT); - result = Curl_SOCKS5_gssapi_negotiate(cf->sockindex, data); + result = Curl_SOCKS5_gssapi_negotiate(cf, data); if(result) { failf(data, "Unable to negotiate SOCKS5 GSS-API context."); return CURLPX_GSSAPI; @@ -713,16 +748,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, } /* FALLTHROUGH */ case CONNECT_AUTH_SEND: - result = Curl_write_plain(data, sockfd, sx->outp, - sx->outstanding, &written); - if(result && (CURLE_AGAIN != result)) { - failf(data, "Failed to send SOCKS5 sub-negotiation request."); - return CURLPX_SEND_AUTH; - } - if(sx->outstanding != written) { - /* remain in state */ - sx->outstanding -= written; - sx->outp += written; + presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH, + "SOCKS5 sub-negotiation request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in sending state */ return CURLPX_OK; } sx->outp = socksreq; @@ -730,21 +761,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, sxstate(sx, data, CONNECT_AUTH_READ); /* FALLTHROUGH */ case CONNECT_AUTH_READ: - result = Curl_read_plain(data, sockfd, (char *)sx->outp, - sx->outstanding, &actualread); - if(result && (CURLE_AGAIN != result)) { - failf(data, "Unable to receive SOCKS5 sub-negotiation response."); - return CURLPX_RECV_AUTH; - } - else if(!result && !actualread) { - /* connection closed */ - failf(data, "connection to proxy closed"); - return CURLPX_CLOSED; - } - else if(actualread != sx->outstanding) { - /* remain in state */ - sx->outstanding -= actualread; - sx->outp += actualread; + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH, + "SOCKS5 sub-negotiation response"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in reading state */ return CURLPX_OK; } /* ignore the first (VER) byte */ @@ -761,7 +783,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, case CONNECT_REQ_INIT: if(socks5_resolve_local) { enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port, - FALSE, &dns); + TRUE, &dns); if(rc == CURLRESOLV_ERROR) return CURLPX_RESOLVE_HOST; @@ -909,16 +931,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, sxstate(sx, data, CONNECT_REQ_SENDING); /* FALLTHROUGH */ case CONNECT_REQ_SENDING: - result = Curl_write_plain(data, sockfd, (char *)sx->outp, - sx->outstanding, &written); - if(result && (CURLE_AGAIN != result)) { - failf(data, "Failed to send SOCKS5 connect request."); - return CURLPX_SEND_REQUEST; - } - if(sx->outstanding != written) { - /* remain in state */ - sx->outstanding -= written; - sx->outp += written; + presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST, + "SOCKS5 connect request"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in send state */ return CURLPX_OK; } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) @@ -932,25 +950,15 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, sxstate(sx, data, CONNECT_REQ_READ); /* FALLTHROUGH */ case CONNECT_REQ_READ: - result = Curl_read_plain(data, sockfd, (char *)sx->outp, - sx->outstanding, &actualread); - if(result && (CURLE_AGAIN != result)) { - failf(data, "Failed to receive SOCKS5 connect request ack."); - return CURLPX_RECV_REQACK; - } - else if(!result && !actualread) { - /* connection closed */ - failf(data, "connection to proxy closed"); - return CURLPX_CLOSED; - } - else if(actualread != sx->outstanding) { - /* remain in state */ - sx->outstanding -= actualread; - sx->outp += actualread; + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK, + "SOCKS5 connect request ack"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in reading state */ return CURLPX_OK; } - - if(socksreq[0] != 5) { /* version */ + else if(socksreq[0] != 5) { /* version */ failf(data, "SOCKS5 reply has wrong version, version should be 5."); return CURLPX_BAD_VERSION; @@ -1031,21 +1039,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf, #endif /* FALLTHROUGH */ case CONNECT_REQ_READ_MORE: - result = Curl_read_plain(data, sockfd, (char *)sx->outp, - sx->outstanding, &actualread); - if(result && (CURLE_AGAIN != result)) { - failf(data, "Failed to receive SOCKS5 connect request ack."); - return CURLPX_RECV_ADDRESS; - } - else if(!result && !actualread) { - /* connection closed */ - failf(data, "connection to proxy closed"); - return CURLPX_CLOSED; - } - else if(actualread != sx->outstanding) { - /* remain in state */ - sx->outstanding -= actualread; - sx->outp += actualread; + presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS, + "SOCKS5 connect request address"); + if(CURLPX_OK != presult) + return presult; + else if(sx->outstanding) { + /* remain in reading state */ return CURLPX_OK; } sxstate(sx, data, CONNECT_DONE); @@ -1151,7 +1150,6 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf, result = connect_SOCKS(cf, sx, data); if(!result && sx->state == CONNECT_DONE) { cf->connected = TRUE; - Curl_updateconninfo(data, conn, conn->sock[cf->sockindex]); Curl_verboseconnect(data, conn); socks_proxy_cf_free(cf); } @@ -1171,7 +1169,7 @@ static int socks_cf_get_select_socks(struct Curl_cfilter *cf, if(!fds && cf->next->connected && !cf->connected && sx) { /* If we are not connected, the filter below is and has nothing * to wait on, we determine what to wait for. */ - socks[0] = cf->conn->sock[cf->sockindex]; + socks[0] = Curl_conn_cf_get_socket(cf, data); switch(sx->state) { case CONNECT_RESOLVING: case CONNECT_SOCKS_READ: @@ -1205,13 +1203,6 @@ static void socks_proxy_cf_destroy(struct Curl_cfilter *cf, socks_proxy_cf_free(cf); } -static void socks_proxy_cf_detach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - (void)data; - socks_proxy_cf_free(cf); -} - static void socks_cf_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, const char **phost, @@ -1229,11 +1220,11 @@ static void socks_cf_get_host(struct Curl_cfilter *cf, } } -static const struct Curl_cftype cft_socks_proxy = { +struct Curl_cftype Curl_cft_socks_proxy = { "SOCKS-PROXYY", CF_TYPE_IP_CONNECT, + 0, socks_proxy_cf_destroy, - Curl_cf_def_setup, socks_proxy_cf_connect, socks_proxy_cf_close, socks_cf_get_host, @@ -1241,8 +1232,10 @@ static const struct Curl_cftype cft_socks_proxy = { Curl_cf_def_data_pending, Curl_cf_def_send, Curl_cf_def_recv, - Curl_cf_def_attach_data, - socks_proxy_cf_detach_data, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, }; CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data, @@ -1252,10 +1245,23 @@ CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data, struct Curl_cfilter *cf; CURLcode result; - result = Curl_cf_create(&cf, &cft_socks_proxy, NULL); + result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL); if(!result) Curl_conn_cf_add(data, conn, sockindex, cf); return result; } +CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + (void)data; + result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL); + if(!result) + Curl_conn_cf_insert_after(cf_at, cf); + return result; +} + #endif /* CURL_DISABLE_PROXY */ diff --git a/Utilities/cmcurl/lib/socks.h b/Utilities/cmcurl/lib/socks.h index 2e2fa18f81..ba5b54a7bd 100644 --- a/Utilities/cmcurl/lib/socks.h +++ b/Utilities/cmcurl/lib/socks.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -37,8 +37,8 @@ * * This is STUPID BLOCKING behavior */ -int Curl_blockread_all(struct Curl_easy *data, - curl_socket_t sockfd, +int Curl_blockread_all(struct Curl_cfilter *cf, + struct Curl_easy *data, char *buf, ssize_t buffersize, ssize_t *n); @@ -47,7 +47,7 @@ int Curl_blockread_all(struct Curl_easy *data, /* * This function handles the SOCKS5 GSS-API negotiation and initialization */ -CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, +CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, struct Curl_easy *data); #endif @@ -55,6 +55,11 @@ CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data, struct connectdata *conn, int sockindex); +CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); + +extern struct Curl_cftype Curl_cft_socks_proxy; + #endif /* CURL_DISABLE_PROXY */ #endif /* HEADER_CURL_SOCKS_H */ diff --git a/Utilities/cmcurl/lib/socks_gssapi.c b/Utilities/cmcurl/lib/socks_gssapi.c index f14099febf..2ede8c7c29 100644 --- a/Utilities/cmcurl/lib/socks_gssapi.c +++ b/Utilities/cmcurl/lib/socks_gssapi.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2012, Markus Moeller, <markus_moeller@compuserve.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Markus Moeller, <markus_moeller@compuserve.com> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -30,6 +30,7 @@ #include "curl_gssapi.h" #include "urldata.h" #include "sendf.h" +#include "cfilters.h" #include "connect.h" #include "timeval.h" #include "socks.h" @@ -101,14 +102,14 @@ static int check_gss_err(struct Curl_easy *data, return 0; } -CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, +CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct connectdata *conn = data->conn; - curl_socket_t sock = conn->sock[sockindex]; + struct connectdata *conn = cf->conn; + curl_socket_t sock = conn->sock[cf->sockindex]; CURLcode code; ssize_t actualread; - ssize_t written; + ssize_t nwritten; int result; OM_uint32 gss_major_status, gss_minor_status, gss_status; OM_uint32 gss_ret_flags; @@ -203,8 +204,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, us_length = htons((short)gss_send_token.length); memcpy(socksreq + 2, &us_length, sizeof(short)); - code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written); - if(code || (4 != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + if(code || (4 != nwritten)) { failf(data, "Failed to send GSS-API authentication request."); gss_release_name(&gss_status, &server); gss_release_buffer(&gss_status, &gss_recv_token); @@ -213,10 +214,10 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } - code = Curl_write_plain(data, sock, (char *)gss_send_token.value, - gss_send_token.length, &written); - - if(code || ((ssize_t)gss_send_token.length != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, + (char *)gss_send_token.value, + gss_send_token.length, &code); + if(code || ((ssize_t)gss_send_token.length != nwritten)) { failf(data, "Failed to send GSS-API authentication token."); gss_release_name(&gss_status, &server); gss_release_buffer(&gss_status, &gss_recv_token); @@ -242,7 +243,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, * +----+------+-----+----------------+ */ - result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread); + result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive GSS-API authentication response."); gss_release_name(&gss_status, &server); @@ -281,7 +282,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_OUT_OF_MEMORY; } - result = Curl_blockread_all(data, sock, (char *)gss_recv_token.value, + result = Curl_blockread_all(cf, data, (char *)gss_recv_token.value, gss_recv_token.length, &actualread); if(result || (actualread != us_length)) { @@ -410,8 +411,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, memcpy(socksreq + 2, &us_length, sizeof(short)); } - code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written); - if(code || (4 != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); + if(code || (4 != nwritten)) { failf(data, "Failed to send GSS-API encryption request."); gss_release_buffer(&gss_status, &gss_w_token); gss_delete_sec_context(&gss_status, &gss_context, NULL); @@ -420,17 +421,18 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, if(data->set.socks5_gssapi_nec) { memcpy(socksreq, &gss_enc, 1); - code = Curl_write_plain(data, sock, socksreq, 1, &written); - if(code || ( 1 != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code); + if(code || ( 1 != nwritten)) { failf(data, "Failed to send GSS-API encryption type."); gss_delete_sec_context(&gss_status, &gss_context, NULL); return CURLE_COULDNT_CONNECT; } } else { - code = Curl_write_plain(data, sock, (char *)gss_w_token.value, - gss_w_token.length, &written); - if(code || ((ssize_t)gss_w_token.length != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, + (char *)gss_w_token.value, + gss_w_token.length, &code); + if(code || ((ssize_t)gss_w_token.length != nwritten)) { failf(data, "Failed to send GSS-API encryption type."); gss_release_buffer(&gss_status, &gss_w_token); gss_delete_sec_context(&gss_status, &gss_context, NULL); @@ -439,7 +441,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, gss_release_buffer(&gss_status, &gss_w_token); } - result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread); + result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive GSS-API encryption response."); gss_delete_sec_context(&gss_status, &gss_context, NULL); @@ -470,7 +472,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, gss_delete_sec_context(&gss_status, &gss_context, NULL); return CURLE_OUT_OF_MEMORY; } - result = Curl_blockread_all(data, sock, (char *)gss_recv_token.value, + result = Curl_blockread_all(cf, data, (char *)gss_recv_token.value, gss_recv_token.length, &actualread); if(result || (actualread != us_length)) { diff --git a/Utilities/cmcurl/lib/socks_sspi.c b/Utilities/cmcurl/lib/socks_sspi.c index 210a0dfbc5..d1200ea037 100644 --- a/Utilities/cmcurl/lib/socks_sspi.c +++ b/Utilities/cmcurl/lib/socks_sspi.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2012, 2011, Markus Moeller, <markus_moeller@compuserve.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Markus Moeller, <markus_moeller@compuserve.com> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -29,6 +29,7 @@ #include "urldata.h" #include "sendf.h" +#include "cfilters.h" #include "connect.h" #include "strerror.h" #include "timeval.h" @@ -62,11 +63,11 @@ static int check_sspi_err(struct Curl_easy *data, } /* This is the SSPI-using version of this function */ -CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, +CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct connectdata *conn = data->conn; - curl_socket_t sock = conn->sock[sockindex]; + struct connectdata *conn = cf->conn; + curl_socket_t sock = conn->sock[cf->sockindex]; CURLcode code; ssize_t actualread; ssize_t written; @@ -206,7 +207,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, us_length = htons((short)sspi_send_token.cbBuffer); memcpy(socksreq + 2, &us_length, sizeof(short)); - code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); if(code || (4 != written)) { failf(data, "Failed to send SSPI authentication request."); free(service_name); @@ -219,8 +220,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } - code = Curl_write_plain(data, sock, (char *)sspi_send_token.pvBuffer, - sspi_send_token.cbBuffer, &written); + written = Curl_conn_cf_send(cf->next, data, + (char *)sspi_send_token.pvBuffer, + sspi_send_token.cbBuffer, &code); if(code || (sspi_send_token.cbBuffer != (size_t)written)) { failf(data, "Failed to send SSPI authentication token."); free(service_name); @@ -260,7 +262,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, * +----+------+-----+----------------+ */ - result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread); + result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive SSPI authentication response."); free(service_name); @@ -300,7 +302,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, s_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } - result = Curl_blockread_all(data, sock, (char *)sspi_recv_token.pvBuffer, + result = Curl_blockread_all(cf, data, (char *)sspi_recv_token.pvBuffer, sspi_recv_token.cbBuffer, &actualread); if(result || (actualread != us_length)) { @@ -468,7 +470,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, memcpy(socksreq + 2, &us_length, sizeof(short)); } - code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code); if(code || (4 != written)) { failf(data, "Failed to send SSPI encryption request."); if(sspi_send_token.pvBuffer) @@ -479,7 +481,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, if(data->set.socks5_gssapi_nec) { memcpy(socksreq, &gss_enc, 1); - code = Curl_write_plain(data, sock, (char *)socksreq, 1, &written); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code); if(code || (1 != written)) { failf(data, "Failed to send SSPI encryption type."); s_pSecFn->DeleteSecurityContext(&sspi_context); @@ -487,8 +489,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, } } else { - code = Curl_write_plain(data, sock, (char *)sspi_send_token.pvBuffer, - sspi_send_token.cbBuffer, &written); + written = Curl_conn_cf_send(cf->next, data, + (char *)sspi_send_token.pvBuffer, + sspi_send_token.cbBuffer, &code); if(code || (sspi_send_token.cbBuffer != (size_t)written)) { failf(data, "Failed to send SSPI encryption type."); if(sspi_send_token.pvBuffer) @@ -500,7 +503,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); } - result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread); + result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread); if(result || (actualread != 4)) { failf(data, "Failed to receive SSPI encryption response."); s_pSecFn->DeleteSecurityContext(&sspi_context); @@ -532,7 +535,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_OUT_OF_MEMORY; } - result = Curl_blockread_all(data, sock, (char *)sspi_w_token[0].pvBuffer, + result = Curl_blockread_all(cf, data, (char *)sspi_w_token[0].pvBuffer, sspi_w_token[0].cbBuffer, &actualread); if(result || (actualread != us_length)) { diff --git a/Utilities/cmcurl/lib/speedcheck.c b/Utilities/cmcurl/lib/speedcheck.c index 3ddc43d2de..580efbde75 100644 --- a/Utilities/cmcurl/lib/speedcheck.c +++ b/Utilities/cmcurl/lib/speedcheck.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/speedcheck.h b/Utilities/cmcurl/lib/speedcheck.h index cb44eb04ec..bff2f32b77 100644 --- a/Utilities/cmcurl/lib/speedcheck.h +++ b/Utilities/cmcurl/lib/speedcheck.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/splay.c b/Utilities/cmcurl/lib/splay.c index 33b44aa1c6..48e079b32a 100644 --- a/Utilities/cmcurl/lib/splay.c +++ b/Utilities/cmcurl/lib/splay.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1997 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/splay.h b/Utilities/cmcurl/lib/splay.h index 015e2ca52f..dd1d07ac2e 100644 --- a/Utilities/cmcurl/lib/splay.h +++ b/Utilities/cmcurl/lib/splay.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1997 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/strcase.c b/Utilities/cmcurl/lib/strcase.c index 7fb9c80870..7c0b4ef909 100644 --- a/Utilities/cmcurl/lib/strcase.c +++ b/Utilities/cmcurl/lib/strcase.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/strcase.h b/Utilities/cmcurl/lib/strcase.h index 192e0da099..8c50bbcba6 100644 --- a/Utilities/cmcurl/lib/strcase.h +++ b/Utilities/cmcurl/lib/strcase.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/strdup.c b/Utilities/cmcurl/lib/strdup.c index ac22b6ddaf..07a61391ae 100644 --- a/Utilities/cmcurl/lib/strdup.c +++ b/Utilities/cmcurl/lib/strdup.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -37,7 +37,7 @@ #include "memdebug.h" #ifndef HAVE_STRDUP -char *curlx_strdup(const char *str) +char *Curl_strdup(const char *str) { size_t len; char *newstr; diff --git a/Utilities/cmcurl/lib/strdup.h b/Utilities/cmcurl/lib/strdup.h index fb46808b83..c3430b54d5 100644 --- a/Utilities/cmcurl/lib/strdup.h +++ b/Utilities/cmcurl/lib/strdup.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -26,7 +26,7 @@ #include "curl_setup.h" #ifndef HAVE_STRDUP -extern char *curlx_strdup(const char *str); +char *Curl_strdup(const char *str); #endif #ifdef WIN32 wchar_t* Curl_wcsdup(const wchar_t* src); diff --git a/Utilities/cmcurl/lib/strerror.c b/Utilities/cmcurl/lib/strerror.c index b9a51e26b9..3ec10e3695 100644 --- a/Utilities/cmcurl/lib/strerror.c +++ b/Utilities/cmcurl/lib/strerror.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2004 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -550,6 +550,9 @@ curl_url_strerror(CURLUcode error) case CURLUE_BAD_USER: return "Bad user"; + case CURLUE_LACKS_IDN: + return "libcurl lacks IDN support"; + case CURLUE_LAST: break; } diff --git a/Utilities/cmcurl/lib/strerror.h b/Utilities/cmcurl/lib/strerror.h index 658f16c10e..399712f8eb 100644 --- a/Utilities/cmcurl/lib/strerror.h +++ b/Utilities/cmcurl/lib/strerror.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/strtok.c b/Utilities/cmcurl/lib/strtok.c index 6120bcc28e..d8e1e8183f 100644 --- a/Utilities/cmcurl/lib/strtok.c +++ b/Utilities/cmcurl/lib/strtok.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/strtok.h b/Utilities/cmcurl/lib/strtok.h index 641a3daed8..321cba2326 100644 --- a/Utilities/cmcurl/lib/strtok.h +++ b/Utilities/cmcurl/lib/strtok.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/strtoofft.c b/Utilities/cmcurl/lib/strtoofft.c index fb8d92196f..077b25792e 100644 --- a/Utilities/cmcurl/lib/strtoofft.c +++ b/Utilities/cmcurl/lib/strtoofft.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/strtoofft.h b/Utilities/cmcurl/lib/strtoofft.h index 311dae4403..34d293ba38 100644 --- a/Utilities/cmcurl/lib/strtoofft.h +++ b/Utilities/cmcurl/lib/strtoofft.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/system_win32.c b/Utilities/cmcurl/lib/system_win32.c index bede9c7dcd..0cdaf3b2fe 100644 --- a/Utilities/cmcurl/lib/system_win32.c +++ b/Utilities/cmcurl/lib/system_win32.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/system_win32.h b/Utilities/cmcurl/lib/system_win32.h index 167804e3c5..24899cb2d5 100644 --- a/Utilities/cmcurl/lib/system_win32.h +++ b/Utilities/cmcurl/lib/system_win32.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/telnet.c b/Utilities/cmcurl/lib/telnet.c index 22bc81e755..a964bfdcde 100644 --- a/Utilities/cmcurl/lib/telnet.c +++ b/Utilities/cmcurl/lib/telnet.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/telnet.h b/Utilities/cmcurl/lib/telnet.h index 6dd99b48dc..30782d8375 100644 --- a/Utilities/cmcurl/lib/telnet.h +++ b/Utilities/cmcurl/lib/telnet.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/tftp.c b/Utilities/cmcurl/lib/tftp.c index 9e6d9490ed..164d3c723c 100644 --- a/Utilities/cmcurl/lib/tftp.c +++ b/Utilities/cmcurl/lib/tftp.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -48,6 +48,7 @@ #include "urldata.h" #include <curl/curl.h> +#include "cf-socket.h" #include "transfer.h" #include "sendf.h" #include "tftp.h" @@ -529,8 +530,8 @@ static CURLcode tftp_send_first(struct tftp_state_data *state, not have a size_t argument, like older unixes that want an 'int' */ senddata = sendto(state->sockfd, (void *)state->spacket.data, (SEND_TYPE_ARG3)sbytes, 0, - data->conn->ip_addr->ai_addr, - data->conn->ip_addr->ai_addrlen); + &data->conn->remote_addr->sa_addr, + data->conn->remote_addr->addrlen); if(senddata != (ssize_t)sbytes) { char buffer[STRERROR_LEN]; failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); @@ -1014,7 +1015,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) state->requested_blksize = blksize; ((struct sockaddr *)&state->local_addr)->sa_family = - (CURL_SA_FAMILY_T)(conn->ip_addr->ai_family); + (CURL_SA_FAMILY_T)(conn->remote_addr->family); tftp_set_timeouts(state); @@ -1033,7 +1034,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done) * IPv4 and IPv6... */ int rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr, - conn->ip_addr->ai_addrlen); + conn->remote_addr->addrlen); if(rc) { char buffer[STRERROR_LEN]; failf(data, "bind() failed; %s", diff --git a/Utilities/cmcurl/lib/tftp.h b/Utilities/cmcurl/lib/tftp.h index 3f1fda6382..5d2d5da615 100644 --- a/Utilities/cmcurl/lib/tftp.h +++ b/Utilities/cmcurl/lib/tftp.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/timediff.c b/Utilities/cmcurl/lib/timediff.c index c5893187dd..1b762bbd3e 100644 --- a/Utilities/cmcurl/lib/timediff.c +++ b/Utilities/cmcurl/lib/timediff.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/timediff.h b/Utilities/cmcurl/lib/timediff.h index 90e547457f..fb318d4f2b 100644 --- a/Utilities/cmcurl/lib/timediff.h +++ b/Utilities/cmcurl/lib/timediff.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/timeval.c b/Utilities/cmcurl/lib/timeval.c index 647d7b0fc5..dca1c6fe50 100644 --- a/Utilities/cmcurl/lib/timeval.c +++ b/Utilities/cmcurl/lib/timeval.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/timeval.h b/Utilities/cmcurl/lib/timeval.h index 8d4fef4e17..92e484ad1d 100644 --- a/Utilities/cmcurl/lib/timeval.h +++ b/Utilities/cmcurl/lib/timeval.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/transfer.c b/Utilities/cmcurl/lib/transfer.c index ba0410fc51..69df214ce4 100644 --- a/Utilities/cmcurl/lib/transfer.c +++ b/Utilities/cmcurl/lib/transfer.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -73,6 +73,7 @@ #include "url.h" #include "getinfo.h" #include "vtls/vtls.h" +#include "vquic/vquic.h" #include "select.h" #include "multiif.h" #include "connect.h" @@ -367,27 +368,12 @@ static int data_pending(struct Curl_easy *data) { struct connectdata *conn = data->conn; -#ifdef ENABLE_QUIC - if(conn->transport == TRNSPRT_QUIC) - return Curl_quic_data_pending(data); -#endif - if(conn->handler->protocol&PROTO_FAMILY_FTP) return Curl_conn_data_pending(data, SECONDARYSOCKET); /* in the case of libssh2, we can never be really sure that we have emptied its internal buffers so we MUST always try until we get EAGAIN back */ return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) || -#ifdef USE_NGHTTP2 - /* For HTTP/2, we may read up everything including response body - with header fields in Curl_http_readwrite_headers. If no - content-length is provided, curl waits for the connection - close, which we emulate it using conn->proto.httpc.closed = - TRUE. The thing is if we read everything, then http2_recv won't - be called and we cannot signal the HTTP/2 stream has closed. As - a workaround, we return nonzero here to call http2_recv. */ - ((conn->handler->protocol&PROTO_FAMILY_HTTP) && conn->httpversion >= 20) || -#endif Curl_conn_data_pending(data, FIRSTSOCKET); } @@ -454,29 +440,16 @@ static CURLcode readwrite_data(struct Curl_easy *data, bool is_empty_data = FALSE; size_t buffersize = data->set.buffer_size; size_t bytestoread = buffersize; -#ifdef USE_NGHTTP2 - bool is_http2 = ((conn->handler->protocol & PROTO_FAMILY_HTTP) && - (conn->httpversion == 20)); -#endif - bool is_http3 = -#ifdef ENABLE_QUIC - ((conn->handler->protocol & PROTO_FAMILY_HTTP) && - (conn->httpversion == 30)); -#else - FALSE; -#endif - - if( -#ifdef USE_NGHTTP2 - /* For HTTP/2, 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. */ - !is_http2 && -#endif - !is_http3 && /* Same reason mentioned above. */ - k->size != -1 && !k->header) { + /* 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); + bool data_eof_handled = is_http3 + || Curl_conn_is_http2(data, conn, FIRSTSOCKET); + + if(!data_eof_handled && k->size != -1 && !k->header) { /* make sure we don't read too much */ curl_off_t totalleft = k->size - k->bytecount; if(totalleft < (curl_off_t)bytestoread) @@ -499,7 +472,7 @@ static CURLcode readwrite_data(struct Curl_easy *data, 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")); + DEBUGF(infof(data, DMSG(data, "readwrite_data: we're done"))); nread = 0; } @@ -518,14 +491,9 @@ static CURLcode readwrite_data(struct Curl_easy *data, buf[nread] = 0; } else { - /* if we receive 0 or less here, either the http2 stream is closed or the + /* 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! */ -#ifdef USE_NGHTTP2 - if(is_http2 && !nread) - DEBUGF(infof(data, "nread == 0, stream closed, bailing")); - else -#endif - if(is_http3 && !nread) + if(data_eof_handled) DEBUGF(infof(data, "nread == 0, stream closed, bailing")); else DEBUGF(infof(data, "nread <= 0, server closed connection, bailing")); @@ -776,8 +744,8 @@ static CURLcode readwrite_data(struct Curl_easy *data, k->keepon &= ~KEEP_RECV; } - if(k->keepon & KEEP_RECV_PAUSE) { - /* this is a paused transfer */ + if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) { + /* this is a paused or stopped transfer */ break; } @@ -799,19 +767,18 @@ static CURLcode readwrite_data(struct Curl_easy *data, } out: - DEBUGF(infof(data, "readwrite_data(handle=%p) -> %d", data, result)); + if(result) + DEBUGF(infof(data, DMSG(data, "readwrite_data() -> %d"), result)); return result; } CURLcode Curl_done_sending(struct Curl_easy *data, struct SingleRequest *k) { - struct connectdata *conn = data->conn; k->keepon &= ~KEEP_SEND; /* we're done writing */ /* These functions should be moved into the handler struct! */ - Curl_http2_done_sending(data, conn); - Curl_quic_done_sending(data); + Curl_conn_ev_data_done_send(data); return CURLE_OK; } @@ -1088,6 +1055,7 @@ CURLcode Curl_readwrite(struct connectdata *conn, { struct SingleRequest *k = &data->req; CURLcode result; + struct curltime now; int didwhat = 0; curl_socket_t fd_read; @@ -1113,6 +1081,8 @@ CURLcode Curl_readwrite(struct connectdata *conn, if(data->state.drain) { select_res |= CURL_CSELECT_IN; DEBUGF(infof(data, "Curl_readwrite: forcibly told to drain data")); + if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) + select_res |= CURL_CSELECT_OUT; } #endif @@ -1155,7 +1125,7 @@ CURLcode Curl_readwrite(struct connectdata *conn, } #endif - k->now = Curl_now(); + now = Curl_now(); if(!didwhat) { /* no read no write, this is a timeout? */ if(k->exp100 == EXP100_AWAITING_CONTINUE) { @@ -1172,7 +1142,7 @@ CURLcode Curl_readwrite(struct connectdata *conn, */ - timediff_t ms = Curl_timediff(k->now, k->start100); + timediff_t ms = Curl_timediff(now, k->start100); if(ms >= data->set.expect_100_timeout) { /* we've waited long enough, continue anyway */ k->exp100 = EXP100_SEND_DATA; @@ -1182,35 +1152,31 @@ CURLcode Curl_readwrite(struct connectdata *conn, } } -#ifdef ENABLE_QUIC - if(conn->transport == TRNSPRT_QUIC) { - result = Curl_quic_idle(data); - if(result) - goto out; - } -#endif + result = Curl_conn_ev_data_idle(data); + if(result) + goto out; } if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, k->now); + result = Curl_speedcheck(data, now); if(result) goto out; if(k->keepon) { - if(0 > Curl_timeleft(data, &k->now, FALSE)) { + if(0 > Curl_timeleft(data, &now, FALSE)) { if(k->size != -1) { failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %" CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(k->now, data->progress.t_startsingle), + Curl_timediff(now, data->progress.t_startsingle), k->bytecount, k->size); } else { failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T " milliseconds with %" CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_timediff(k->now, data->progress.t_startsingle), + Curl_timediff(now, data->progress.t_startsingle), k->bytecount); } result = CURLE_OPERATION_TIMEDOUT; @@ -1264,7 +1230,8 @@ CURLcode Curl_readwrite(struct connectdata *conn, KEEP_RECV_PAUSE|KEEP_SEND_PAUSE))) ? TRUE : FALSE; result = CURLE_OK; out: - DEBUGF(infof(data, "Curl_readwrite(handle=%p) -> %d", data, result)); + if(result) + DEBUGF(infof(data, DMSG(data, "Curl_readwrite() -> %d"), result)); return result; } @@ -1377,6 +1344,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) data->state.authhost.want = data->set.httpauth; data->state.authproxy.want = data->set.proxyauth; Curl_safefree(data->info.wouldredirect); + Curl_data_priority_clear_state(data); if(data->state.httpreq == HTTPREQ_PUT) data->state.infilesize = data->set.filesize; @@ -1389,15 +1357,16 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) else data->state.infilesize = 0; -#ifndef CURL_DISABLE_COOKIES /* If there is a list of cookie files to read, do it now! */ - if(data->state.cookielist) - Curl_cookie_loadfiles(data); -#endif + Curl_cookie_loadfiles(data); + /* If there is a list of host pairs to deal with */ if(data->state.resolve) result = Curl_loadhostpairs(data); + /* If there is a list of hsts files to read */ + Curl_hsts_loadfiles(data); + if(!result) { /* Allow data->set.use_port to set which port to use. This needs to be * disabled for example when we follow Location: headers to URLs using @@ -1433,7 +1402,6 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) } } #endif - Curl_http2_init_state(&data->state); result = Curl_hsts_loadcb(data, data->hsts); } @@ -1871,7 +1839,7 @@ Curl_setup_transfer( httpsending = ((conn->handler->protocol&PROTO_FAMILY_HTTP) && (http->sending == HTTPSEND_REQUEST)); - if(conn->bits.multiplex || conn->httpversion == 20 || httpsending) { + if(conn->bits.multiplex || conn->httpversion >= 20 || httpsending) { /* when multiplexing, the read/write sockets need to be the same! */ conn->sockfd = sockindex == -1 ? ((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) : diff --git a/Utilities/cmcurl/lib/transfer.h b/Utilities/cmcurl/lib/transfer.h index 4092508729..536ac249b7 100644 --- a/Utilities/cmcurl/lib/transfer.h +++ b/Utilities/cmcurl/lib/transfer.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/url.c b/Utilities/cmcurl/lib/url.c index 3ab63a068f..1bb93df913 100644 --- a/Utilities/cmcurl/lib/url.c +++ b/Utilities/cmcurl/lib/url.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -431,10 +431,17 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_dyn_free(&data->state.headerb); Curl_safefree(data->state.ulbuf); Curl_flush_cookies(data, TRUE); +#ifndef CURL_DISABLE_COOKIES + curl_slist_free_all(data->set.cookielist); /* clean up list */ +#endif Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]); Curl_altsvc_cleanup(&data->asi); Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); - Curl_hsts_cleanup(&data->hsts); +#ifndef CURL_DISABLE_HSTS + if(!data->share || !data->share->hsts) + Curl_hsts_cleanup(&data->hsts); + curl_slist_free_all(data->set.hstslist); /* clean up list */ +#endif #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) Curl_http_auth_cleanup_digest(data); #endif @@ -445,7 +452,7 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_resolver_cancel(data); Curl_resolver_cleanup(data->state.async.resolver); - Curl_http2_cleanup_dependencies(data); + Curl_data_priority_cleanup(data); /* No longer a dirty share, if it exists */ if(data->share) { @@ -531,11 +538,11 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) /* Timeout every 24 hours by default */ set->general_ssl.ca_cache_timeout = 24 * 60 * 60; - set->proxyport = 0; - set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */ set->httpauth = CURLAUTH_BASIC; /* defaults to basic */ #ifndef CURL_DISABLE_PROXY + set->proxyport = 0; + set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */ set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */ /* SOCKS5 proxy auth defaults to username/password + GSS-API */ set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI; @@ -556,11 +563,11 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) #endif set->ssl.primary.verifypeer = TRUE; set->ssl.primary.verifyhost = TRUE; -#ifdef USE_TLS_SRP - set->ssl.primary.authtype = CURL_TLSAUTH_NONE; -#endif - /* defaults to any auth type */ +#ifdef USE_SSH + /* defaults to any auth type */ set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; + set->new_directory_perms = 0755; /* Default permissions */ +#endif set->ssl.primary.sessionid = TRUE; /* session ID caching enabled by default */ #ifndef CURL_DISABLE_PROXY @@ -568,7 +575,6 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) #endif set->new_file_perms = 0644; /* Default permissions */ - set->new_directory_perms = 0755; /* Default permissions */ set->allowed_protocols = (curl_prot_t) CURLPROTO_ALL; set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | CURLPROTO_FTPS; @@ -631,14 +637,15 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->maxage_conn = 118; set->maxlifetime_conn = 0; set->http09_allowed = FALSE; - set->httpwant = #ifdef USE_HTTP2 - CURL_HTTP_VERSION_2TLS + set->httpwant = CURL_HTTP_VERSION_2TLS #else - CURL_HTTP_VERSION_1_1 + set->httpwant = CURL_HTTP_VERSION_1_1 #endif ; - Curl_http2_init_userset(set); +#if defined(USE_HTTP2) || defined(USE_HTTP3) + memset(&set->priority, 0, sizeof(set->priority)); +#endif set->quick_exit = 0L; return result; } @@ -698,45 +705,6 @@ CURLcode Curl_open(struct Curl_easy **curl) return result; } -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND -static void conn_reset_postponed_data(struct connectdata *conn, int num) -{ - struct postponed_data * const psnd = &(conn->postponed[num]); - if(psnd->buffer) { - DEBUGASSERT(psnd->allocated_size > 0); - DEBUGASSERT(psnd->recv_size <= psnd->allocated_size); - DEBUGASSERT(psnd->recv_size ? - (psnd->recv_processed < psnd->recv_size) : - (psnd->recv_processed == 0)); - DEBUGASSERT(psnd->bindsock != CURL_SOCKET_BAD); - free(psnd->buffer); - psnd->buffer = NULL; - psnd->allocated_size = 0; - psnd->recv_size = 0; - psnd->recv_processed = 0; -#ifdef DEBUGBUILD - psnd->bindsock = CURL_SOCKET_BAD; /* used only for DEBUGASSERT */ -#endif /* DEBUGBUILD */ - } - else { - DEBUGASSERT(psnd->allocated_size == 0); - DEBUGASSERT(psnd->recv_size == 0); - DEBUGASSERT(psnd->recv_processed == 0); - DEBUGASSERT(psnd->bindsock == CURL_SOCKET_BAD); - } -} - -static void conn_reset_all_postponed_data(struct connectdata *conn) -{ - conn_reset_postponed_data(conn, 0); - conn_reset_postponed_data(conn, 1); -} -#else /* ! USE_RECV_BEFORE_SEND_WORKAROUND */ -/* Use "do-nothing" macro instead of function when workaround not used */ -#define conn_reset_all_postponed_data(c) do {} while(0) -#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */ - - static void conn_shutdown(struct Curl_easy *data) { DEBUGASSERT(data); @@ -777,13 +745,14 @@ 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); Curl_safefree(conn->secondaryhostname); - conn_reset_all_postponed_data(conn); Curl_llist_destroy(&conn->easyq, NULL); Curl_safefree(conn->localdev); Curl_free_primary_ssl_config(&conn->ssl_config); @@ -867,24 +836,6 @@ void Curl_disconnect(struct Curl_easy *data, } /* - * This function should return TRUE if the socket is to be assumed to - * be dead. Most commonly this happens when the server has closed the - * connection due to inactivity. - */ -static bool SocketIsDead(curl_socket_t sock) -{ - int sval; - bool ret_val = TRUE; - - sval = SOCKET_READABLE(sock, 0); - if(sval == 0) - /* timeout */ - ret_val = FALSE; - - return ret_val; -} - -/* * IsMultiplexingPossible() * * Return a bitmask with the available multiplexing options for the given @@ -1014,8 +965,7 @@ static bool extract_if_dead(struct connectdata *conn, } else { - /* Use the general method for determining the death of a connection */ - dead = SocketIsDead(conn->sock[FIRSTSOCKET]); + dead = !Curl_conn_is_alive(data, conn); } if(dead) { @@ -1344,8 +1294,10 @@ ConnectionExists(struct Curl_easy *data, /* If multiplexing isn't enabled on the h2 connection and h1 is explicitly requested, handle it: */ if((needle->handler->protocol & PROTO_FAMILY_HTTP) && - (check->httpversion >= 20) && - (data->state.httpwant < CURL_HTTP_VERSION_2_0)) + (((check->httpversion >= 20) && + (data->state.httpwant < CURL_HTTP_VERSION_2_0)) + || ((check->httpversion >= 30) && + (data->state.httpwant < CURL_HTTP_VERSION_3)))) continue; if(get_protocol_family(needle->handler) == PROTO_FAMILY_SSH) { @@ -1467,9 +1419,8 @@ ConnectionExists(struct Curl_easy *data, #ifdef USE_NGHTTP2 /* If multiplexed, make sure we don't go over concurrency limit */ if(check->bits.multiplex) { - /* Multiplexed connections can only be HTTP/2 for now */ - struct http_conn *httpc = &check->proto.httpc; - if(multiplexed >= httpc->settings.max_concurrent_streams) { + if(multiplexed >= Curl_conn_get_max_concurrent(data, check, + FIRSTSOCKET)) { infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)", multiplexed); continue; @@ -1551,15 +1502,9 @@ 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->tempsock[0] = CURL_SOCKET_BAD; /* no file descriptor */ - conn->tempsock[1] = CURL_SOCKET_BAD; /* no file descriptor */ conn->connection_id = -1; /* no ID */ conn->port = -1; /* unknown at this point */ conn->remote_port = -1; /* unknown at this point */ -#if defined(USE_RECV_BEFORE_SEND_WORKAROUND) && defined(DEBUGBUILD) - conn->postponed[0].bindsock = CURL_SOCKET_BAD; /* no file descriptor */ - conn->postponed[1].bindsock = CURL_SOCKET_BAD; /* no file descriptor */ -#endif /* USE_RECV_BEFORE_SEND_WORKAROUND && DEBUGBUILD */ /* Default protocol-independent behavior doesn't support persistent connections, so we set this to force-close. Protocols that support @@ -2329,7 +2274,7 @@ static CURLcode parse_proxy(struct Curl_easy *data, result = CURLE_OUT_OF_MEMORY; goto error; } - /* path will be "/", if no path was was found */ + /* path will be "/", if no path was found */ if(strcmp("/", path)) { is_unix_proxy = TRUE; free(host); @@ -2412,6 +2357,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, char *socksproxy = NULL; char *no_proxy = NULL; CURLcode result = CURLE_OK; + bool spacesep = FALSE; /************************************************************* * Extract the user and password from the authentication string @@ -2458,7 +2404,8 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, } if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? - data->set.str[STRING_NOPROXY] : no_proxy)) { + data->set.str[STRING_NOPROXY] : no_proxy, + &spacesep)) { Curl_safefree(proxy); Curl_safefree(socksproxy); } @@ -2467,6 +2414,8 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data, /* if the host is not in the noproxy list, detect proxy. */ proxy = detect_proxy(data, conn); #endif /* CURL_DISABLE_HTTP */ + if(spacesep) + infof(data, "space-separated NOPROXY patterns are deprecated"); Curl_safefree(no_proxy); @@ -2795,7 +2744,7 @@ static CURLcode override_login(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } /* no user was set but a password, set a blank user */ - if(userp && !*userp && *passwdp) { + if(!*userp && *passwdp) { *userp = strdup(""); if(!*userp) return CURLE_OUT_OF_MEMORY; @@ -3345,12 +3294,6 @@ static void reuse_conn(struct Curl_easy *data, struct connectdata *temp, struct connectdata *existing) { - /* 'local_ip' and 'local_port' get filled with local's numerical - ip address and port number whenever an outgoing connection is - **established** from the primary socket to a remote address. */ - char local_ip[MAX_IPADR_LEN] = ""; - int local_port = -1; - /* get the user+password information from the temp struct since it may * be new for this request even when we re-use an existing connection */ if(temp->user) { @@ -3382,6 +3325,20 @@ static void reuse_conn(struct Curl_easy *data, } #endif + /* Finding a connection for reuse in the cache matches, among other + * things on the "remote-relevant" hostname. This is not necessarily + * the authority of the URL, e.g. conn->host. For example: + * - we use a proxy (not tunneling). we want to send all requests + * that use the same proxy on this connection. + * - we have a "connect-to" setting that may redirect the hostname of + * a new request to the same remote endpoint of an existing conn. + * We want to reuse an existing conn to the remote endpoint. + * Since connection reuse does not match on conn->host necessarily, we + * switch `existing` conn to `temp` conn's host settings. + * TODO: is this correct in the case of TLS connections that have + * used the original hostname in SNI to negotiate? Do we send + * requests for another host through the different SNI? + */ Curl_free_idnconverted_hostname(&existing->host); Curl_free_idnconverted_hostname(&existing->conn_to_host); Curl_safefree(existing->host.rawalloc); @@ -3398,15 +3355,6 @@ static void reuse_conn(struct Curl_easy *data, existing->hostname_resolve = temp->hostname_resolve; temp->hostname_resolve = NULL; - /* persist connection info in session handle */ - if(existing->transport == TRNSPRT_TCP) { - Curl_conninfo_local(data, existing->sock[FIRSTSOCKET], - local_ip, &local_port); - } - Curl_persistconninfo(data, existing, local_ip, local_port); - - conn_reset_all_postponed_data(temp); /* free buffers */ - /* re-use init */ existing->bits.reuse = TRUE; /* yes, we're re-using here */ @@ -3880,6 +3828,13 @@ static CURLcode create_conn(struct Curl_easy *data, * Resolve the address of the server or proxy *************************************************************/ result = resolve_server(data, conn, async); + if(result) + goto out; + + /* Everything general done, inform filters that they need + * to prepare for a data transfer. + */ + result = Curl_conn_ev_data_setup(data); out: return result; @@ -4007,7 +3962,6 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) data->state.httpreq = HTTPREQ_HEAD; k->start = Curl_now(); /* start time */ - k->now = k->start; /* current time is now */ k->header = TRUE; /* assume header */ k->bytecount = 0; k->ignorebody = FALSE; @@ -4018,3 +3972,103 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn) return CURLE_OK; } + +#if defined(USE_HTTP2) || defined(USE_HTTP3) + +#ifdef USE_NGHTTP2 + +static void priority_remove_child(struct Curl_easy *parent, + struct Curl_easy *child) +{ + struct Curl_data_prio_node **pnext = &parent->set.priority.children; + struct Curl_data_prio_node *pnode = parent->set.priority.children; + + DEBUGASSERT(child->set.priority.parent == parent); + while(pnode && pnode->data != child) { + pnext = &pnode->next; + pnode = pnode->next; + } + + DEBUGASSERT(pnode); + if(pnode) { + *pnext = pnode->next; + free(pnode); + } + + child->set.priority.parent = 0; + child->set.priority.exclusive = FALSE; +} + +CURLcode Curl_data_priority_add_child(struct Curl_easy *parent, + struct Curl_easy *child, + bool exclusive) +{ + if(child->set.priority.parent) { + priority_remove_child(child->set.priority.parent, child); + } + + if(parent) { + struct Curl_data_prio_node **tail; + struct Curl_data_prio_node *pnode; + + pnode = calloc(1, sizeof(*pnode)); + if(!pnode) + return CURLE_OUT_OF_MEMORY; + pnode->data = child; + + if(parent->set.priority.children && exclusive) { + /* exclusive: move all existing children underneath the new child */ + struct Curl_data_prio_node *node = parent->set.priority.children; + while(node) { + node->data->set.priority.parent = child; + node = node->next; + } + + tail = &child->set.priority.children; + while(*tail) + tail = &(*tail)->next; + + DEBUGASSERT(!*tail); + *tail = parent->set.priority.children; + parent->set.priority.children = 0; + } + + tail = &parent->set.priority.children; + while(*tail) { + (*tail)->data->set.priority.exclusive = FALSE; + tail = &(*tail)->next; + } + + DEBUGASSERT(!*tail); + *tail = pnode; + } + + child->set.priority.parent = parent; + child->set.priority.exclusive = exclusive; + return CURLE_OK; +} + +#endif /* USE_NGHTTP2 */ + +void Curl_data_priority_cleanup(struct Curl_easy *data) +{ +#ifdef USE_NGHTTP2 + while(data->set.priority.children) { + struct Curl_easy *tmp = data->set.priority.children->data; + priority_remove_child(data, tmp); + if(data->set.priority.parent) + Curl_data_priority_add_child(data->set.priority.parent, tmp, FALSE); + } + + if(data->set.priority.parent) + priority_remove_child(data->set.priority.parent, data); +#endif + (void)data; +} + +void Curl_data_priority_clear_state(struct Curl_easy *data) +{ + memset(&data->state.priority, 0, sizeof(data->state.priority)); +} + +#endif /* defined(USE_HTTP2) || defined(USE_HTTP3) */ diff --git a/Utilities/cmcurl/lib/url.h b/Utilities/cmcurl/lib/url.h index 1a03c564c6..3b58df403e 100644 --- a/Utilities/cmcurl/lib/url.h +++ b/Utilities/cmcurl/lib/url.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -59,4 +59,20 @@ const struct Curl_handler *Curl_builtin_scheme(const char *scheme, void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn); #endif +#if defined(USE_HTTP2) || defined(USE_HTTP3) +void Curl_data_priority_cleanup(struct Curl_easy *data); +void Curl_data_priority_clear_state(struct Curl_easy *data); +#else +#define Curl_data_priority_cleanup(x) +#define Curl_data_priority_clear_state(x) +#endif /* !(defined(USE_HTTP2) || defined(USE_HTTP3)) */ + +#ifdef USE_NGHTTP2 +CURLcode Curl_data_priority_add_child(struct Curl_easy *parent, + struct Curl_easy *child, + bool exclusive); +#else +#define Curl_data_priority_add_child(x, y, z) CURLE_NOT_BUILT_IN +#endif + #endif /* HEADER_CURL_URL_H */ diff --git a/Utilities/cmcurl/lib/urlapi-int.h b/Utilities/cmcurl/lib/urlapi-int.h index 43a83ef6e4..28e5dd7ea4 100644 --- a/Utilities/cmcurl/lib/urlapi-int.h +++ b/Utilities/cmcurl/lib/urlapi-int.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/urlapi.c b/Utilities/cmcurl/lib/urlapi.c index b96af35ad2..29927b3d92 100644 --- a/Utilities/cmcurl/lib/urlapi.c +++ b/Utilities/cmcurl/lib/urlapi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -33,6 +33,7 @@ #include "inet_pton.h" #include "inet_ntop.h" #include "strdup.h" +#include "idn.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -116,14 +117,11 @@ static const char *find_host_sep(const char *url) } /* - * Decide in an encoding-independent manner whether a character in a URL must - * be escaped. This is used in urlencode_str(). + * Decide whether a character in a URL must be escaped. */ -static bool urlchar_needs_escaping(int c) -{ - return !(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c)); -} +#define urlchar_needs_escaping(c) (!(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c))) +static const char hexdigits[] = "0123456789abcdef"; /* urlencode_str() writes data into an output dynbuf and URL-encodes the * spaces in the source URL accordingly. * @@ -167,7 +165,10 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url, left = FALSE; if(urlchar_needs_escaping(*iptr)) { - if(Curl_dyn_addf(o, "%%%02x", *iptr)) + 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 { @@ -492,35 +493,21 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u, UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, bool has_scheme) { - char *portptr = NULL; - char endbracket; - int len; + char *portptr; char *hostname = Curl_dyn_ptr(host); /* * Find the end of an IPv6 address, either on the ']' ending bracket or * a percent-encoded zone index. */ - if(1 == sscanf(hostname, "[%*45[0123456789abcdefABCDEF:.]%c%n", - &endbracket, &len)) { - if(']' == endbracket) - portptr = &hostname[len]; - else if('%' == endbracket) { - int zonelen = len; - if(1 == sscanf(hostname + zonelen, "%*[^]]%c%n", &endbracket, &len)) { - if(']' != endbracket) - return CURLUE_BAD_IPV6; - portptr = &hostname[--zonelen + len + 1]; - } - else - return CURLUE_BAD_IPV6; - } - else + if(hostname[0] == '[') { + portptr = strchr(hostname, ']'); + if(!portptr) return CURLUE_BAD_IPV6; - + portptr++; /* this is a RFC2732-style specified IP-address */ - if(portptr && *portptr) { + if(*portptr) { if(*portptr != ':') - return CURLUE_BAD_IPV6; + return CURLUE_BAD_PORT_NUMBER; } else portptr = NULL; @@ -584,11 +571,9 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, hostname++; hlen -= 2; - if(hostname[hlen] != ']') - return CURLUE_BAD_IPV6; - - /* only valid letters are ok */ + /* only valid IPv6 letters are ok */ len = strspn(hostname, l); + if(hlen != len) { hlen = len; if(hostname[len] == '%') { @@ -602,8 +587,7 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, while(*h && (*h != ']') && (i < 15)) zoneid[i++] = *h++; if(!i || (']' != *h)) - /* impossible to reach? */ - return CURLUE_MALFORMED_INPUT; + return CURLUE_BAD_IPV6; zoneid[i] = 0; u->zoneid = strdup(zoneid); if(!u->zoneid) @@ -782,25 +766,28 @@ static CURLUcode decode_host(struct dynbuf *host) * * RETURNS * - * an allocated dedotdotified output string + * Zero for success and 'out' set to an allocated dedotdotified string. */ -UNITTEST char *dedotdotify(const char *input, size_t clen); -UNITTEST char *dedotdotify(const char *input, size_t clen) +UNITTEST int dedotdotify(const char *input, size_t clen, char **outp); +UNITTEST int dedotdotify(const char *input, size_t clen, char **outp) { - char *out = malloc(clen + 1); char *outptr; const char *orginput = input; char *queryp; + char *out; + + *outp = NULL; + /* the path always starts with a slash, and a slash has not dot */ + if((clen < 2) || !memchr(input, '.', clen)) + return 0; + + out = malloc(clen + 1); if(!out) - return NULL; /* out of memory */ + return 1; /* out of memory */ *out = 0; /* null-terminates, for inputs like "./" */ outptr = out; - if(!*input) - /* zero length input string, return that */ - return out; - /* * To handle query-parts properly, we must find it and remove it during the * dotdot-operation and then append it again at the end to the output @@ -905,7 +892,8 @@ UNITTEST char *dedotdotify(const char *input, size_t clen) memcpy(outptr, &orginput[oindex], qlen + 1); /* include zero byte */ } - return out; + *outp = out; + return 0; /* success */ } static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) @@ -1152,7 +1140,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) size_t qlen = strlen(query) - fraglen; /* includes '?' */ pathlen = strlen(path) - qlen - fraglen; if(qlen > 1) { - if(qlen && (flags & CURLU_URLENCODE)) { + if(flags & CURLU_URLENCODE) { struct dynbuf enc; Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); /* skip the leading question mark */ @@ -1199,8 +1187,8 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) path = u->path = Curl_dyn_ptr(&enc); } - if(!pathlen) { - /* there is no path left, unset */ + if(pathlen <= 1) { + /* there is no path left or just the slash, unset */ path = NULL; } else { @@ -1224,13 +1212,16 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(!(flags & CURLU_PATH_AS_IS)) { /* remove ../ and ./ sequences according to RFC3986 */ - char *newp = dedotdotify((char *)path, pathlen); - if(!newp) { + char *dedot; + int err = dedotdotify((char *)path, pathlen, &dedot); + if(err) { result = CURLUE_OUT_OF_MEMORY; goto fail; } - free(u->path); - u->path = newp; + if(dedot) { + free(u->path); + u->path = dedot; + } } } @@ -1379,6 +1370,7 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what, char portbuf[7]; bool urldecode = (flags & CURLU_URLDECODE)?1:0; bool urlencode = (flags & CURLU_URLENCODE)?1:0; + bool punycode = FALSE; bool plusdecode = FALSE; (void)flags; if(!u) @@ -1408,6 +1400,7 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what, case CURLUPART_HOST: ptr = u->host; ifmissing = CURLUE_NO_HOST; + punycode = (flags & CURLU_PUNYCODE)?1:0; break; case CURLUPART_ZONEID: ptr = u->zoneid; @@ -1460,6 +1453,7 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what, char *options = u->options; char *port = u->port; char *allochost = NULL; + punycode = (flags & CURLU_PUNYCODE)?1:0; if(u->scheme && strcasecompare("file", u->scheme)) { url = aprintf("file://%s%s%s", u->path, @@ -1514,6 +1508,17 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what, if(!allochost) return CURLUE_OUT_OF_MEMORY; } + else if(punycode) { + if(!Curl_is_ASCII_name(u->host)) { +#ifndef USE_IDN + return CURLUE_LACKS_IDN; +#else + allochost = Curl_idn_decode(u->host); + if(!allochost) + return CURLUE_OUT_OF_MEMORY; +#endif + } + } else { /* only encode '%' in output host name */ char *host = u->host; @@ -1611,6 +1616,19 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what, free(*part); *part = Curl_dyn_ptr(&enc); } + else if(punycode) { + if(!Curl_is_ASCII_name(u->host)) { +#ifndef USE_IDN + return CURLUE_LACKS_IDN; +#else + char *allochost = Curl_idn_decode(*part); + if(!allochost) + return CURLUE_OUT_OF_MEMORY; + free(*part); + *part = allochost; +#endif + } + } return CURLUE_OK; } @@ -1807,7 +1825,10 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, return CURLUE_OUT_OF_MEMORY; } else { - result = Curl_dyn_addf(&enc, "%%%02x", *i); + char out[3]={'%'}; + out[1] = hexdigits[*i>>4]; + out[2] = hexdigits[*i & 0xf]; + result = Curl_dyn_addn(&enc, out, 3); if(result) return CURLUE_OUT_OF_MEMORY; } diff --git a/Utilities/cmcurl/lib/urldata.h b/Utilities/cmcurl/lib/urldata.h index 3d7545c689..4cfffa7737 100644 --- a/Utilities/cmcurl/lib/urldata.h +++ b/Utilities/cmcurl/lib/urldata.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -113,23 +113,6 @@ typedef unsigned int curl_prot_t; input easier and better. */ #define CURL_MAX_INPUT_LENGTH 8000000 -/* Macros intended for DEBUGF logging, use like: - * DEBUGF(infof(data, CFMSG(cf, "this filter %s rocks"), "very much")); - * and it will output: - * [CONN-1-0][CF-SSL] this filter very much rocks - * on connection #1 with sockindex 0 for filter of type "SSL". */ -#define DMSG(d,msg) \ - "[CONN-%ld] "msg, (d)->conn->connection_id -#define DMSGI(d,i,msg) \ - "[CONN-%ld-%d] "msg, (d)->conn->connection_id, (i) -#define CMSG(c,msg) \ - "[CONN-%ld] "msg, (conn)->connection_id -#define CMSGI(c,i,msg) \ - "[CONN-%ld-%d] "msg, (conn)->connection_id, (i) -#define CFMSG(cf,msg) \ - "[CONN-%ld-%d][CF-%s] "msg, (cf)->conn->connection_id, \ - (cf)->sockindex, (cf)->cft->name - #include "cookie.h" #include "psl.h" @@ -187,8 +170,8 @@ typedef CURLcode (*Curl_datastream)(struct Curl_easy *data, #include "mqtt.h" #include "wildcard.h" #include "multihandle.h" -#include "quic.h" #include "c-hyper.h" +#include "cf-socket.h" #ifdef HAVE_GSSAPI # ifdef HAVE_GSSGNU @@ -268,8 +251,6 @@ typedef enum { struct ssl_backend_data; struct ssl_primary_config { - long version; /* what version the client wants to use */ - long version_max; /* max supported version the client wants to use */ char *CApath; /* certificate dir (doesn't work on windows) */ char *CAfile; /* certificate to verify peer against */ char *issuercert; /* optional issuer certificate filename */ @@ -284,10 +265,11 @@ struct ssl_primary_config { #ifdef USE_TLS_SRP char *username; /* TLS username (for, e.g., SRP) */ char *password; /* TLS password (for, e.g., SRP) */ - enum CURL_TLSAUTH authtype; /* TLS authentication type (default SRP) */ #endif char *curves; /* list of curves to use */ unsigned char ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ + unsigned int version_max; /* max supported version the client wants to use */ + unsigned char version; /* what version the client wants to use */ BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ BIT(verifystatus); /* set TRUE if certificate status must be checked */ @@ -648,7 +630,6 @@ struct SingleRequest { curl_off_t pendingheader; /* this many bytes left to send is actually header and not body */ struct curltime start; /* transfer started at this time */ - struct curltime now; /* current time */ enum { HEADER_NORMAL, /* no bad header at all */ HEADER_PARTHEADER, /* part of the chunk is a bad header, the rest @@ -707,6 +688,7 @@ struct SingleRequest { struct dohdata *doh; /* DoH specific data for this request */ #endif unsigned char setcookies; + unsigned char writer_stack_depth; /* Unencoding stack depth. */ BIT(header); /* incoming data has HTTP header */ BIT(content_range); /* set TRUE if Content-Range: was found */ BIT(upload_done); /* set to TRUE when doing chunked transfer-encoding @@ -783,8 +765,8 @@ struct Curl_handler { /* This function *MAY* be set to a protocol-dependent function that is run * by the curl_disconnect(), as a step in the disconnection. If the handler * is called because the connection has been considered dead, - * dead_connection is set to TRUE. The connection is already disassociated - * from the transfer here. + * dead_connection is set to TRUE. The connection is (again) associated with + * the transfer here. */ CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *, bool dead_connection); @@ -830,7 +812,7 @@ struct Curl_handler { #define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per request instead of per connection */ #define PROTOPT_ALPN (1<<8) /* set ALPN for this */ -#define PROTOPT_STREAM (1<<9) /* a protocol with individual logical streams */ +/* (1<<9) was PROTOPT_STREAM, now free */ #define PROTOPT_URLOPTIONS (1<<10) /* allow options part in the userinfo field of the URL */ #define PROTOPT_PROXY_AS_HTTP (1<<11) /* allow this non-HTTP scheme over a @@ -848,20 +830,6 @@ struct Curl_handler { #define CONNRESULT_NONE 0 /* No extra information. */ #define CONNRESULT_DEAD (1<<0) /* The connection is dead. */ -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND -struct postponed_data { - char *buffer; /* Temporal store for received data during - sending, must be freed */ - size_t allocated_size; /* Size of temporal store */ - size_t recv_size; /* Size of received data during sending */ - size_t recv_processed; /* Size of processed part of postponed data */ -#ifdef DEBUGBUILD - curl_socket_t bindsock;/* Structure must be bound to specific socket, - used only for DEBUGASSERT */ -#endif /* DEBUGBUILD */ -}; -#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */ - struct proxy_info { struct hostname host; int port; @@ -909,16 +877,9 @@ struct connectdata { there is no name resolve done. */ struct Curl_dns_entry *dns_entry; - /* 'ip_addr' is the particular IP we connected to. It points to a struct - within the DNS cache, so this pointer is only valid as long as the DNS - cache entry remains locked. It gets unlocked in multi_done() */ - struct Curl_addrinfo *ip_addr; - struct Curl_addrinfo *tempaddr[2]; /* for happy eyeballs */ - -#ifdef ENABLE_QUIC - struct quicsocket hequic[2]; /* two, for happy eyeballs! */ - struct quicsocket *quic; -#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). */ + const struct Curl_sockaddr_ex *remote_addr; struct hostname host; char *hostname_resolve; /* host name to resolve to address, allocated */ @@ -947,31 +908,16 @@ struct connectdata { struct curltime lastused; /* when returned to the connection cache */ curl_socket_t sock[2]; /* two sockets, the second is used for the data transfer when doing FTP */ - curl_socket_t tempsock[2]; /* temporary sockets for happy eyeballs */ - int tempfamily[2]; /* family used for the temp sockets */ Curl_recv *recv[2]; Curl_send *send[2]; struct Curl_cfilter *cfilter[2]; /* connection filters */ -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND - struct postponed_data postponed[2]; /* two buffers for two sockets */ -#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */ struct ssl_primary_config ssl_config; #ifndef CURL_DISABLE_PROXY struct ssl_primary_config proxy_ssl_config; #endif struct ConnectBits bits; /* various state-flags for this connection */ - /* connecttime: when connect() is called on the current IP address. Used to - be able to track when to move on to try next IP - but only when the multi - interface is used. */ - struct curltime connecttime; - - /* The field below gets set in Curl_connecthost */ - /* how long time in milliseconds to spend on trying to connect to each IP - address, per family */ - timediff_t timeoutms_per_addr[2]; - const struct Curl_handler *handler; /* Connection's protocol handler */ const struct Curl_handler *given; /* The protocol first given */ @@ -1034,16 +980,15 @@ 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; #endif -#ifndef CURL_DISABLE_HTTP - struct http_conn httpc; -#endif #ifdef USE_SSH struct ssh_conn sshc; #endif @@ -1070,6 +1015,9 @@ struct connectdata { #ifndef CURL_DISABLE_MQTT struct mqtt_conn mqtt; #endif +#ifdef USE_WEBSOCKETS + struct ws_conn ws; +#endif } proto; struct connectbundle *bundle; /* The bundle we are member of */ @@ -1086,14 +1034,13 @@ struct connectdata { that subsequent bound-requested connections aren't accidentally re-using wrong connections. */ char *localdev; - int localportrange; + unsigned short localportrange; int cselect_bits; /* bitmask of socket events */ int waitfor; /* current READ/WRITE bits to wait for */ #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) int socks5_gssapi_enctype; #endif - /* The field below gets set in Curl_connecthost */ - int num_addr; /* number of addresses to try to connect to */ + /* The field below gets set in connect.c:connecthost() */ int port; /* which port to use locally - to connect to */ int remote_port; /* the remote port, not the proxy port! */ int conn_to_port; /* the remote port to connect to. valid only if @@ -1238,10 +1185,29 @@ struct auth { should be RFC compliant */ }; -struct Curl_http2_dep { - struct Curl_http2_dep *next; +#ifdef USE_NGHTTP2 +struct Curl_data_prio_node { + struct Curl_data_prio_node *next; struct Curl_easy *data; }; +#endif + +/** + * Priority information for an easy handle in relation to others + * on the same connection. + * TODO: we need to adapt it to the new priority scheme as defined in RFC 9218 + */ +struct Curl_data_priority { +#ifdef USE_NGHTTP2 + /* tree like dependencies only implemented in nghttp2 */ + struct Curl_easy *parent; + struct Curl_data_prio_node *children; +#endif + int weight; +#ifdef USE_NGHTTP2 + BIT(exclusive); +#endif +}; /* * This struct is for holding data that was attempted to get sent to the user's @@ -1270,6 +1236,7 @@ typedef enum { EXPIRE_TOOFAST, EXPIRE_QUIC, EXPIRE_FTP_ACCEPT, + EXPIRE_ALPN_EYEBALLS, EXPIRE_LAST /* not an actual timer, used as a marker only */ } expire_id; @@ -1389,24 +1356,17 @@ struct UrlState { size_t drain; /* Increased when this stream has data to read, even if its socket is not necessarily is readable. Decreased when checked. */ + struct Curl_data_priority priority; /* shallow copy of data->set */ #endif curl_read_callback fread_func; /* read callback/function */ void *in; /* CURLOPT_READDATA */ -#ifdef USE_HTTP2 - struct Curl_easy *stream_depends_on; - int stream_weight; -#endif CURLU *uh; /* URL handle for the current parsed URL */ struct urlpieces up; unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any) is this */ char *url; /* work URL, copied from UserDefined */ char *referer; /* referer string */ -#ifndef CURL_DISABLE_COOKIES - struct curl_slist *cookielist; /* list of cookie files set by - curl_easy_setopt(COOKIEFILE) calls */ -#endif struct curl_slist *resolve; /* set to point to the set.resolve list when this should be dealt with in pretransfer */ #ifndef CURL_DISABLE_HTTP @@ -1471,7 +1431,6 @@ struct UrlState { BIT(done); /* set to FALSE when Curl_init_do() is called and set to TRUE when multi_done() is called, to prevent multi_done() to get invoked twice when the multi interface is used. */ - BIT(stream_depends_e); /* set or don't set the Exclusive bit */ BIT(previouslypending); /* this transfer WAS in the multi->pending queue */ BIT(cookie_engine); BIT(prefer_ascii); /* ASCII rather than binary */ @@ -1620,15 +1579,9 @@ struct UserDefined { void *out; /* CURLOPT_WRITEDATA */ void *in_set; /* CURLOPT_READDATA */ void *writeheader; /* write the header to this if non-NULL */ - unsigned short proxyport; /* If non-zero, use this port number by - default. If the proxy string features a - ":[port]" that one will override this. */ unsigned short use_port; /* which port to use (when not using default) */ unsigned long httpauth; /* kind of HTTP authentication to use (bitmask) */ unsigned long proxyauth; /* kind of proxy authentication to use (bitmask) */ -#ifndef CURL_DISABLE_PROXY - unsigned char socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */ -#endif long maxredirs; /* maximum no. of http(s) redirects to follow, set to -1 for infinity */ @@ -1638,8 +1591,9 @@ struct UserDefined { of strlen(), and then the data *may* be binary (contain zero bytes) */ unsigned short localport; /* local port number to bind to */ - int localportrange; /* number of additional port numbers to test in case the - 'localport' one can't be bind()ed */ + unsigned short localportrange; /* number of additional port numbers to test + in case the 'localport' one can't be + bind()ed */ curl_write_callback fwrite_func; /* function that stores the output */ curl_write_callback fwrite_header; /* function that stores headers */ curl_write_callback fwrite_rtp; /* function that stores interleaved RTP */ @@ -1661,7 +1615,13 @@ struct UserDefined { void *prereq_userp; /* pre-initial request user data */ void *seek_client; /* pointer to pass to the seek callback */ +#ifndef CURL_DISABLE_COOKIES + struct curl_slist *cookielist; /* list of cookie files set by + curl_easy_setopt(COOKIEFILE) calls */ +#endif #ifndef CURL_DISABLE_HSTS + struct curl_slist *hstslist; /* list of HSTS files set by + curl_easy_setopt(HSTS) calls */ curl_hstsread_callback hsts_read; void *hsts_read_userp; curl_hstswrite_callback hsts_write; @@ -1688,17 +1648,8 @@ struct UserDefined { download */ curl_off_t set_resume_from; /* continue [ftp] transfer from here */ struct curl_slist *headers; /* linked list of extra headers */ - struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */ struct curl_httppost *httppost; /* linked list of old POST data */ curl_mimepart mimepost; /* MIME/POST data. */ - struct curl_slist *quote; /* after connection is established */ - struct curl_slist *postquote; /* after the transfer */ - struct curl_slist *prequote; /* before the transfer, after type */ - struct curl_slist *source_quote; /* 3rd party quote */ - struct curl_slist *source_prequote; /* in 3rd party transfer mode - before - the transfer on source host */ - struct curl_slist *source_postquote; /* in 3rd party transfer mode - after - the transfer on source host */ #ifndef CURL_DISABLE_TELNET struct curl_slist *telnet_options; /* linked list of telnet options */ #endif @@ -1708,13 +1659,18 @@ struct UserDefined { the hostname and port to connect to */ time_t timevalue; /* what time to compare with */ unsigned char timecondition; /* kind of time comparison: curl_TimeCond */ - unsigned char proxytype; /* what kind of proxy: curl_proxytype */ unsigned char method; /* what kind of HTTP request: Curl_HttpReq */ unsigned char httpwant; /* when non-zero, a specific HTTP version requested to be used in the library's request(s) */ struct ssl_config_data ssl; /* user defined SSL stuff */ #ifndef CURL_DISABLE_PROXY struct ssl_config_data proxy_ssl; /* user defined SSL stuff for proxy */ + struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */ + unsigned short proxyport; /* If non-zero, use this port number by + default. If the proxy string features a + ":[port]" that one will override this. */ + unsigned char proxytype; /* what kind of proxy: curl_proxytype */ + unsigned char socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */ #endif struct ssl_general_config general_ssl; /* general user defined SSL stuff */ int dns_cache_timeout; /* DNS cache timeout (seconds) */ @@ -1722,7 +1678,9 @@ struct UserDefined { unsigned int upload_buffer_size; /* size of upload buffer to use, keep it >= CURL_MAX_WRITE_SIZE */ void *private_data; /* application-private data */ +#ifndef CURL_DISABLE_HTTP struct curl_slist *http200aliases; /* linked list of aliases for http200 */ +#endif unsigned char ipver; /* the CURL_IPRESOLVE_* defines in the public header file 0 - whatever, 1 - v2, 2 - v6 */ curl_off_t max_filesize; /* Maximum file size to download */ @@ -1732,26 +1690,32 @@ struct UserDefined { unsigned char ftp_ccc; /* FTP CCC options: curl_ftpccc */ unsigned int accepttimeout; /* in milliseconds, 0 means no timeout */ #endif - /* Desppie the name ftp_create_missing_dirs is for FTP(S) and SFTP +#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) + struct curl_slist *quote; /* after connection is established */ + struct curl_slist *postquote; /* after the transfer */ + struct curl_slist *prequote; /* before the transfer, after type */ + /* Despite the name, ftp_create_missing_dirs is for FTP(S) and SFTP 1 - create directories that don't exist 2 - the same but also allow MKD to fail once */ unsigned char ftp_create_missing_dirs; +#endif #ifdef USE_LIBSSH2 curl_sshhostkeycallback ssh_hostkeyfunc; /* hostkey check callback */ void *ssh_hostkeyfunc_userp; /* custom pointer to callback */ #endif - +#ifdef USE_SSH curl_sshkeycallback ssh_keyfunc; /* key matching callback */ void *ssh_keyfunc_userp; /* custom pointer to callback */ + int ssh_auth_types; /* allowed SSH auth types */ + unsigned int new_directory_perms; /* when creating remote dirs */ +#endif #ifndef CURL_DISABLE_NETRC unsigned char use_netrc; /* enum CURL_NETRC_OPTION values */ #endif curl_usessl use_ssl; /* if AUTH TLS is to be attempted etc, for FTP or IMAP or POP3 or others! */ unsigned int new_file_perms; /* when creating remote files */ - unsigned int new_directory_perms; /* when creating remote dirs */ - int ssh_auth_types; /* allowed SSH auth types */ char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ struct curl_blob *blobs[BLOB_LAST]; #ifdef ENABLE_IPV6 @@ -1759,8 +1723,9 @@ struct UserDefined { #endif curl_prot_t allowed_protocols; curl_prot_t redir_protocols; +#ifndef CURL_DISABLE_MIME unsigned int mime_options; /* Mime option flags. */ - +#endif #ifndef CURL_DISABLE_RTSP void *rtp_out; /* write RTP to this if non-NULL */ /* Common RTSP header options */ @@ -1775,8 +1740,9 @@ struct UserDefined { to pattern (e.g. if WILDCARDMATCH is on) */ void *fnmatch_data; #endif - long gssapi_delegation; /* GSS-API credential delegation, see the - documentation of CURLOPT_GSSAPI_DELEGATION */ + /* GSS-API credential delegation, see the documentation of + CURLOPT_GSSAPI_DELEGATION */ + unsigned char gssapi_delegation; int tcp_keepidle; /* seconds in idle before sending keepalive probe */ int tcp_keepintvl; /* seconds between TCP keepalive probes */ @@ -1784,10 +1750,8 @@ struct UserDefined { size_t maxconnects; /* Max idle connections in the connection cache */ long expect_100_timeout; /* in milliseconds */ -#ifdef USE_HTTP2 - struct Curl_easy *stream_depends_on; - int stream_weight; - struct Curl_http2_dep *stream_dependents; +#if defined(USE_HTTP2) || defined(USE_HTTP3) + struct Curl_data_priority priority; #endif curl_resolver_start_callback resolver_start; /* optional callback called before resolver start */ @@ -1798,8 +1762,10 @@ struct UserDefined { struct Curl_easy *dohfor; /* this is a DoH request for that transfer */ #endif CURLU *uh; /* URL handle for the current parsed URL */ +#ifndef CURL_DISABLE_HTTP void *trailer_data; /* pointer to pass to trailer data callback */ curl_trailer_callback trailer_callback; /* trailing data callback */ +#endif char keep_post; /* keep POSTs as POSTs after a 30x request; each bit represents a request, from 301 to 303 */ #ifndef CURL_DISABLE_SMTP @@ -1877,7 +1843,6 @@ struct UserDefined { BIT(suppress_connect_headers); /* suppress proxy CONNECT response headers from user callbacks */ BIT(dns_shuffle_addresses); /* whether to shuffle addresses before use */ - BIT(stream_depends_e); /* set or don't set the Exclusive bit */ BIT(haproxyprotocol); /* whether to send HAProxy PROXY protocol v1 header */ BIT(abstract_unix_socket); diff --git a/Utilities/cmcurl/lib/vauth/cleartext.c b/Utilities/cmcurl/lib/vauth/cleartext.c index b82b171467..c651fc5145 100644 --- a/Utilities/cmcurl/lib/vauth/cleartext.c +++ b/Utilities/cmcurl/lib/vauth/cleartext.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -28,7 +28,8 @@ #include "curl_setup.h" #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ - !defined(CURL_DISABLE_POP3) + !defined(CURL_DISABLE_POP3) || \ + (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) #include <curl/curl.h> #include "urldata.h" diff --git a/Utilities/cmcurl/lib/vauth/cram.c b/Utilities/cmcurl/lib/vauth/cram.c index 475d31b8d7..5894ed4bcf 100644 --- a/Utilities/cmcurl/lib/vauth/cram.c +++ b/Utilities/cmcurl/lib/vauth/cram.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/digest.c b/Utilities/cmcurl/lib/vauth/digest.c index c81ce10958..b7a0d92a31 100644 --- a/Utilities/cmcurl/lib/vauth/digest.c +++ b/Utilities/cmcurl/lib/vauth/digest.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/digest.h b/Utilities/cmcurl/lib/vauth/digest.h index d785bdd91b..68fdb28c47 100644 --- a/Utilities/cmcurl/lib/vauth/digest.h +++ b/Utilities/cmcurl/lib/vauth/digest.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/digest_sspi.c b/Utilities/cmcurl/lib/vauth/digest_sspi.c index 6c95a3ef70..8fb8669393 100644 --- a/Utilities/cmcurl/lib/vauth/digest_sspi.c +++ b/Utilities/cmcurl/lib/vauth/digest_sspi.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2016, Steve Holme, <steve_holme@hotmail.com>. - * Copyright (C) 2015 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/gsasl.c b/Utilities/cmcurl/lib/vauth/gsasl.c index a73c644434..c7d0a8d3b2 100644 --- a/Utilities/cmcurl/lib/vauth/gsasl.c +++ b/Utilities/cmcurl/lib/vauth/gsasl.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Simon Josefsson, <simon@josefsson.org>, et al. + * Copyright (C) Simon Josefsson, <simon@josefsson.org>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c index bac78049dc..65eb3e1b50 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2019, Steve Holme, <steve_holme@hotmail.com>. - * Copyright (C) 2015 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/krb5_sspi.c b/Utilities/cmcurl/lib/vauth/krb5_sspi.c index 015bc66a35..c487149b9d 100644 --- a/Utilities/cmcurl/lib/vauth/krb5_sspi.c +++ b/Utilities/cmcurl/lib/vauth/krb5_sspi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2022, Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/vauth/ntlm.c b/Utilities/cmcurl/lib/vauth/ntlm.c index 0141e173fd..2a5d4a4908 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm.c +++ b/Utilities/cmcurl/lib/vauth/ntlm.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/ntlm.h b/Utilities/cmcurl/lib/vauth/ntlm.h index 14ebba2440..31ce921cd1 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm.h +++ b/Utilities/cmcurl/lib/vauth/ntlm.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c index 193576acaa..5118963f4d 100644 --- a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c +++ b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/oauth2.c b/Utilities/cmcurl/lib/vauth/oauth2.c index 1604b303e0..a4adbdcf15 100644 --- a/Utilities/cmcurl/lib/vauth/oauth2.c +++ b/Utilities/cmcurl/lib/vauth/oauth2.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -27,7 +27,8 @@ #include "curl_setup.h" #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \ - !defined(CURL_DISABLE_POP3) + !defined(CURL_DISABLE_POP3) || \ + (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP)) #include <curl/curl.h> #include "urldata.h" diff --git a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c index 25dff967d1..e1d52b755c 100644 --- a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c +++ b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/spnego_sspi.c b/Utilities/cmcurl/lib/vauth/spnego_sspi.c index d845caca6c..d3245d0b18 100644 --- a/Utilities/cmcurl/lib/vauth/spnego_sspi.c +++ b/Utilities/cmcurl/lib/vauth/spnego_sspi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vauth/vauth.c b/Utilities/cmcurl/lib/vauth/vauth.c index 58fe05139d..62fc7c40fe 100644 --- a/Utilities/cmcurl/lib/vauth/vauth.c +++ b/Utilities/cmcurl/lib/vauth/vauth.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2022, Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/vauth/vauth.h b/Utilities/cmcurl/lib/vauth/vauth.h index c310c66375..e17d7aad6e 100644 --- a/Utilities/cmcurl/lib/vauth/vauth.h +++ b/Utilities/cmcurl/lib/vauth/vauth.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2022, Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/version.c b/Utilities/cmcurl/lib/version.c index c80841c166..55b54a06e8 100644 --- a/Utilities/cmcurl/lib/version.c +++ b/Utilities/cmcurl/lib/version.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -24,12 +24,16 @@ #include "curl_setup.h" +#ifdef USE_NGHTTP2 +#include <nghttp2/nghttp2.h> +#endif + #include <curl/curl.h> #include "urldata.h" #include "vtls/vtls.h" #include "http2.h" #include "vssh/ssh.h" -#include "quic.h" +#include "vquic/vquic.h" #include "curl_printf.h" #include "easy_lock.h" diff --git a/Utilities/cmcurl/lib/version_win32.c b/Utilities/cmcurl/lib/version_win32.c index e8f14f9dff..872d5b4f3c 100644 --- a/Utilities/cmcurl/lib/version_win32.c +++ b/Utilities/cmcurl/lib/version_win32.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/version_win32.h b/Utilities/cmcurl/lib/version_win32.h index 7a9a6a14f1..3899174a31 100644 --- a/Utilities/cmcurl/lib/version_win32.h +++ b/Utilities/cmcurl/lib/version_win32.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>. + * Copyright (C) Steve Holme, <steve_holme@hotmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/vquic/curl_msh3.c b/Utilities/cmcurl/lib/vquic/curl_msh3.c new file mode 100644 index 0000000000..1930703418 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_msh3.c @@ -0,0 +1,841 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#ifdef USE_MSH3 + +#include "urldata.h" +#include "timeval.h" +#include "multiif.h" +#include "sendf.h" +#include "curl_log.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "connect.h" +#include "progress.h" +#include "h2h3.h" +#include "curl_msh3.h" +#include "socketpair.h" +#include "vquic/vquic.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +#define DEBUG_CF 1 + +#if DEBUG_CF && defined(DEBUGBUILD) +#define CF_DEBUGF(x) x +#else +#define CF_DEBUGF(x) do { } while(0) +#endif + +#define MSH3_REQ_INIT_BUF_LEN 16384 +#define MSH3_REQ_MAX_BUF_LEN 0x100000 + +#ifdef _WIN32 +#define msh3_lock CRITICAL_SECTION +#define msh3_lock_initialize(lock) InitializeCriticalSection(lock) +#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock) +#define msh3_lock_acquire(lock) EnterCriticalSection(lock) +#define msh3_lock_release(lock) LeaveCriticalSection(lock) +#else /* !_WIN32 */ +#include <pthread.h> +#define msh3_lock pthread_mutex_t +#define msh3_lock_initialize(lock) do { \ + pthread_mutexattr_t attr; \ + pthread_mutexattr_init(&attr); \ + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \ + pthread_mutex_init(lock, &attr); \ + pthread_mutexattr_destroy(&attr); \ +}while(0) +#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock) +#define msh3_lock_acquire(lock) pthread_mutex_lock(lock) +#define msh3_lock_release(lock) pthread_mutex_unlock(lock) +#endif /* _WIN32 */ + + +static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection, + void *IfContext); +static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection, + void *IfContext); +static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection, + void *IfContext, + MSH3_REQUEST *Request); +static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, + void *IfContext, + const MSH3_HEADER *Header); +static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, + void *IfContext, uint32_t *Length, + const uint8_t *Data); +static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, + bool Aborted, uint64_t AbortError); +static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request, + void *IfContext); +static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request, + void *IfContext, void *SendContext); + + +void Curl_msh3_ver(char *p, size_t len) +{ + uint32_t v[4]; + MsH3Version(v); + (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]); +} + +#define SP_LOCAL 0 +#define SP_REMOTE 1 + +struct cf_msh3_ctx { + MSH3_API *api; + MSH3_CONNECTION *qconn; + struct Curl_sockaddr_ex addr; + curl_socket_t sock[2]; /* fake socket pair until we get support in msh3 */ + char l_ip[MAX_IPADR_LEN]; /* local IP as string */ + int l_port; /* local port number */ + struct curltime connect_started; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + /* Flags written by msh3/msquic thread */ + bool handshake_complete; + bool handshake_succeeded; + bool connected; + /* Flags written by curl thread */ + BIT(verbose); + BIT(active); +}; + +static const MSH3_CONNECTION_IF msh3_conn_if = { + msh3_conn_connected, + msh3_conn_shutdown_complete, + msh3_conn_new_request +}; + +static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection, + void *IfContext) +{ + struct cf_msh3_ctx *ctx = IfContext; + (void)Connection; + if(ctx->verbose) + CF_DEBUGF(fprintf(stderr, "* [MSH3] evt: connected\n")); + ctx->handshake_succeeded = true; + ctx->connected = true; + ctx->handshake_complete = true; +} + +static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection, + void *IfContext) +{ + struct cf_msh3_ctx *ctx = IfContext; + (void)Connection; + if(ctx->verbose) + CF_DEBUGF(fprintf(stderr, "* [MSH3] evt: shutdown complete\n")); + ctx->connected = false; + ctx->handshake_complete = true; +} + +static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection, + void *IfContext, + MSH3_REQUEST *Request) +{ + (void)Connection; + (void)IfContext; + (void)Request; +} + +static const MSH3_REQUEST_IF msh3_request_if = { + msh3_header_received, + msh3_data_received, + msh3_complete, + msh3_shutdown_complete, + msh3_data_sent +}; + +static CURLcode msh3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct HTTP *stream = data->req.p.http; + (void)cf; + + DEBUGASSERT(stream); + if(!stream->recv_buf) { + DEBUGF(LOG_CF(data, cf, "req: setup")); + stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN); + if(!stream->recv_buf) { + return CURLE_OUT_OF_MEMORY; + } + stream->req = ZERO_NULL; + msh3_lock_initialize(&stream->recv_lock); + stream->recv_buf_alloc = MSH3_REQ_INIT_BUF_LEN; + stream->recv_buf_max = MSH3_REQ_MAX_BUF_LEN; + stream->recv_header_len = 0; + stream->recv_header_complete = false; + stream->recv_data_len = 0; + stream->recv_data_complete = false; + stream->recv_error = CURLE_OK; + } + return CURLE_OK; +} + +/* Requires stream->recv_lock to be held */ +static bool msh3request_ensure_room(struct HTTP *stream, size_t len) +{ + uint8_t *new_recv_buf; + const size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len; + + if(cur_recv_len + len > stream->recv_buf_alloc) { + size_t new_recv_buf_alloc_len = stream->recv_buf_alloc; + do { + new_recv_buf_alloc_len <<= 1; /* TODO - handle overflow */ + } while(cur_recv_len + len > new_recv_buf_alloc_len); + CF_DEBUGF(fprintf(stderr, "* enlarging buffer to %zu\n", + new_recv_buf_alloc_len)); + new_recv_buf = malloc(new_recv_buf_alloc_len); + if(!new_recv_buf) { + CF_DEBUGF(fprintf(stderr, "* FAILED: enlarging buffer to %zu\n", + new_recv_buf_alloc_len)); + return false; + } + if(cur_recv_len) { + memcpy(new_recv_buf, stream->recv_buf, cur_recv_len); + } + stream->recv_buf_alloc = new_recv_buf_alloc_len; + free(stream->recv_buf); + stream->recv_buf = new_recv_buf; + } + return true; +} + +static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, + void *IfContext, + const MSH3_HEADER *Header) +{ + struct Curl_easy *data = IfContext; + struct HTTP *stream = data->req.p.http; + size_t total_len; + (void)Request; + + if(stream->recv_header_complete) { + CF_DEBUGF(fprintf(stderr, "* ignoring header after data\n")); + return; + } + + msh3_lock_acquire(&stream->recv_lock); + + if((Header->NameLength == 7) && + !strncmp(H2H3_PSEUDO_STATUS, (char *)Header->Name, 7)) { + total_len = 10 + Header->ValueLength; + if(!msh3request_ensure_room(stream, total_len)) { + CF_DEBUGF(fprintf(stderr, "* ERROR: unable to buffer: %.*s\n", + (int)Header->NameLength, Header->Name)); + stream->recv_error = CURLE_OUT_OF_MEMORY; + goto release_lock; + } + msnprintf((char *)stream->recv_buf + stream->recv_header_len, + stream->recv_buf_alloc - stream->recv_header_len, + "HTTP/3 %.*s \r\n", (int)Header->ValueLength, Header->Value); + } + else { + total_len = 4 + Header->NameLength + Header->ValueLength; + if(!msh3request_ensure_room(stream, total_len)) { + CF_DEBUGF(fprintf(stderr, "* ERROR: unable to buffer: %.*s\n", + (int)Header->NameLength, Header->Name)); + stream->recv_error = CURLE_OUT_OF_MEMORY; + goto release_lock; + } + msnprintf((char *)stream->recv_buf + stream->recv_header_len, + stream->recv_buf_alloc - stream->recv_header_len, + "%.*s: %.*s\r\n", + (int)Header->NameLength, Header->Name, + (int)Header->ValueLength, Header->Value); + } + + stream->recv_header_len += total_len; + data->state.drain = 1; + +release_lock: + msh3_lock_release(&stream->recv_lock); +} + +static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, + void *IfContext, uint32_t *Length, + const uint8_t *Data) +{ + struct Curl_easy *data = IfContext; + struct HTTP *stream = data->req.p.http; + size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len; + + (void)Request; + if(data && data->set.verbose) + CF_DEBUGF(fprintf(stderr, "* [MSH3] req: evt: received %u. %zu buffered, " + "%zu allocated\n", + *Length, cur_recv_len, stream->recv_buf_alloc)); + /* TODO - Update this code to limit data bufferring by `stream->recv_buf_max` + and return `false` when we reach that limit. Then, when curl drains some + of the buffer, making room, call MsH3RequestSetReceiveEnabled to enable + receive callbacks again. */ + msh3_lock_acquire(&stream->recv_lock); + + if(!stream->recv_header_complete) { + if(data && data->set.verbose) + CF_DEBUGF(fprintf(stderr, "* [MSH3] req: Headers complete!\n")); + if(!msh3request_ensure_room(stream, 2)) { + stream->recv_error = CURLE_OUT_OF_MEMORY; + goto release_lock; + } + stream->recv_buf[stream->recv_header_len++] = '\r'; + stream->recv_buf[stream->recv_header_len++] = '\n'; + stream->recv_header_complete = true; + cur_recv_len += 2; + } + if(!msh3request_ensure_room(stream, *Length)) { + stream->recv_error = CURLE_OUT_OF_MEMORY; + goto release_lock; + } + memcpy(stream->recv_buf + cur_recv_len, Data, *Length); + stream->recv_data_len += (size_t)*Length; + data->state.drain = 1; + +release_lock: + msh3_lock_release(&stream->recv_lock); + return true; +} + +static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, + bool Aborted, uint64_t AbortError) +{ + struct Curl_easy *data = IfContext; + struct HTTP *stream = data->req.p.http; + + (void)Request; + (void)AbortError; + if(data && data->set.verbose) + CF_DEBUGF(fprintf(stderr, "* [MSH3] req: evt: complete, aborted=%s\n", + Aborted ? "true" : "false")); + msh3_lock_acquire(&stream->recv_lock); + if(Aborted) { + stream->recv_error = CURLE_HTTP3; /* TODO - how do we pass AbortError? */ + } + stream->recv_header_complete = true; + stream->recv_data_complete = true; + msh3_lock_release(&stream->recv_lock); +} + +static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request, + void *IfContext) +{ + struct Curl_easy *data = IfContext; + struct HTTP *stream = data->req.p.http; + (void)Request; + (void)stream; +} + +static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request, + void *IfContext, void *SendContext) +{ + struct Curl_easy *data = IfContext; + struct HTTP *stream = data->req.p.http; + (void)Request; + (void)stream; + (void)SendContext; +} + +static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct HTTP *stream = data->req.p.http; + size_t outsize = 0; + + (void)cf; + DEBUGF(LOG_CF(data, cf, "req: recv with %zu byte buffer", len)); + + if(stream->recv_error) { + failf(data, "request aborted"); + data->state.drain = 0; + *err = stream->recv_error; + return -1; + } + + *err = CURLE_OK; + msh3_lock_acquire(&stream->recv_lock); + + if(stream->recv_header_len) { + outsize = len; + if(stream->recv_header_len < outsize) { + outsize = stream->recv_header_len; + } + memcpy(buf, stream->recv_buf, outsize); + if(outsize < stream->recv_header_len + stream->recv_data_len) { + memmove(stream->recv_buf, stream->recv_buf + outsize, + stream->recv_header_len + stream->recv_data_len - outsize); + } + stream->recv_header_len -= outsize; + DEBUGF(LOG_CF(data, cf, "req: returned %zu bytes of header", outsize)); + } + else if(stream->recv_data_len) { + outsize = len; + if(stream->recv_data_len < outsize) { + outsize = stream->recv_data_len; + } + memcpy(buf, stream->recv_buf, outsize); + if(outsize < stream->recv_data_len) { + memmove(stream->recv_buf, stream->recv_buf + outsize, + stream->recv_data_len - outsize); + } + stream->recv_data_len -= outsize; + DEBUGF(LOG_CF(data, cf, "req: returned %zu bytes of data", outsize)); + if(stream->recv_data_len == 0 && stream->recv_data_complete) + data->state.drain = 1; + } + else if(stream->recv_data_complete) { + DEBUGF(LOG_CF(data, cf, "req: receive complete")); + data->state.drain = 0; + } + else { + DEBUGF(LOG_CF(data, cf, "req: nothing here, call again")); + *err = CURLE_AGAIN; + outsize = -1; + } + + msh3_lock_release(&stream->recv_lock); + + return (ssize_t)outsize; +} + +static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + struct h2h3req *hreq; + size_t hdrlen = 0; + size_t sentlen = 0; + + /* Sizes must match for cast below to work" */ + DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo)); + DEBUGF(LOG_CF(data, cf, "req: send %zu bytes", len)); + + if(!stream->req) { + /* The first send on the request contains the headers and possibly some + data. Parse out the headers and create the request, then if there is + any data left over go ahead and send it too. */ + + *err = msh3_data_setup(cf, data); + if(*err) { + failf(data, "could not setup data"); + return -1; + } + + *err = Curl_pseudo_headers(data, buf, len, &hdrlen, &hreq); + if(*err) { + failf(data, "Curl_pseudo_headers failed"); + return -1; + } + + DEBUGF(LOG_CF(data, cf, "req: send %zu headers", hreq->entries)); + stream->req = MsH3RequestOpen(ctx->qconn, &msh3_request_if, data, + (MSH3_HEADER*)hreq->header, hreq->entries, + hdrlen == len ? MSH3_REQUEST_FLAG_FIN : + MSH3_REQUEST_FLAG_NONE); + Curl_pseudo_free(hreq); + if(!stream->req) { + failf(data, "request open failed"); + *err = CURLE_SEND_ERROR; + return -1; + } + *err = CURLE_OK; + return len; + } + + DEBUGF(LOG_CF(data, cf, "req: send %zd body bytes", len)); + if(len > 0xFFFFFFFF) { + /* msh3 doesn't support size_t sends currently. */ + *err = CURLE_SEND_ERROR; + return -1; + } + + /* TODO - Need an explicit signal to know when to FIN. */ + if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN, buf, (uint32_t)len, + stream)) { + *err = CURLE_SEND_ERROR; + return -1; + } + + /* TODO - msh3/msquic will hold onto this memory until the send complete + event. How do we make sure curl doesn't free it until then? */ + sentlen += len; + *err = CURLE_OK; + return sentlen; +} + +static int cf_msh3_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *socks) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + int bitmap = GETSOCK_BLANK; + + if(stream && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) { + socks[0] = ctx->sock[SP_LOCAL]; + + if(stream->recv_error) { + bitmap |= GETSOCK_READSOCK(0); + data->state.drain = 1; + } + else if(stream->recv_header_len || stream->recv_data_len) { + bitmap |= GETSOCK_READSOCK(0); + data->state.drain = 1; + } + } + DEBUGF(LOG_CF(data, cf, "select_sock %u -> %d", + (uint32_t)data->state.drain, bitmap)); + + return bitmap; +} + +static bool cf_msh3_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct HTTP *stream = data->req.p.http; + + (void)cf; + DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data pending = %hhu", + (bool)(stream->recv_header_len || stream->recv_data_len))); + return stream->recv_header_len || stream->recv_data_len; +} + +static void cf_msh3_active(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + + /* use this socket from now on */ + cf->conn->sock[cf->sockindex] = ctx->sock[SP_LOCAL]; + /* the first socket info gets set at conn and data */ + if(cf->sockindex == FIRSTSOCKET) { + cf->conn->remote_addr = &ctx->addr; + #ifdef ENABLE_IPV6 + cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE; + #endif + Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port); + } + ctx->active = TRUE; +} + +static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + CURLcode result = CURLE_OK; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_SETUP: + result = msh3_data_setup(cf, data); + break; + case CF_CTRL_DATA_DONE: + DEBUGF(LOG_CF(data, cf, "req: done")); + if(stream) { + if(stream->recv_buf) { + Curl_safefree(stream->recv_buf); + msh3_lock_uninitialize(&stream->recv_lock); + } + if(stream->req) { + MsH3RequestClose(stream->req); + stream->req = ZERO_NULL; + } + } + break; + case CF_CTRL_DATA_DONE_SEND: + DEBUGF(LOG_CF(data, cf, "req: send done")); + stream->upload_done = TRUE; + break; + case CF_CTRL_CONN_INFO_UPDATE: + DEBUGF(LOG_CF(data, cf, "req: update info")); + cf_msh3_active(cf, data); + break; + case CF_CTRL_CONN_REPORT_STATS: + if(cf->sockindex == FIRSTSOCKET) + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + break; + + default: + break; + } + return result; +} + +static CURLcode cf_connect_start(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + bool verify = !!cf->conn->ssl_config.verifypeer; + MSH3_ADDR addr = {0}; + memcpy(&addr, &ctx->addr.sa_addr, ctx->addr.addrlen); + MSH3_SET_PORT(&addr, (uint16_t)cf->conn->remote_port); + ctx->verbose = (data && data->set.verbose); + + if(verify && (cf->conn->ssl_config.CAfile || cf->conn->ssl_config.CApath)) { + /* TODO: need a way to provide trust anchors to MSH3 */ +#ifdef DEBUGBUILD + /* we need this for our test cases to run */ + DEBUGF(LOG_CF(data, cf, "non-standard CA not supported, " + "switching off verifypeer in DEBUG mode")); + verify = 0; +#else + DEBUGF(LOG_CF(data, cf, "non-standard CA not supported, " + "attempting with built-in verification")); +#endif + } + + DEBUGF(LOG_CF(data, cf, "connecting to %s:%d (verify=%d)", + cf->conn->host.name, (int)cf->conn->remote_port, verify)); + + ctx->api = MsH3ApiOpen(); + if(!ctx->api) { + failf(data, "can't create msh3 api"); + return CURLE_FAILED_INIT; + } + + ctx->qconn = MsH3ConnectionOpen(ctx->api, + &msh3_conn_if, + ctx, + cf->conn->host.name, + &addr, + !verify); + if(!ctx->qconn) { + failf(data, "can't create msh3 connection"); + if(ctx->api) { + MsH3ApiClose(ctx->api); + ctx->api = NULL; + } + return CURLE_FAILED_INIT; + } + + return CURLE_OK; +} + +static CURLcode cf_msh3_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + (void)blocking; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + if(ctx->sock[SP_LOCAL] == CURL_SOCKET_BAD) { + if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0]) < 0) { + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + return CURLE_COULDNT_CONNECT; + } + } + + *done = FALSE; + if(!ctx->qconn) { + ctx->connect_started = Curl_now(); + result = cf_connect_start(cf, data); + if(result) + goto out; + } + + if(ctx->handshake_complete) { + ctx->handshake_at = Curl_now(); + if(ctx->handshake_succeeded) { + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + cf->connected = TRUE; + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + connkeep(cf->conn, "HTTP/3 default"); + Curl_pgrsTime(data, TIMER_APPCONNECT); + } + else { + failf(data, "failed to connect, handshake failed"); + result = CURLE_COULDNT_CONNECT; + } + } + +out: + return result; +} + +static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + + (void)data; + if(ctx) { + DEBUGF(LOG_CF(data, cf, "destroying")); + if(ctx->qconn) + MsH3ConnectionClose(ctx->qconn); + if(ctx->api) + MsH3ApiClose(ctx->api); + + if(ctx->active) { + /* We share our socket at cf->conn->sock[cf->sockindex] when active. + * If it is no longer there, someone has stolen (and hopefully + * closed it) and we just forget about it. + */ + if(ctx->sock[SP_LOCAL] == cf->conn->sock[cf->sockindex]) { + DEBUGF(LOG_CF(data, cf, "cf_msh3_close(%d) active", + (int)ctx->sock[SP_LOCAL])); + cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD; + } + else { + DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d) no longer at " + "conn->sock[], discarding", (int)ctx->sock[SP_LOCAL])); + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + } + if(cf->sockindex == FIRSTSOCKET) + cf->conn->remote_addr = NULL; + } + if(ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) { + sclose(ctx->sock[SP_LOCAL]); + } + if(ctx->sock[SP_REMOTE] != CURL_SOCKET_BAD) { + sclose(ctx->sock[SP_REMOTE]); + } + memset(ctx, 0, sizeof(*ctx)); + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + } +} + +static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + cf_msh3_close(cf, data); + free(cf->ctx); + cf->ctx = NULL; +} + +static CURLcode cf_msh3_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { + /* TODO: we do not have access to this so far, fake it */ + (void)ctx; + *pres1 = 100; + return CURLE_OK; + } + default: + break; + } + return cf->next? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + + (void)data; + return ctx && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD && ctx->qconn && + ctx->connected; +} + +struct Curl_cftype Curl_cft_http3 = { + "HTTP/3", + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + 0, + cf_msh3_destroy, + cf_msh3_connect, + cf_msh3_close, + Curl_cf_def_get_host, + cf_msh3_get_select_socks, + cf_msh3_data_pending, + cf_msh3_send, + cf_msh3_recv, + cf_msh3_data_event, + cf_msh3_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_msh3_query, +}; + +CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_msh3_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + (void)ai; /* TODO: msh3 resolves itself? */ + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC); + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + +out: + *pcf = (!result)? cf : NULL; + if(result) { + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +bool Curl_conn_is_msh3(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + + (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; +} + +#endif /* USE_MSH3 */ diff --git a/Utilities/cmcurl/lib/vquic/msh3.h b/Utilities/cmcurl/lib/vquic/curl_msh3.h index ce884d92dd..33931f59bb 100644 --- a/Utilities/cmcurl/lib/vquic/msh3.h +++ b/Utilities/cmcurl/lib/vquic/curl_msh3.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_VQUIC_MSH3_H -#define HEADER_CURL_VQUIC_MSH3_H +#ifndef HEADER_CURL_VQUIC_CURL_MSH3_H +#define HEADER_CURL_VQUIC_CURL_MSH3_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -30,11 +30,17 @@ #include <msh3.h> -struct quicsocket { - MSH3_API* api; - MSH3_CONNECTION* conn; -}; +void Curl_msh3_ver(char *p, size_t len); + +CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_msh3(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); #endif /* USE_MSQUIC */ -#endif /* HEADER_CURL_VQUIC_MSH3_H */ +#endif /* HEADER_CURL_VQUIC_CURL_MSH3_H */ diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c new file mode 100644 index 0000000000..ffdaeade35 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c @@ -0,0 +1,2515 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#ifdef USE_NGTCP2 +#include <ngtcp2/ngtcp2.h> +#include <nghttp3/nghttp3.h> + +#ifdef USE_OPENSSL +#include <openssl/err.h> +#ifdef OPENSSL_IS_BORINGSSL +#include <ngtcp2/ngtcp2_crypto_boringssl.h> +#else +#include <ngtcp2/ngtcp2_crypto_openssl.h> +#endif +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include <ngtcp2/ngtcp2_crypto_gnutls.h> +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include <ngtcp2/ngtcp2_crypto_wolfssl.h> +#include "vtls/wolfssl.h" +#endif + +#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 "select.h" +#include "vquic.h" +#include "vquic_int.h" +#include "h2h3.h" +#include "vtls/keylog.h" +#include "vtls/vtls.h" +#include "curl_ngtcp2.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#define H3_ALPN_H3_29 "\x5h3-29" +#define H3_ALPN_H3 "\x2h3" + +/* + * This holds outgoing HTTP/3 stream data that is used by nghttp3 until acked. + * It is used as a circular buffer. Add new bytes at the end until it reaches + * the far end, then start over at index 0 again. + */ + +#define H3_SEND_SIZE (256*1024) +struct h3out { + uint8_t buf[H3_SEND_SIZE]; + size_t used; /* number of bytes used in the buffer */ + size_t windex; /* index in the buffer where to start writing the next + data block */ +}; + +#define QUIC_MAX_STREAMS (256*1024) +#define QUIC_MAX_DATA (1*1024*1024) +#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS) +#define QUIC_HANDSHAKE_TIMEOUT (10*NGTCP2_SECONDS) + +#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. + */ +void Curl_ngtcp2_ver(char *p, size_t len) +{ + const ngtcp2_info *ng2 = ngtcp2_version(0); + const nghttp3_info *ht3 = nghttp3_version(0); + (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s", + ng2->version_str, ht3->version_str); +} + +struct cf_ngtcp2_ctx { + struct cf_quic_ctx q; + ngtcp2_path connected_path; + ngtcp2_conn *qconn; + ngtcp2_cid dcid; + ngtcp2_cid scid; + uint32_t version; + ngtcp2_settings settings; + ngtcp2_transport_params transport_params; + ngtcp2_connection_close_error 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; + int qlogfd; + 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 */ + BIT(got_first_byte); /* if first byte was received */ +}; + +/* How to access `call_data` from a cf_ngtcp2 filter */ +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data + + +/* ngtcp2 default congestion controller does not perform pacing. Limit + the maximum packet burst to MAX_PKT_BURST packets. */ +#define MAX_PKT_BURST 10 + +static CURLcode cf_process_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data); +static CURLcode cf_flush_egress(struct Curl_cfilter *cf, + struct Curl_easy *data); +static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data); + +static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) +{ + struct Curl_cfilter *cf = conn_ref->user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + return ctx->qconn; +} + +static ngtcp2_tstamp timestamp(void) +{ + struct curltime ct = Curl_now(); + return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS; +} + +#ifdef DEBUG_NGTCP2 +static void quic_printf(void *user_data, const char *fmt, ...) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + (void)ctx; /* TODO: need an easy handle to infof() message */ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} +#endif + +static void qlog_callback(void *user_data, uint32_t flags, + const void *data, size_t datalen) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + (void)flags; + if(ctx->qlogfd != -1) { + ssize_t rc = write(ctx->qlogfd, data, datalen); + if(rc == -1) { + /* on write error, stop further write attempts */ + close(ctx->qlogfd); + ctx->qlogfd = -1; + } + } + +} + +static void quic_settings(struct cf_ngtcp2_ctx *ctx, + struct Curl_easy *data) +{ + ngtcp2_settings *s = &ctx->settings; + ngtcp2_transport_params *t = &ctx->transport_params; + size_t stream_win_size = CURL_MAX_READ_SIZE; + + ngtcp2_settings_default(s); + ngtcp2_transport_params_default(t); +#ifdef DEBUG_NGTCP2 + s->log_printf = quic_printf; +#else + s->log_printf = NULL; +#endif + + (void)data; + s->initial_ts = timestamp(); + s->handshake_timeout = QUIC_HANDSHAKE_TIMEOUT; + s->max_window = 100 * stream_win_size; + s->max_stream_window = stream_win_size; + + t->initial_max_data = 10 * stream_win_size; + t->initial_max_stream_data_bidi_local = stream_win_size; + t->initial_max_stream_data_bidi_remote = stream_win_size; + t->initial_max_stream_data_uni = stream_win_size; + t->initial_max_streams_bidi = QUIC_MAX_STREAMS; + t->initial_max_streams_uni = QUIC_MAX_STREAMS; + t->max_idle_timeout = QUIC_IDLE_TIMEOUT; + if(ctx->qlogfd != -1) { + s->qlog.write = qlog_callback; + } +} + +#ifdef USE_OPENSSL +static void keylog_callback(const SSL *ssl, const char *line) +{ + (void)ssl; + Curl_tls_keylog_write_line(line); +} +#elif defined(USE_GNUTLS) +static int keylog_callback(gnutls_session_t session, const char *label, + const gnutls_datum_t *secret) +{ + gnutls_datum_t crandom; + gnutls_datum_t srandom; + + gnutls_session_get_random(session, &crandom, &srandom); + if(crandom.size != 32) { + return -1; + } + + 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 connectdata *conn = cf->conn; + CURLcode result = CURLE_FAILED_INIT; + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); + + if(!ssl_ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + +#ifdef OPENSSL_IS_BORINGSSL + if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); + goto out; + } +#else + if(ngtcp2_crypto_openssl_configure_client_context(ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_openssl_configure_client_context failed"); + goto out; + } +#endif + + SSL_CTX_set_default_verify_paths(ssl_ctx); + +#ifdef OPENSSL_IS_BORINGSSL + if(SSL_CTX_set1_curves_list(ssl_ctx, QUIC_GROUPS) != 1) { + failf(data, "SSL_CTX_set1_curves_list failed"); + goto out; + } +#else + if(SSL_CTX_set_ciphersuites(ssl_ctx, QUIC_CIPHERS) != 1) { + char error_buffer[256]; + ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); + failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer); + goto out; + } + + if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) { + failf(data, "SSL_CTX_set1_groups_list failed"); + goto out; + } +#endif + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); + } + + result = Curl_ssl_setup_x509_store(cf, data, ssl_ctx); + if(result) + goto out; + + /* OpenSSL always tries to verify the peer, this only says whether it should + * fail to connect if the verification fails, or if it should continue + * anyway. In the latter case the result of the verification is checked with + * SSL_get_verify_result() below. */ + SSL_CTX_set_verify(ssl_ctx, conn->ssl_config.verifypeer ? + SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + 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_get_config(data, FIRSTSOCKET); + DEBUGASSERT(ssl_config); + + if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob + || ssl_config->cert_type) { + return Curl_ossl_set_client_cert( + data, ssl_ctx, ssl_config->primary.clientcert, + ssl_config->primary.cert_blob, ssl_config->cert_type, + ssl_config->key, ssl_config->key_blob, + ssl_config->key_type, ssl_config->key_passwd); + } + + return CURLE_OK; +} + +/** SSL callbacks ***/ + +static CURLcode quic_init_ssl(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + const uint8_t *alpn = NULL; + size_t alpnlen = 0; + + (void)data; + 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 */ + SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name); + 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; + CURLcode result; + gnutls_datum_t alpn[2]; + /* this will need some attention when HTTPS proxy over QUIC get fixed */ + const char * const hostname = cf->conn->host.name; + long * const pverifyresult = &data->set.ssl.certverifyresult; + int rc; + + DEBUGASSERT(ctx->gtls == NULL); + ctx->gtls = calloc(1, sizeof(*(ctx->gtls))); + if(!ctx->gtls) + return CURLE_OUT_OF_MEMORY; + + result = gtls_client_init(data, &cf->conn->ssl_config, &data->set.ssl, + hostname, ctx->gtls, pverifyresult); + if(result) + return result; + + gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref); + + if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) { + DEBUGF(LOG_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) { + DEBUGF(LOG_CF(data, cf, "gnutls_priority_set_direct failed: %s\n", + gnutls_strerror(rc))); + return CURLE_QUIC_CONNECT_ERROR; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback); + } + + /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */ + alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1; + alpn[0].size = sizeof(H3_ALPN_H3_29) - 2; + alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1; + alpn[1].size = sizeof(H3_ALPN_H3) - 2; + + gnutls_alpn_set_protocols(ctx->gtls->session, + alpn, 2, GNUTLS_ALPN_MANDATORY); + return CURLE_OK; +} +#elif defined(USE_WOLFSSL) + +static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx, + struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct connectdata *conn = cf->conn; + CURLcode result = CURLE_FAILED_INIT; + WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + + if(!ssl_ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); + goto out; + } + + wolfSSL_CTX_set_default_verify_paths(ssl_ctx); + + if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) { + char error_buffer[256]; + ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); + failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer); + goto out; + } + + if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) { + failf(data, "SSL_CTX_set1_groups_list failed"); + goto out; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { +#if defined(HAVE_SECRET_CALLBACK) + wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); +#else + failf(data, "wolfSSL was built without keylog callback"); + goto out; +#endif + } + + if(conn->ssl_config.verifypeer) { + const char * const ssl_cafile = conn->ssl_config.CAfile; + const char * const ssl_capath = conn->ssl_config.CApath; + + wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + if(conn->ssl_config.CAfile || conn->ssl_config.CApath) { + /* tell wolfSSL where to find CA certificates that are used to verify + the server's certificate. */ + if(!wolfSSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + goto out; + } + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } +#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; + (void)tconn; + return 0; +} + +static void report_consumed_data(struct Curl_cfilter *cf, + struct Curl_easy *data, + size_t consumed) +{ + struct HTTP *stream = data->req.p.http; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + /* the HTTP/1.1 response headers are written to the buffer, but + * consuming those does not count against flow control. */ + if(stream->recv_buf_nonflow) { + if(consumed >= stream->recv_buf_nonflow) { + consumed -= stream->recv_buf_nonflow; + stream->recv_buf_nonflow = 0; + } + else { + stream->recv_buf_nonflow -= consumed; + consumed = 0; + } + } + if(consumed > 0) { + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] consumed %zu DATA bytes", + stream->stream3_id, consumed)); + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->stream3_id, + consumed); + ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + } + if(!stream->closed && data->state.drain + && !stream->memlen + && !Curl_dyn_len(&stream->overflow)) { + /* nothing buffered any more */ + data->state.drain = 0; + } +} + +static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, + int64_t stream_id, uint64_t offset, + const uint8_t *buf, size_t buflen, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + nghttp3_ssize nconsumed; + int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; + struct Curl_easy *data = stream_user_data; + (void)offset; + (void)data; + + nconsumed = + nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read_stream(len=%zu) -> %zd", + stream_id, buflen, nconsumed)); + if(nconsumed < 0) { + ngtcp2_connection_close_error_set_application_error( + &ctx->last_error, + nghttp3_err_infer_quic_app_error_code((int)nconsumed), NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + /* number of bytes inside buflen which consists of framing overhead + * including QPACK HEADERS. In other words, it does not consume payload of + * DATA frame. */ + ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(tconn, nconsumed); + + return 0; +} + +static int +cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, + uint64_t offset, uint64_t datalen, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)stream_id; + (void)tconn; + (void)offset; + (void)datalen; + (void)stream_user_data; + + rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen); + if(rv) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, + int64_t stream3_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 cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + + (void)tconn; + (void)data; + /* stream is closed... */ + + if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + + rv = nghttp3_conn_close_stream(ctx->h3conn, stream3_id, + app_error_code); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] quic close(err=%" + PRIu64 ") -> %d", stream3_id, app_error_code, rv)); + if(rv) { + ngtcp2_connection_close_error_set_application_error( + &ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, + uint64_t final_size, uint64_t app_error_code, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + int rv; + (void)tconn; + (void)final_size; + (void)app_error_code; + (void)data; + + rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] reset -> %d", stream_id, rv)); + if(rv) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)tconn; + (void)app_error_code; + (void)stream_user_data; + + rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); + if(rv) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn, + uint64_t max_streams, + void *user_data) +{ + (void)tconn; + (void)max_streams; + (void)user_data; + + return 0; +} + +static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)tconn; + (void)max_data; + (void)stream_user_data; + + rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id); + if(rv) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static void cb_rand(uint8_t *dest, size_t destlen, + const ngtcp2_rand_ctx *rand_ctx) +{ + CURLcode result; + (void)rand_ctx; + + result = Curl_rand(NULL, dest, destlen); + if(result) { + /* cb_rand is only used for non-cryptographic context. If Curl_rand + failed, just fill 0 and call it *random*. */ + memset(dest, 0, destlen); + } +} + +static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data) +{ + CURLcode result; + (void)tconn; + (void)user_data; + + result = Curl_rand(NULL, cid->data, cidlen); + if(result) + return NGTCP2_ERR_CALLBACK_FAILURE; + cid->datalen = cidlen; + + result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN); + if(result) + return NGTCP2_ERR_CALLBACK_FAILURE; + + return 0; +} + +static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_crypto_level level, + void *user_data) +{ + struct Curl_cfilter *cf = user_data; + (void)tconn; + + if(level != NGTCP2_CRYPTO_LEVEL_APPLICATION) { + return 0; + } + + if(init_ngh3_conn(cf) != CURLE_OK) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static ngtcp2_callbacks ng_callbacks = { + ngtcp2_crypto_client_initial_cb, + NULL, /* recv_client_initial */ + ngtcp2_crypto_recv_crypto_data_cb, + cb_handshake_completed, + NULL, /* recv_version_negotiation */ + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + cb_recv_stream_data, + cb_acked_stream_data_offset, + NULL, /* stream_open */ + cb_stream_close, + NULL, /* recv_stateless_reset */ + ngtcp2_crypto_recv_retry_cb, + cb_extend_max_local_streams_bidi, + NULL, /* extend_max_local_streams_uni */ + cb_rand, + cb_get_new_connection_id, + NULL, /* remove_connection_id */ + ngtcp2_crypto_update_key_cb, /* update_key */ + NULL, /* path_validation */ + NULL, /* select_preferred_addr */ + cb_stream_reset, + NULL, /* extend_max_remote_streams_bidi */ + NULL, /* extend_max_remote_streams_uni */ + cb_extend_max_stream_data, + NULL, /* dcid_status */ + NULL, /* handshake_confirmed */ + NULL, /* recv_new_token */ + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + NULL, /* recv_datagram */ + NULL, /* ack_datagram */ + NULL, /* lost_datagram */ + ngtcp2_crypto_get_path_challenge_data_cb, + cb_stream_stop_sending, + NULL, /* version_negotiation */ + cb_recv_rx_key, + NULL, /* recv_tx_key */ + NULL, /* early_data_rejected */ +}; + +static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *socks) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct SingleRequest *k = &data->req; + int rv = GETSOCK_BLANK; + struct HTTP *stream = data->req.p.http; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + socks[0] = ctx->q.sockfd; + + /* in an HTTP/3 connection we can basically always get a frame so we should + always be ready for one */ + rv |= GETSOCK_READSOCK(0); + + /* we're still uploading or the HTTP/2 layer wants to send data */ + if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND && + (!stream->h3out || stream->h3out->used < H3_SEND_SIZE) && + ngtcp2_conn_get_cwnd_left(ctx->qconn) && + ngtcp2_conn_get_max_data_left(ctx->qconn) && + nghttp3_conn_is_stream_writable(ctx->h3conn, stream->stream3_id)) + rv |= GETSOCK_WRITESOCK(0); + + DEBUGF(LOG_CF(data, cf, "get_select_socks -> %x (sock=%d)", + rv, (int)socks[0])); + CF_DATA_RESTORE(cf, save); + return rv; +} + +static void notify_drain(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)cf; + if(!data->state.drain) { + data->state.drain = 1; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + + +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 HTTP *stream = data->req.p.http; + (void)conn; + (void)stream_id; + (void)app_error_code; + (void)cf; + + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] h3 close(err=%" PRIx64 ")", + stream_id, app_error_code)); + stream->closed = TRUE; + stream->error3 = app_error_code; + if(app_error_code == NGHTTP3_H3_INTERNAL_ERROR) { + /* TODO: we do not get a specific error when the remote end closed + * the response before it was complete. */ + stream->reset = TRUE; + } + notify_drain(cf, data); + return 0; +} + +/* + * write_resp_raw() copies resonse 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 HTTP *stream = data->req.p.http; + CURLcode result = CURLE_OK; + const char *buf = mem; + size_t ncopy = memlen; + /* copy as much as possible to the receive buffer */ + if(stream->len) { + size_t len = CURLMIN(ncopy, stream->len); + memcpy(stream->mem + stream->memlen, buf, len); + stream->len -= len; + stream->memlen += len; + buf += len; + ncopy -= len; + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] resp_raw: added %zu bytes" + " to data buffer", stream->stream3_id, len)); + } + /* copy the rest to the overflow buffer */ + if(ncopy) { + result = Curl_dyn_addn(&stream->overflow, buf, ncopy); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] resp_raw: added %zu bytes" + " to overflow buffer -> %d", + stream->stream3_id, ncopy, result)); + notify_drain(cf, data); + } + + if(!flow) + stream->recv_buf_nonflow += memlen; + if(CF_DATA_CURRENT(cf) != data) { + notify_drain(cf, data); + } + 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; + CURLcode result; + + (void)conn; + (void)stream3_id; + + result = write_resp_raw(cf, data, buf, buflen, TRUE); + return result? -1 : 0; +} + +static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, + size_t consumed, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + (void)conn; + (void)stream_user_data; + + /* nghttp3 has consumed bytes on the QUIC stream and we need to + * tell the QUIC connection to increase its flow control */ + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream3_id, consumed); + ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + return 0; +} + +/* Decode HTTP status code. Returns -1 if no valid status code was + decoded. (duplicate from http2.c) */ +static int decode_status_code(const uint8_t *value, size_t len) +{ + int i; + int res; + + if(len != 3) { + return -1; + } + + res = 0; + + for(i = 0; i < 3; ++i) { + char c = value[i]; + + if(c < '0' || c > '9') { + return -1; + } + + res *= 10; + res += c - '0'; + } + + return res; +} + +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 HTTP *stream = data->req.p.http; + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)fin; + (void)cf; + + /* add a CRLF only if we've received some headers */ + if(stream->firstheader) { + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + } + + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] end_headers(status_code=%d", + stream_id, stream->status_code)); + if(stream->status_code / 100 != 1) { + stream->bodystarted = TRUE; + } + 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 HTTP *stream = data->req.p.http; + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)token; + (void)flags; + (void)cf; + + if(token == NGHTTP3_QPACK_TOKEN__STATUS) { + char line[14]; /* status line is always 13 characters long */ + size_t ncopy; + + DEBUGASSERT(!stream->firstheader); + stream->status_code = decode_status_code(h3val.base, h3val.len); + DEBUGASSERT(stream->status_code != -1); + ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", + stream->status_code); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] status: %s", + stream_id, line)); + result = write_resp_raw(cf, data, line, ncopy, FALSE); + if(result) { + return -1; + } + stream->firstheader = TRUE; + } + else { + /* store as an HTTP1-style header */ + DEBUGASSERT(stream->firstheader); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" 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_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 cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + (void)conn; + (void)stream_user_data; + + rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, stream_id, app_error_code); + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + 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 cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + int rv; + (void)conn; + (void)data; + + rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, stream_id, + app_error_code); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] reset -> %d", stream_id, rv)); + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static nghttp3_callbacks ngh3_callbacks = { + cb_h3_acked_stream_data, /* 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 */ +}; + +static int init_ngh3_conn(struct Curl_cfilter *cf) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result; + int rc; + int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; + + if(ngtcp2_conn_get_max_local_streams_uni(ctx->qconn) < 3) { + return CURLE_QUIC_CONNECT_ERROR; + } + + nghttp3_settings_default(&ctx->h3settings); + + rc = nghttp3_conn_client_new(&ctx->h3conn, + &ngh3_callbacks, + &ctx->h3settings, + nghttp3_mem_default(), + cf); + if(rc) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id, + qpack_dec_stream_id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto fail; + } + + return CURLE_OK; + fail: + + return result; +} + +static void drain_overflow_buffer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct HTTP *stream = data->req.p.http; + size_t overlen = Curl_dyn_len(&stream->overflow); + size_t ncopy = CURLMIN(overlen, stream->len); + + (void)cf; + if(ncopy > 0) { + memcpy(stream->mem + stream->memlen, + Curl_dyn_ptr(&stream->overflow), ncopy); + stream->len -= ncopy; + stream->memlen += ncopy; + if(ncopy != overlen) + /* make the buffer only keep the tail */ + (void)Curl_dyn_tail(&stream->overflow, overlen - ncopy); + else { + Curl_dyn_reset(&stream->overflow); + } + } +} + +static ssize_t recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + CURLcode *err) +{ + struct HTTP *stream = data->req.p.http; + ssize_t nread = -1; + + (void)cf; + + if(stream->reset) { + failf(data, + "HTTP/3 stream %" PRId64 " reset by server", stream->stream3_id); + *err = CURLE_PARTIAL_FILE; + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, was reset -> %d", + stream->stream3_id, *err)); + goto out; + } + else if(stream->error3 != NGHTTP3_H3_NO_ERROR) { + failf(data, + "HTTP/3 stream %" PRId64 " was not closed cleanly: (err 0x%" PRIx64 + ")", + stream->stream3_id, stream->error3); + *err = CURLE_HTTP3; + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed uncleanly" + " -> %d", stream->stream3_id, *err)); + goto out; + } + + if(!stream->bodystarted) { + failf(data, + "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" + " all response header fields, treated as error", + stream->stream3_id); + *err = CURLE_HTTP3; + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed incomplete" + " -> %d", stream->stream3_id, *err)); + goto out; + } + else { + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed ok" + " -> %d", stream->stream3_id, *err)); + } + *err = CURLE_OK; + nread = 0; + +out: + data->state.drain = 0; + return nread; +} + +/* incoming data frames on the h3 stream */ +static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + ssize_t nread = -1; + struct cf_call_data save; + + (void)ctx; + + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->qconn); + DEBUGASSERT(ctx->h3conn); + *err = CURLE_OK; + + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv(len=%zu) start", + stream->stream3_id, len)); + /* TODO: this implementation of response DATA buffering is fragile. + * It makes the following assumptions: + * - the `buf` passed here has the same lifetime as the easy handle + * - data returned in `buf` from this call is immediately used and `buf` + * can be overwritten during any handling of other transfers at + * this connection. + */ + if(!stream->memlen) { + /* `buf` was not known before or is currently not used by stream, + * assign it (again). */ + stream->mem = buf; + stream->len = len; + } + + /* if there's data in the overflow buffer, move as much + as possible to the receive buffer now */ + drain_overflow_buffer(cf, data); + + if(cf_process_ingress(cf, data)) { + *err = CURLE_RECV_ERROR; + nread = -1; + goto out; + } + + if(stream->memlen) { + nread = stream->memlen; + /* reset to allow more data to come */ + /* TODO: very brittle buffer use design: + * - stream->mem has now `nread` bytes of response data + * - we assume that the caller will use those immediately and + * we can overwrite that with new data on our next invocation from + * anywhere. + */ + stream->mem = buf; + stream->memlen = 0; + stream->len = len; + /* extend the stream window with the data we're consuming and send out + any additional packets to tell the server that we can receive more */ + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv -> %zd bytes", + stream->stream3_id, nread)); + report_consumed_data(cf, data, nread); + if(cf_flush_egress(cf, data)) { + *err = CURLE_SEND_ERROR; + nread = -1; + } + goto out; + } + + if(stream->closed) { + nread = recv_closed_stream(cf, data, err); + goto out; + } + + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv -> EAGAIN", + stream->stream3_id)); + *err = CURLE_AGAIN; + nread = -1; +out: + if(cf_flush_egress(cf, data)) { + *err = CURLE_SEND_ERROR; + nread = -1; + goto out; + } + + CF_DATA_RESTORE(cf, save); + return nread; +} + +/* this amount of data has now been acked on this stream */ +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 HTTP *stream = data->req.p.http; + (void)user_data; + + (void)cf; + if(!data->set.postfields) { + stream->h3out->used -= datalen; + DEBUGF(LOG_CF(data, cf, "cb_h3_acked_stream_data, %"PRIu64" bytes, " + "%zd left unacked", datalen, stream->h3out->used)); + DEBUGASSERT(stream->h3out->used < H3_SEND_SIZE); + + if(stream->h3out->used == 0) { + int rv = nghttp3_conn_resume_stream(conn, stream_id); + if(rv) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + } + } + return 0; +} + +static nghttp3_ssize cb_h3_readfunction(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; + size_t nread; + struct HTTP *stream = data->req.p.http; + (void)cf; + (void)conn; + (void)stream_id; + (void)user_data; + (void)veccnt; + + if(data->set.postfields) { + vec[0].base = data->set.postfields; + vec[0].len = data->state.infilesize; + *pflags = NGHTTP3_DATA_FLAG_EOF; + return 1; + } + + if(stream->upload_len && H3_SEND_SIZE <= stream->h3out->used) { + return NGHTTP3_ERR_WOULDBLOCK; + } + + nread = CURLMIN(stream->upload_len, H3_SEND_SIZE - stream->h3out->used); + if(nread > 0) { + /* nghttp3 wants us to hold on to the data until it tells us it is okay to + delete it. Append the data at the end of the h3out buffer. Since we can + only return consecutive data, copy the amount that fits and the next + part comes in next invoke. */ + struct h3out *out = stream->h3out; + if(nread + out->windex > H3_SEND_SIZE) + nread = H3_SEND_SIZE - out->windex; + + memcpy(&out->buf[out->windex], stream->upload_mem, nread); + + /* that's the chunk we return to nghttp3 */ + vec[0].base = &out->buf[out->windex]; + vec[0].len = nread; + + out->windex += nread; + out->used += nread; + + if(out->windex == H3_SEND_SIZE) + out->windex = 0; /* wrap */ + stream->upload_mem += nread; + stream->upload_len -= nread; + if(data->state.infilesize != -1) { + stream->upload_left -= nread; + if(!stream->upload_left) + *pflags = NGHTTP3_DATA_FLAG_EOF; + } + DEBUGF(LOG_CF(data, cf, "cb_h3_readfunction %zd bytes%s (at %zd unacked)", + nread, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"", + out->used)); + } + if(stream->upload_done && !stream->upload_len && + (stream->upload_left <= 0)) { + DEBUGF(LOG_CF(data, cf, "cb_h3_readfunction sets EOF")); + *pflags = NGHTTP3_DATA_FLAG_EOF; + return nread ? 1 : 0; + } + else if(!nread) { + return NGHTTP3_ERR_WOULDBLOCK; + } + return 1; +} + +/* Index where :authority header field will appear in request header + field list. */ +#define AUTHORITY_DST_IDX 3 + +static CURLcode h3_stream_open(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, + size_t len) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + size_t nheader; + CURLcode result = CURLE_OK; + nghttp3_nv *nva = NULL; + int64_t stream3_id; + int rc = 0; + struct h3out *h3out = NULL; + struct h2h3req *hreq = NULL; + + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream3_id, NULL); + if(rc) { + failf(data, "can get bidi streams"); + goto fail; + } + + stream->stream3_id = stream3_id; + stream->h3req = TRUE; + Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE); + stream->recv_buf_nonflow = 0; + + result = Curl_pseudo_headers(data, mem, len, NULL, &hreq); + if(result) + goto fail; + nheader = hreq->entries; + + nva = malloc(sizeof(nghttp3_nv) * nheader); + if(!nva) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + else { + unsigned int i; + for(i = 0; i < nheader; i++) { + nva[i].name = (unsigned char *)hreq->header[i].name; + nva[i].namelen = hreq->header[i].namelen; + nva[i].value = (unsigned char *)hreq->header[i].value; + nva[i].valuelen = hreq->header[i].valuelen; + nva[i].flags = NGHTTP3_NV_FLAG_NONE; + } + } + + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: { + nghttp3_data_reader data_reader; + 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, but not zero */ + + data_reader.read_data = cb_h3_readfunction; + + h3out = calloc(sizeof(struct h3out), 1); + if(!h3out) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + stream->h3out = h3out; + + rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id, + nva, nheader, &data_reader, data); + if(rc) + goto fail; + break; + } + default: + stream->upload_left = 0; /* nothing left to send */ + rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id, + nva, nheader, NULL, data); + if(rc) + goto fail; + break; + } + + Curl_safefree(nva); + + infof(data, "Using HTTP/3 Stream ID: %" PRId64 " (easy handle %p)", + stream3_id, (void *)data); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] opened for %s", + stream3_id, data->state.url)); + + Curl_pseudo_free(hreq); + return CURLE_OK; + +fail: + if(rc) { + switch(rc) { + case NGHTTP3_ERR_CONN_CLOSING: + DEBUGF(LOG_CF(data, cf, "h3sid[%"PRId64"] failed to send, " + "connection is closing", stream->stream3_id)); + result = CURLE_RECV_ERROR; + break; + default: + DEBUGF(LOG_CF(data, cf, "h3sid[%"PRId64"] failed to send -> %d (%s)", + stream->stream3_id, rc, ngtcp2_strerror(rc))); + result = CURLE_SEND_ERROR; + break; + } + } + free(nva); + Curl_pseudo_free(hreq); + return result; +} + +static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + ssize_t sent = 0; + struct HTTP *stream = data->req.p.http; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx->qconn); + DEBUGASSERT(ctx->h3conn); + *err = CURLE_OK; + + if(stream->closed) { + *err = CURLE_HTTP3; + sent = -1; + goto out; + } + + if(!stream->h3req) { + CURLcode result = h3_stream_open(cf, data, buf, len); + if(result) { + DEBUGF(LOG_CF(data, cf, "failed to open stream -> %d", result)); + sent = -1; + goto out; + } + /* Assume that mem of length len only includes HTTP/1.1 style + header fields. In other words, it does not contain request + body. */ + sent = len; + } + else { + DEBUGF(LOG_CF(data, cf, "ngh3_stream_send() wants to send %zd bytes", + len)); + if(!stream->upload_len) { + stream->upload_mem = buf; + stream->upload_len = len; + (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->stream3_id); + } + else { + *err = CURLE_AGAIN; + sent = -1; + goto out; + } + } + + if(cf_flush_egress(cf, data)) { + *err = CURLE_SEND_ERROR; + sent = -1; + goto out; + } + + /* Reset post upload buffer after resumed. */ + if(stream->upload_mem) { + if(data->set.postfields) { + sent = len; + } + else { + sent = len - stream->upload_len; + } + + stream->upload_mem = NULL; + stream->upload_len = 0; + + if(sent == 0) { + *err = CURLE_AGAIN; + sent = -1; + goto out; + } + } +out: + CF_DATA_RESTORE(cf, save); + return sent; +} + +static CURLcode qng_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + const char *hostname, *disp_hostname; + int port; + char *snihost; + + Curl_conn_get_host(data, cf->sockindex, &hostname, &disp_hostname, &port); + snihost = Curl_ssl_snihost(data, hostname, NULL); + if(!snihost) + return CURLE_PEER_FAILED_VERIFICATION; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + + if(cf->conn->ssl_config.verifyhost) { +#ifdef USE_OPENSSL + X509 *server_cert; + server_cert = SSL_get_peer_certificate(ctx->ssl); + if(!server_cert) { + return CURLE_PEER_FAILED_VERIFICATION; + } + result = Curl_ossl_verifyhost(data, cf->conn, server_cert); + X509_free(server_cert); + if(result) + return result; +#elif defined(USE_GNUTLS) + result = Curl_gtls_verifyserver(data, ctx->gtls->session, + &cf->conn->ssl_config, &data->set.ssl, + hostname, disp_hostname, + data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + if(result) + return result; +#elif defined(USE_WOLFSSL) + if(wolfSSL_check_domain_name(ctx->ssl, snihost) == SSL_FAILURE) + return CURLE_PEER_FAILED_VERIFICATION; +#endif + infof(data, "Verified certificate just fine"); + } + else + infof(data, "Skipped certificate verification"); +#ifdef USE_OPENSSL + if(data->set.ssl.certinfo) + /* asked to gather certificate info */ + (void)Curl_ossl_certchain(data, ctx->ssl); +#endif + return result; +} + +static CURLcode cf_process_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + ssize_t recvd; + int rv; + uint8_t buf[65536]; + size_t bufsize = sizeof(buf); + size_t pktcount = 0, total_recvd = 0; + struct sockaddr_storage remote_addr; + socklen_t remote_addrlen; + ngtcp2_path path; + ngtcp2_tstamp ts = timestamp(); + ngtcp2_pkt_info pi = { 0 }; + + for(;;) { + remote_addrlen = sizeof(remote_addr); + while((recvd = recvfrom(ctx->q.sockfd, (char *)buf, bufsize, 0, + (struct sockaddr *)&remote_addr, + &remote_addrlen)) == -1 && + SOCKERRNO == EINTR) + ; + if(recvd == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + DEBUGF(LOG_CF(data, cf, "ingress, recvfrom -> EAGAIN")); + goto out; + } + if(!cf->connected && SOCKERRNO == ECONNREFUSED) { + const char *r_ip; + int r_port; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + failf(data, "ngtcp2: connection to %s port %u refused", + r_ip, r_port); + return CURLE_COULDNT_CONNECT; + } + failf(data, "ngtcp2: recvfrom() unexpectedly returned %zd (errno=%d)", + recvd, SOCKERRNO); + return CURLE_RECV_ERROR; + } + + if(recvd > 0 && !ctx->got_first_byte) { + ctx->first_byte_at = Curl_now(); + ctx->got_first_byte = TRUE; + } + + ++pktcount; + total_recvd += recvd; + + ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr, + remote_addrlen); + + rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, buf, recvd, ts); + if(rv) { + DEBUGF(LOG_CF(data, cf, "ingress, read_pkt -> %s", + ngtcp2_strerror(rv))); + if(!ctx->last_error.error_code) { + if(rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_connection_close_error_set_transport_error_tls_alert( + &ctx->last_error, + ngtcp2_conn_get_tls_alert(ctx->qconn), NULL, 0); + } + else { + ngtcp2_connection_close_error_set_transport_error_liberr( + &ctx->last_error, rv, NULL, 0); + } + } + + if(rv == NGTCP2_ERR_CRYPTO) + /* this is a "TLS problem", but a failed certificate verification + is a common reason for this */ + return CURLE_PEER_FAILED_VERIFICATION; + return CURLE_RECV_ERROR; + } + } + +out: + (void)pktcount; + (void)total_recvd; + DEBUGF(LOG_CF(data, cf, "ingress, recvd %zu packets with %zd bytes", + pktcount, total_recvd)); + return CURLE_OK; +} + +static CURLcode cf_flush_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rv; + size_t sent; + ngtcp2_ssize outlen; + uint8_t *outpos = ctx->q.pktbuf; + size_t max_udp_payload_size = + ngtcp2_conn_get_max_tx_udp_payload_size(ctx->qconn); + size_t path_max_udp_payload_size = + ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn); + size_t max_pktcnt = + CURLMIN(MAX_PKT_BURST, ctx->q.pktbuflen / max_udp_payload_size); + size_t pktcnt = 0; + size_t gsolen = 0; /* this disables gso until we have a clue */ + ngtcp2_path_storage ps; + ngtcp2_tstamp ts = timestamp(); + ngtcp2_tstamp expiry; + ngtcp2_duration timeout; + int64_t stream_id; + nghttp3_ssize veccnt; + int fin; + nghttp3_vec vec[16]; + ngtcp2_ssize ndatalen; + uint32_t flags; + CURLcode curlcode; + + rv = ngtcp2_conn_handle_expiry(ctx->qconn, ts); + if(rv) { + failf(data, "ngtcp2_conn_handle_expiry returned error: %s", + ngtcp2_strerror(rv)); + ngtcp2_connection_close_error_set_transport_error_liberr(&ctx->last_error, + rv, NULL, 0); + return CURLE_SEND_ERROR; + } + + if(ctx->q.num_blocked_pkt) { + curlcode = vquic_send_blocked_pkt(cf, data, &ctx->q); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + } + + ngtcp2_path_storage_zero(&ps); + + for(;;) { + veccnt = 0; + stream_id = -1; + fin = 0; + + if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) { + veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec, + sizeof(vec) / sizeof(vec[0])); + if(veccnt < 0) { + failf(data, "nghttp3_conn_writev_stream returned error: %s", + nghttp3_strerror((int)veccnt)); + ngtcp2_connection_close_error_set_application_error( + &ctx->last_error, + nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0); + return CURLE_SEND_ERROR; + } + } + + flags = NGTCP2_WRITE_STREAM_FLAG_MORE | + (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0); + outlen = ngtcp2_conn_writev_stream(ctx->qconn, &ps.path, NULL, outpos, + max_udp_payload_size, + &ndatalen, flags, stream_id, + (const ngtcp2_vec *)vec, veccnt, ts); + if(outlen == 0) { + /* ngtcp2 does not want to send more packets, if the buffer is + * not empty, send that now */ + if(outpos != ctx->q.pktbuf) { + curlcode = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf, + outpos - ctx->q.pktbuf, gsolen, &sent); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf + sent, + outpos - ctx->q.pktbuf - sent, + gsolen); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + } + /* done for now */ + goto out; + } + if(outlen < 0) { + switch(outlen) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + assert(ndatalen == -1); + nghttp3_conn_block_stream(ctx->h3conn, stream_id); + continue; + case NGTCP2_ERR_STREAM_SHUT_WR: + assert(ndatalen == -1); + nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id); + continue; + case NGTCP2_ERR_WRITE_MORE: + /* ngtcp2 wants to send more. update the flow of the stream whose data + * is in the buffer and continue */ + assert(ndatalen >= 0); + rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen); + if(rv) { + failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", + nghttp3_strerror(rv)); + return CURLE_SEND_ERROR; + } + continue; + default: + assert(ndatalen == -1); + failf(data, "ngtcp2_conn_writev_stream returned error: %s", + ngtcp2_strerror((int)outlen)); + ngtcp2_connection_close_error_set_transport_error_liberr( + &ctx->last_error, (int)outlen, NULL, 0); + return CURLE_SEND_ERROR; + } + } + else if(ndatalen >= 0) { + /* ngtcp2 thinks it has added all it wants. Update the stream */ + rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen); + if(rv) { + failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", + nghttp3_strerror(rv)); + return CURLE_SEND_ERROR; + } + } + + /* advance to the end of the buffered packet data */ + outpos += outlen; + + if(pktcnt == 0) { + /* first packet buffer chunk. use this as gsolen. It's how ngtcp2 + * indicates the intended segment size. */ + gsolen = outlen; + } + else if((size_t)outlen > gsolen || + (gsolen > path_max_udp_payload_size && (size_t)outlen != gsolen)) { + /* Packet larger than path_max_udp_payload_size is PMTUD probe + packet and it might not be sent because of EMSGSIZE. Send + them separately to minimize the loss. */ + /* send the pktbuf *before* the last addition */ + curlcode = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf, + outpos - outlen - ctx->q.pktbuf, gsolen, &sent); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + /* blocked, add the pktbuf *before* and *at* the last addition + * separately to the blocked packages */ + vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf + sent, + outpos - outlen - ctx->q.pktbuf - sent, gsolen); + vquic_push_blocked_pkt(cf, &ctx->q, outpos - outlen, outlen, outlen); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + /* send the pktbuf *at* the last addition */ + curlcode = vquic_send_packet(cf, data, &ctx->q, outpos - outlen, outlen, + outlen, &sent); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + assert(0 == sent); + vquic_push_blocked_pkt(cf, &ctx->q, outpos - outlen, outlen, outlen); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + /* pktbuf has been completely sent */ + pktcnt = 0; + outpos = ctx->q.pktbuf; + continue; + } + + if(++pktcnt >= max_pktcnt || (size_t)outlen < gsolen) { + /* enough packets or last one is shorter than the intended + * segment size, indicating that it is time to send. */ + curlcode = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf, + outpos - ctx->q.pktbuf, gsolen, &sent); + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf + sent, + outpos - ctx->q.pktbuf - sent, gsolen); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + return curlcode; + } + /* pktbuf has been completely sent */ + pktcnt = 0; + outpos = ctx->q.pktbuf; + } + } + +out: + /* non-errored exit. check when we should run again. */ + expiry = ngtcp2_conn_get_expiry(ctx->qconn); + if(expiry != UINT64_MAX) { + if(expiry <= ts) { + timeout = 0; + } + else { + timeout = expiry - ts; + if(timeout % NGTCP2_MILLISECONDS) { + timeout += NGTCP2_MILLISECONDS; + } + } + Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC); + } + + return CURLE_OK; +} + +/* + * Called from transfer.c:data_pending to know if we should keep looping + * to receive more data from the connection. + */ +static bool cf_ngtcp2_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + /* We may have received more data than we're able to hold in the receive + buffer and allocated an overflow buffer. Since it's possible that + there's no more data coming on the socket, we need to keep reading + until the overflow buffer is empty. */ + const struct HTTP *stream = data->req.p.http; + (void)cf; + return Curl_dyn_len(&stream->overflow) > 0; +} + +static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_ngtcp2_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_DONE: { + struct HTTP *stream = data->req.p.http; + Curl_dyn_free(&stream->overflow); + free(stream->h3out); + break; + } + case CF_CTRL_DATA_DONE_SEND: { + struct HTTP *stream = data->req.p.http; + stream->upload_done = TRUE; + (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->stream3_id); + break; + } + case CF_CTRL_DATA_IDLE: + if(timestamp() >= ngtcp2_conn_get_expiry(ctx->qconn)) { + if(cf_flush_egress(cf, data)) { + result = CURLE_SEND_ERROR; + } + } + break; + case CF_CTRL_CONN_REPORT_STATS: + if(cf->sockindex == FIRSTSOCKET) { + if(ctx->got_first_byte) + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->first_byte_at); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + } + break; + default: + break; + } + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + if(ctx->qlogfd != -1) { + close(ctx->qlogfd); + ctx->qlogfd = -1; + } +#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 + vquic_ctx_free(&ctx->q); + if(ctx->h3conn) + nghttp3_conn_del(ctx->h3conn); + if(ctx->qconn) + ngtcp2_conn_del(ctx->qconn); + + memset(ctx, 0, sizeof(*ctx)); + ctx->call_data = save; +} + +static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + if(ctx && ctx->qconn) { + char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; + ngtcp2_tstamp ts; + ngtcp2_ssize rc; + + DEBUGF(LOG_CF(data, cf, "close")); + ts = timestamp(); + rc = ngtcp2_conn_write_connection_close(ctx->qconn, NULL, /* path */ + NULL, /* pkt_info */ + (uint8_t *)buffer, sizeof(buffer), + &ctx->last_error, ts); + if(rc > 0) { + while((send(ctx->q.sockfd, buffer, rc, 0) == -1) && + SOCKERRNO == EINTR); + } + + cf_ngtcp2_ctx_clear(ctx); + } + + cf->connected = FALSE; + CF_DATA_RESTORE(cf, save); +} + +static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + DEBUGF(LOG_CF(data, cf, "destroy")); + if(ctx) { + cf_ngtcp2_ctx_clear(ctx); + free(ctx); + } + cf->ctx = NULL; + /* No CF_DATA_RESTORE(cf, save) possible */ +} + +/* + * Might be called twice for happy eyeballs. + */ +static CURLcode cf_connect_start(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + int rc; + int rv; + CURLcode result; + const struct Curl_sockaddr_ex *sockaddr; + int qfd; + + ctx->version = NGTCP2_PROTO_VER_MAX; +#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); + if(result) + return result; + + ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; + + ctx->scid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN); + if(result) + return result; + + (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd); + ctx->qlogfd = qfd; /* -1 if failure above */ + quic_settings(ctx, data); + + result = vquic_ctx_init(&ctx->q, + NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE * MAX_PKT_BURST); + if(result) + return result; + + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, + &sockaddr, NULL, NULL, NULL, NULL); + ctx->q.local_addrlen = sizeof(ctx->q.local_addr); + rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, + &ctx->q.local_addrlen); + if(rv == -1) + return CURLE_QUIC_CONNECT_ERROR; + + ngtcp2_addr_init(&ctx->connected_path.local, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen); + ngtcp2_addr_init(&ctx->connected_path.remote, + &sockaddr->sa_addr, sockaddr->addrlen); + + rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, + &ctx->connected_path, + NGTCP2_PROTO_VER_V1, &ng_callbacks, + &ctx->settings, &ctx->transport_params, + NULL, cf); + if(rc) + return CURLE_QUIC_CONNECT_ERROR; + +#ifdef USE_GNUTLS + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session); +#else + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl); +#endif + + ngtcp2_connection_close_error_default(&ctx->last_error); + + ctx->conn_ref.get_conn = get_conn; + ctx->conn_ref.user_data = cf; + + return CURLE_OK; +} + +static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + struct curltime now; + + 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 */ + DEBUGF(LOG_CF(data, cf, "waiting for reconnect time")); + goto out; + } + + if(!ctx->qconn) { + ctx->started_at = now; + result = cf_connect_start(cf, data); + if(result) + goto out; + result = cf_flush_egress(cf, data); + /* we do not expect to be able to recv anything yet */ + goto out; + } + + result = cf_process_ingress(cf, data); + if(result) + goto out; + + result = cf_flush_egress(cf, data); + if(result) + goto out; + + if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) { + ctx->handshake_at = now; + DEBUGF(LOG_CF(data, cf, "handshake complete after %dms", + (int)Curl_timediff(now, ctx->started_at))); + result = qng_verify_peer(cf, data); + if(!result) { + DEBUGF(LOG_CF(data, cf, "peer verified")); + cf->connected = TRUE; + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + connkeep(cf->conn, "HTTP/3 default"); + } + } + +out: + if(result == CURLE_RECV_ERROR && ctx->qconn && + ngtcp2_conn_is_in_draining_period(ctx->qconn)) { + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE right away. Our connection then enters the DRAINING + * state. + * This may be a stopping of the service or it may be that the server + * is reloading and a new instance will start serving soon. + * In any case, we tear down our socket and start over with a new one. + * We re-open the underlying UDP cf right now, but do not start + * connecting until called again. + */ + int reconn_delay_ms = 200; + + DEBUGF(LOG_CF(data, cf, "connect, remote closed, reconnect after %dms", + reconn_delay_ms)); + Curl_conn_cf_close(cf->next, data); + cf_ngtcp2_ctx_clear(ctx); + result = Curl_conn_cf_connect(cf->next, data, FALSE, done); + if(!result && *done) { + *done = FALSE; + ctx->reconnect_at = now; + ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000; + Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC); + result = CURLE_OK; + } + } + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(result) { + const char *r_ip; + int r_port; + + 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 + DEBUGF(LOG_CF(data, cf, "connect -> %d, done=%d", result, *done)); + CF_DATA_RESTORE(cf, save); + return result; +} + +static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { + const ngtcp2_transport_params *rp; + DEBUGASSERT(pres1); + + CF_DATA_SAVE(save, cf, data); + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(rp) + *pres1 = (rp->initial_max_streams_bidi > INT_MAX)? + INT_MAX : (int)rp->initial_max_streams_bidi; + else /* not arrived yet? */ + *pres1 = Curl_multi_max_concurrent_streams(data->multi); + DEBUGF(LOG_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; + 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_ngtcp2_destroy, + cf_ngtcp2_connect, + cf_ngtcp2_close, + Curl_cf_def_get_host, + cf_ngtcp2_get_select_socks, + cf_ngtcp2_data_pending, + cf_ngtcp2_send, + cf_ngtcp2_recv, + cf_ngtcp2_data_event, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_ngtcp2_query, +}; + +CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_ngtcp2_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL, *udp_cf = NULL; + CURLcode result; + + (void)data; + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + cf_ngtcp2_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(udp_cf, data); + Curl_safefree(cf); + Curl_safefree(ctx); + } + return result; +} + +bool Curl_conn_is_ngtcp2(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + + (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; +} + +#endif diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h new file mode 100644 index 0000000000..8813ec9a77 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h @@ -0,0 +1,61 @@ +#ifndef HEADER_CURL_VQUIC_CURL_NGTCP2_H +#define HEADER_CURL_VQUIC_CURL_NGTCP2_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" + +#ifdef USE_NGTCP2 + +#ifdef HAVE_NETINET_UDP_H +#include <netinet/udp.h> +#endif + +#include <ngtcp2/ngtcp2_crypto.h> +#include <nghttp3/nghttp3.h> +#ifdef USE_OPENSSL +#include <openssl/ssl.h> +#elif defined(USE_WOLFSSL) +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> +#include <wolfssl/quic.h> +#endif + +struct Curl_cfilter; + +#include "urldata.h" + +void Curl_ngtcp2_ver(char *p, size_t len); + +CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_ngtcp2(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); +#endif + +#endif /* HEADER_CURL_VQUIC_CURL_NGTCP2_H */ diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.c b/Utilities/cmcurl/lib/vquic/curl_quiche.c new file mode 100644 index 0000000000..54408d75a3 --- /dev/null +++ b/Utilities/cmcurl/lib/vquic/curl_quiche.c @@ -0,0 +1,1433 @@ +/*************************************************************************** + * _ _ ____ _ + * 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" + +#ifdef USE_QUICHE +#include <quiche.h> +#include <openssl/err.h> +#include <openssl/ssl.h> +#include "urldata.h" +#include "cfilters.h" +#include "cf-socket.h" +#include "sendf.h" +#include "strdup.h" +#include "rand.h" +#include "strcase.h" +#include "multiif.h" +#include "connect.h" +#include "progress.h" +#include "strerror.h" +#include "vquic.h" +#include "vquic_int.h" +#include "curl_quiche.h" +#include "transfer.h" +#include "h2h3.h" +#include "vtls/openssl.h" +#include "vtls/keylog.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#define QUIC_MAX_STREAMS (256*1024) +#define QUIC_MAX_DATA (1*1024*1024) +#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */ + +/* how many UDP packets to send max in one call */ +#define MAX_PKT_BURST 10 +#define MAX_UDP_PAYLOAD_SIZE 1452 + +/* + * Store quiche version info in this buffer. + */ +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); +} + +static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) +{ + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); + + SSL_CTX_set_alpn_protos(ssl_ctx, + (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL, + sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); + + SSL_CTX_set_default_verify_paths(ssl_ctx); + + /* 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); + } + + { + struct connectdata *conn = data->conn; + if(conn->ssl_config.verifypeer) { + const char * const ssl_cafile = conn->ssl_config.CAfile; + const char * const ssl_capath = conn->ssl_config.CApath; + if(ssl_cafile || ssl_capath) { + SSL_CTX_set_verify(ssl_ctx, 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(ssl_ctx, ssl_cafile, ssl_capath)) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + return NULL; + } + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } +#ifdef CURL_CA_FALLBACK + else { + /* verifying the peer without any CA certificates won't work so + use openssl's built-in default as fallback */ + SSL_CTX_set_default_verify_paths(ssl_ctx); + } +#endif + } + } + return ssl_ctx; +} + +struct quic_handshake { + char *buf; /* pointer to the buffer */ + size_t alloclen; /* size of allocation */ + size_t len; /* size of content in buffer */ + size_t nread; /* how many bytes have been read */ +}; + +struct h3_event_node { + struct h3_event_node *next; + quiche_h3_event *ev; +}; + +struct cf_quiche_ctx { + struct cf_quic_ctx q; + 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 */ + BIT(goaway); /* got GOAWAY from server */ + BIT(got_first_byte); /* if first byte was received */ +}; + + +#ifdef DEBUG_QUICHE +static void quiche_debug_log(const char *line, void *argp) +{ + (void)argp; + fprintf(stderr, "%s\n", line); +} +#endif + +static void h3_clear_pending(struct Curl_easy *data) +{ + struct HTTP *stream = data->req.p.http; + + if(stream->pending) { + struct h3_event_node *node, *next; + for(node = stream->pending; node; node = next) { + next = node->next; + quiche_h3_event_free(node->ev); + free(node); + } + stream->pending = NULL; + } +} + +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->cfg) + quiche_config_free(ctx->cfg); + memset(ctx, 0, sizeof(*ctx)); + } +} + +static void notify_drain(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)cf; + data->state.drain = 1; + Curl_expire(data, 0, EXPIRE_RUN_NOW); +} + +static CURLcode h3_add_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream3_id, quiche_h3_event *ev) +{ + struct Curl_easy *mdata; + struct h3_event_node *node, **pnext; + + DEBUGASSERT(data->multi); + for(mdata = data->multi->easyp; mdata; mdata = mdata->next) { + if(mdata->req.p.http && mdata->req.p.http->stream3_id == stream3_id) { + break; + } + } + + if(!mdata) { + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] event discarded, easy handle " + "not found", stream3_id)); + quiche_h3_event_free(ev); + return CURLE_OK; + } + + node = calloc(sizeof(*node), 1); + if(!node) { + quiche_h3_event_free(ev); + return CURLE_OUT_OF_MEMORY; + } + node->ev = ev; + /* append to process them in order of arrival */ + pnext = &mdata->req.p.http->pending; + while(*pnext) { + pnext = &((*pnext)->next); + } + *pnext = node; + notify_drain(cf, mdata); + return CURLE_OK; +} + +struct h3h1header { + char *dest; + size_t destlen; /* left to use */ + size_t nlen; /* used */ +}; + +static int cb_each_header(uint8_t *name, size_t name_len, + uint8_t *value, size_t value_len, + void *argp) +{ + struct h3h1header *headers = (struct h3h1header *)argp; + size_t olen = 0; + + if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) { + msnprintf(headers->dest, + headers->destlen, "HTTP/3 %.*s \r\n", + (int) value_len, value); + } + else if(!headers->nlen) { + return CURLE_HTTP3; + } + else { + msnprintf(headers->dest, + headers->destlen, "%.*s: %.*s\r\n", + (int)name_len, name, (int) value_len, value); + } + olen = strlen(headers->dest); + headers->destlen -= olen; + headers->nlen += olen; + headers->dest += olen; + return 0; +} + +static ssize_t cf_recv_body(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, + CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + ssize_t nread; + size_t offset = 0; + + if(!stream->firstbody) { + /* add a header-body separator CRLF */ + offset = 2; + } + nread = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->stream3_id, + (unsigned char *)buf + offset, len - offset); + if(nread >= 0) { + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][DATA] len=%zd", + stream->stream3_id, nread)); + if(!stream->firstbody) { + stream->firstbody = TRUE; + buf[0] = '\r'; + buf[1] = '\n'; + nread += offset; + } + } + else if(nread == -1) { + *err = CURLE_AGAIN; + stream->h3_recving_data = FALSE; + } + else { + failf(data, "Error %zd in HTTP/3 response body for stream[%"PRId64"]", + nread, stream->stream3_id); + stream->closed = TRUE; + stream->reset = TRUE; + streamclose(cf->conn, "Reset of stream"); + stream->h3_recving_data = FALSE; + nread = -1; + *err = stream->h3_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + } + return nread; +} + +#ifdef DEBUGBUILD +static const char *cf_ev_name(quiche_h3_event *ev) +{ + switch(quiche_h3_event_type(ev)) { + case QUICHE_H3_EVENT_HEADERS: + return "HEADERS"; + case QUICHE_H3_EVENT_DATA: + return "DATA"; + case QUICHE_H3_EVENT_RESET: + return "RESET"; + case QUICHE_H3_EVENT_FINISHED: + return "FINISHED"; + case QUICHE_H3_EVENT_GOAWAY: + return "GOAWAY"; + default: + return "Unknown"; + } +} +#else +#define cf_ev_name(x) "" +#endif + +static ssize_t h3_process_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, + int64_t stream3_id, + quiche_h3_event *ev, + CURLcode *err) +{ + struct HTTP *stream = data->req.p.http; + ssize_t recvd = 0; + int rc; + struct h3h1header headers; + + DEBUGASSERT(stream3_id == stream->stream3_id); + + *err = CURLE_OK; + switch(quiche_h3_event_type(ev)) { + case QUICHE_H3_EVENT_HEADERS: + stream->h3_got_header = TRUE; + headers.dest = buf; + headers.destlen = len; + headers.nlen = 0; + rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers); + if(rc) { + failf(data, "Error %d in HTTP/3 response header for stream[%"PRId64"]", + rc, stream3_id); + *err = CURLE_RECV_ERROR; + recvd = -1; + break; + } + recvd = headers.nlen; + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][HEADERS] len=%zd", + stream3_id, recvd)); + break; + + case QUICHE_H3_EVENT_DATA: + DEBUGASSERT(!stream->closed); + stream->h3_recving_data = TRUE; + recvd = cf_recv_body(cf, data, buf, len, err); + if(recvd < 0) { + if(*err != CURLE_AGAIN) + return -1; + recvd = 0; + } + break; + + case QUICHE_H3_EVENT_RESET: + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][RESET]", stream3_id)); + stream->closed = TRUE; + stream->reset = TRUE; + /* streamclose(cf->conn, "Reset of stream");*/ + stream->h3_recving_data = FALSE; + break; + + case QUICHE_H3_EVENT_FINISHED: + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][FINISHED]", stream3_id)); + stream->closed = TRUE; + /* streamclose(cf->conn, "End of stream");*/ + stream->h3_recving_data = FALSE; + break; + + case QUICHE_H3_EVENT_GOAWAY: + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][GOAWAY]", stream3_id)); + break; + + default: + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv, unhandled event %d", + stream3_id, quiche_h3_event_type(ev))); + break; + } + return recvd; +} + +static ssize_t h3_process_pending(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, + CURLcode *err) +{ + struct HTTP *stream = data->req.p.http; + struct h3_event_node *node = stream->pending, **pnext = &stream->pending; + ssize_t recvd = 0, erecvd; + + *err = CURLE_OK; + DEBUGASSERT(stream); + while(node && len) { + erecvd = h3_process_event(cf, data, buf, len, + stream->stream3_id, node->ev, err); + quiche_h3_event_free(node->ev); + *pnext = node->next; + free(node); + node = *pnext; + if(erecvd < 0) { + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] process event -> %d", + stream->stream3_id, *err)); + return erecvd; + } + recvd += erecvd; + *err = CURLE_OK; + buf += erecvd; + len -= erecvd; + } + return recvd; +} + +static CURLcode cf_process_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + int64_t stream3_id = data->req.p.http? data->req.p.http->stream3_id : -1; + uint8_t buf[65536]; + size_t bufsize = sizeof(buf); + struct sockaddr_storage remote_addr; + socklen_t remote_addrlen; + quiche_recv_info recv_info; + ssize_t recvd, nread; + ssize_t total = 0, pkts = 0; + + DEBUGASSERT(ctx->qconn); + + /* in case the timeout expired */ + quiche_conn_on_timeout(ctx->qconn); + + do { + remote_addrlen = sizeof(remote_addr); + while((recvd = recvfrom(ctx->q.sockfd, (char *)buf, bufsize, 0, + (struct sockaddr *)&remote_addr, + &remote_addrlen)) == -1 && + SOCKERRNO == EINTR) + ; + if(recvd < 0) { + if((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)) { + break; + } + if(SOCKERRNO == ECONNREFUSED) { + const char *r_ip; + int r_port; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + failf(data, "quiche: connection to %s:%u refused", + r_ip, r_port); + return CURLE_COULDNT_CONNECT; + } + failf(data, "quiche: recvfrom() unexpectedly returned %zd " + "(errno: %d, socket %d)", recvd, SOCKERRNO, ctx->q.sockfd); + return CURLE_RECV_ERROR; + } + + total += recvd; + ++pkts; + if(recvd > 0 && !ctx->got_first_byte) { + ctx->first_byte_at = Curl_now(); + ctx->got_first_byte = TRUE; + } + recv_info.from = (struct sockaddr *) &remote_addr; + recv_info.from_len = remote_addrlen; + recv_info.to = (struct sockaddr *) &ctx->q.local_addr; + recv_info.to_len = ctx->q.local_addrlen; + + nread = quiche_conn_recv(ctx->qconn, buf, recvd, &recv_info); + if(nread < 0) { + if(QUICHE_ERR_DONE == nread) { + DEBUGF(LOG_CF(data, cf, "ingress, quiche is DONE")); + return CURLE_OK; + } + else if(QUICHE_ERR_TLS_FAIL == nread) { + long verify_ok = SSL_get_verify_result(ctx->ssl); + if(verify_ok != X509_V_OK) { + failf(data, "SSL certificate problem: %s", + X509_verify_cert_error_string(verify_ok)); + return CURLE_PEER_FAILED_VERIFICATION; + } + } + else { + failf(data, "quiche_conn_recv() == %zd", nread); + return CURLE_RECV_ERROR; + } + } + else if(nread < recvd) { + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] ingress, quiche only " + "accepted %zd/%zd bytes", + stream3_id, nread, recvd)); + } + + } while(pkts < 1000); /* arbitrary */ + + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] ingress, recvd %zd bytes " + "in %zd packets", stream3_id, total, pkts)); + return CURLE_OK; +} + +/* + * flush_egress drains the buffers and sends off data. + * Calls failf() on errors. + */ +static CURLcode cf_flush_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + int64_t stream3_id = data->req.p.http? data->req.p.http->stream3_id : -1; + quiche_send_info send_info; + ssize_t outlen, total_len = 0; + size_t max_udp_payload_size = + quiche_conn_max_send_udp_payload_size(ctx->qconn); + size_t gsolen = max_udp_payload_size; + size_t sent, pktcnt = 0; + CURLcode result; + int64_t timeout_ns; + + ctx->q.no_gso = TRUE; + if(ctx->q.num_blocked_pkt) { + result = vquic_send_blocked_pkt(cf, data, &ctx->q); + if(result) { + if(result == CURLE_AGAIN) { + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, still not " + "able to send blocked packet", stream3_id)); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + goto out; + } + } + + for(;;) { + outlen = quiche_conn_send(ctx->qconn, ctx->q.pktbuf, max_udp_payload_size, + &send_info); + if(outlen == QUICHE_ERR_DONE) { + result = CURLE_OK; + goto out; + } + + if(outlen < 0) { + failf(data, "quiche_conn_send returned %zd", outlen); + result = CURLE_SEND_ERROR; + goto out; + } + + /* send the pktbuf *before* the last addition */ + result = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf, + outlen, gsolen, &sent); + ++pktcnt; + total_len += outlen; + if(result) { + if(result == CURLE_AGAIN) { + /* blocked, add the pktbuf *before* and *at* the last addition + * separately to the blocked packages */ + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, pushing blocked " + "packet with %zd bytes", stream3_id, outlen)); + vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf, outlen, gsolen); + Curl_expire(data, 1, EXPIRE_QUIC); + return CURLE_OK; + } + goto out; + } + } + +out: + timeout_ns = quiche_conn_timeout_as_nanos(ctx->qconn); + if(timeout_ns % 1000000) + timeout_ns += 1000000; + /* expire resolution is milliseconds */ + Curl_expire(data, (timeout_ns / 1000000), EXPIRE_QUIC); + if(pktcnt) + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, sent %zd packets " + "with %zd bytes", stream3_id, pktcnt, total_len)); + return result; +} + +static ssize_t recv_closed_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + CURLcode *err) +{ + struct HTTP *stream = data->req.p.http; + ssize_t nread = -1; + + if(stream->reset) { + failf(data, + "HTTP/3 stream %" PRId64 " reset by server", stream->stream3_id); + *err = stream->h3_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, was reset -> %d", + stream->stream3_id, *err)); + goto out; + } + + if(!stream->h3_got_header) { + failf(data, + "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" + " all response header fields, treated as error", + stream->stream3_id); + /* *err = CURLE_PARTIAL_FILE; */ + *err = CURLE_RECV_ERROR; + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed incomplete" + " -> %d", stream->stream3_id, *err)); + goto out; + } + else { + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed ok" + " -> %d", stream->stream3_id, *err)); + } + *err = CURLE_OK; + nread = 0; + +out: + return nread; +} + +static CURLcode cf_poll_events(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + quiche_h3_event *ev; + + /* Take in the events and distribute them to the transfers. */ + while(1) { + int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev); + if(stream3_id < 0) { + /* nothing more to do */ + break; + } + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv, queue event %s " + "for [h3sid=%"PRId64"]", + stream? stream->stream3_id : -1, cf_ev_name(ev), + stream3_id)); + if(h3_add_event(cf, data, stream3_id, ev) != CURLE_OK) { + return CURLE_OUT_OF_MEMORY; + } + } + return CURLE_OK; +} + +static ssize_t cf_recv_transfer_data(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, + CURLcode *err) +{ + struct HTTP *stream = data->req.p.http; + ssize_t recvd = -1; + size_t offset = 0; + + if(stream->h3_recving_data) { + /* try receiving body first */ + recvd = cf_recv_body(cf, data, buf, len, err); + if(recvd < 0) { + if(*err != CURLE_AGAIN) + return -1; + recvd = 0; + } + if(recvd > 0) { + offset = recvd; + } + } + + if(offset < len && stream->pending) { + /* process any pending events for `data` first. if there are, + * return so the transfer can handle those. We do not want to + * progress ingress while events are pending here. */ + recvd = h3_process_pending(cf, data, buf + offset, len - offset, err); + if(recvd < 0) { + if(*err != CURLE_AGAIN) + return -1; + recvd = 0; + } + if(recvd > 0) { + offset += recvd; + } + } + + if(offset) { + *err = CURLE_OK; + return offset; + } + *err = CURLE_AGAIN; + return 0; +} + +static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct HTTP *stream = data->req.p.http; + ssize_t recvd = -1; + + *err = CURLE_AGAIN; + + recvd = cf_recv_transfer_data(cf, data, buf, len, err); + if(recvd) + goto out; + if(stream->closed) { + recvd = recv_closed_stream(cf, data, err); + goto out; + } + + /* we did get nothing from the quiche buffers or pending events. + * Take in more data from the connection, any error is fatal */ + if(cf_process_ingress(cf, data)) { + DEBUGF(LOG_CF(data, cf, "h3_stream_recv returns on ingress")); + *err = CURLE_RECV_ERROR; + recvd = -1; + goto out; + } + /* poll quiche and distribute the events to the transfers */ + *err = cf_poll_events(cf, data); + if(*err) { + recvd = -1; + goto out; + } + + /* try to receive again for this transfer */ + recvd = cf_recv_transfer_data(cf, data, buf, len, err); + if(recvd) + goto out; + if(stream->closed) { + recvd = recv_closed_stream(cf, data, err); + goto out; + } + recvd = -1; + *err = CURLE_AGAIN; + data->state.drain = 0; + +out: + if(cf_flush_egress(cf, data)) { + DEBUGF(LOG_CF(data, cf, "cf_recv, flush egress failed")); + *err = CURLE_SEND_ERROR; + return -1; + } + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] cf_recv -> %zd, err=%d", + stream->stream3_id, recvd, *err)); + if(recvd > 0) + notify_drain(cf, data); + return recvd; +} + +/* Index where :authority header field will appear in request header + field list. */ +#define AUTHORITY_DST_IDX 3 + +static CURLcode cf_http_request(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, + size_t len) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + size_t nheader; + int64_t stream3_id; + quiche_h3_header *nva = NULL; + CURLcode result = CURLE_OK; + struct h2h3req *hreq = NULL; + + stream->h3req = TRUE; /* send off! */ + stream->closed = FALSE; + stream->reset = FALSE; + + result = Curl_pseudo_headers(data, mem, len, NULL, &hreq); + if(result) + goto fail; + nheader = hreq->entries; + + nva = malloc(sizeof(quiche_h3_header) * nheader); + if(!nva) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } + else { + unsigned int i; + for(i = 0; i < nheader; i++) { + nva[i].name = (unsigned char *)hreq->header[i].name; + nva[i].name_len = hreq->header[i].namelen; + nva[i].value = (unsigned char *)hreq->header[i].value; + nva[i].value_len = hreq->header[i].valuelen; + } + } + + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: + if(data->state.infilesize != -1) + stream->upload_left = data->state.infilesize; + else + /* data sending without specifying the data amount up front */ + stream->upload_left = -1; /* unknown, but not zero */ + + stream->upload_done = !stream->upload_left; + stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader, + stream->upload_done); + break; + default: + stream->upload_left = 0; + stream->upload_done = TRUE; + stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader, + TRUE); + break; + } + + Curl_safefree(nva); + + if(stream3_id < 0) { + if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) { + DEBUGF(LOG_CF(data, cf, "send_request(%s, body_len=%ld) rejected " + "with H3_ERR_STREAM_BLOCKED", + data->state.url, (long)stream->upload_left)); + result = CURLE_AGAIN; + goto fail; + } + else { + DEBUGF(LOG_CF(data, cf, "send_request(%s, body_len=%ld) -> %" PRId64, + data->state.url, (long)stream->upload_left, stream3_id)); + } + result = CURLE_SEND_ERROR; + goto fail; + } + + stream->stream3_id = stream3_id; + infof(data, "Using HTTP/3 Stream ID: %" PRId64 " (easy handle %p)", + stream3_id, (void *)data); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] opened for %s", + stream3_id, data->state.url)); + + Curl_pseudo_free(hreq); + return CURLE_OK; + +fail: + free(nva); + Curl_pseudo_free(hreq); + return result; +} + +static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + ssize_t nwritten; + + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_send(len=%zu) start", + stream->h3req? stream->stream3_id : -1, len)); + *err = cf_process_ingress(cf, data); + if(*err) + return -1; + + if(!stream->h3req) { + CURLcode result = cf_http_request(cf, data, buf, len); + if(result) { + *err = result; + return -1; + } + nwritten = len; + } + else { + nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id, + (uint8_t *)buf, len, FALSE); + DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] send body(len=%zu) -> %zd", + stream->stream3_id, len, nwritten)); + if(nwritten == QUICHE_H3_ERR_DONE) { + /* no error, nothing to do (flow control?) */ + *err = CURLE_AGAIN; + nwritten = -1; + } + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) { + DEBUGF(LOG_CF(data, cf, "send_body(len=%zu) -> exceeds size", len)); + *err = CURLE_SEND_ERROR; + nwritten = -1; + } + else if(nwritten < 0) { + DEBUGF(LOG_CF(data, cf, "send_body(len=%zu) -> SEND_ERROR", len)); + *err = CURLE_SEND_ERROR; + nwritten = -1; + } + else { + *err = CURLE_OK; + } + } + + if(cf_flush_egress(cf, data)) { + *err = CURLE_SEND_ERROR; + return -1; + } + + return nwritten; +} + +static bool stream_is_writeable(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct HTTP *stream = data->req.p.http; + + /* surely, there must be a better way */ + quiche_stream_iter *qiter = quiche_conn_writable(ctx->qconn); + if(qiter) { + uint64_t stream_id; + while(quiche_stream_iter_next(qiter, &stream_id)) { + if(stream_id == (uint64_t)stream->stream3_id) + return TRUE; + } + quiche_stream_iter_free(qiter); + } + return FALSE; +} + +static int cf_quiche_get_select_socks(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_socket_t *socks) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct SingleRequest *k = &data->req; + int rv = GETSOCK_BLANK; + + socks[0] = ctx->q.sockfd; + + /* in an HTTP/3 connection we can basically always get a frame so we should + always be ready for one */ + rv |= GETSOCK_READSOCK(0); + + /* we're still uploading or the HTTP/3 layer wants to send data */ + if(((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) + && stream_is_writeable(cf, data)) + rv |= GETSOCK_WRITESOCK(0); + + return rv; +} + +/* + * Called from transfer.c:data_pending to know if we should keep looping + * to receive more data from the connection. + */ +static bool cf_quiche_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct HTTP *stream = data->req.p.http; + + if(stream->pending) { + DEBUGF(LOG_CF((struct Curl_easy *)data, cf, + "[h3sid=%"PRId64"] has event pending", stream->stream3_id)); + return TRUE; + } + if(stream->h3_recving_data) { + DEBUGF(LOG_CF((struct Curl_easy *)data, cf, + "[h3sid=%"PRId64"] is receiving DATA", stream->stream3_id)); + return TRUE; + } + if(data->state.drain) { + DEBUGF(LOG_CF((struct Curl_easy *)data, cf, + "[h3sid=%"PRId64"] is draining", stream->stream3_id)); + return TRUE; + } + return FALSE; +} + +static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_DATA_DONE: { + struct HTTP *stream = data->req.p.http; + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] easy handle is %s", + stream->stream3_id, arg1? "cancelled" : "done")); + h3_clear_pending(data); + break; + } + case CF_CTRL_DATA_DONE_SEND: { + struct HTTP *stream = data->req.p.http; + ssize_t sent; + stream->upload_done = TRUE; + sent = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id, + NULL, 0, TRUE); + DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] send_body FINISHED", + stream->stream3_id)); + if(sent < 0) + return CURLE_SEND_ERROR; + break; + } + case CF_CTRL_DATA_IDLE: + /* anything to do? */ + break; + case CF_CTRL_CONN_REPORT_STATS: + if(cf->sockindex == FIRSTSOCKET) { + if(ctx->got_first_byte) + Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->first_byte_at); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at); + } + break; + default: + break; + } + return result; +} + +static CURLcode cf_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; + + if(cf->conn->ssl_config.verifyhost) { + X509 *server_cert; + server_cert = SSL_get_peer_certificate(ctx->ssl); + if(!server_cert) { + result = CURLE_PEER_FAILED_VERIFICATION; + goto out; + } + result = Curl_ossl_verifyhost(data, cf->conn, server_cert); + X509_free(server_cert); + if(result) + goto out; + } + else + DEBUGF(LOG_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) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + int rv; + CURLcode result; + const struct Curl_sockaddr_ex *sockaddr; + + DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD); + +#ifdef DEBUG_QUICHE + /* initialize debug log callback only once */ + static int debug_log_init = 0; + if(!debug_log_init) { + quiche_enable_debug_logging(quiche_debug_log, NULL); + debug_log_init = 1; + } +#endif + + result = vquic_ctx_init(&ctx->q, MAX_UDP_PAYLOAD_SIZE * MAX_PKT_BURST); + if(result) + return result; + + ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION); + if(!ctx->cfg) { + failf(data, "can't create quiche config"); + return CURLE_FAILED_INIT; + } + quiche_config_set_max_idle_timeout(ctx->cfg, QUIC_IDLE_TIMEOUT); + quiche_config_set_initial_max_data(ctx->cfg, QUIC_MAX_DATA); + quiche_config_set_initial_max_stream_data_bidi_local( + ctx->cfg, QUIC_MAX_DATA); + quiche_config_set_initial_max_stream_data_bidi_remote( + ctx->cfg, QUIC_MAX_DATA); + quiche_config_set_initial_max_stream_data_uni(ctx->cfg, QUIC_MAX_DATA); + quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS); + quiche_config_set_initial_max_streams_uni(ctx->cfg, QUIC_MAX_STREAMS); + quiche_config_set_application_protos(ctx->cfg, + (uint8_t *) + QUICHE_H3_APPLICATION_PROTOCOL, + sizeof(QUICHE_H3_APPLICATION_PROTOCOL) + - 1); + + DEBUGASSERT(!ctx->ssl); + DEBUGASSERT(!ctx->sslctx); + ctx->sslctx = quic_ssl_ctx(data); + if(!ctx->sslctx) + return CURLE_QUIC_CONNECT_ERROR; + ctx->ssl = SSL_new(ctx->sslctx); + if(!ctx->ssl) + return CURLE_QUIC_CONNECT_ERROR; + + SSL_set_app_data(ctx->ssl, cf); + SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name); + + result = Curl_rand(data, ctx->scid, sizeof(ctx->scid)); + if(result) + return result; + + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, + &sockaddr, NULL, NULL, NULL, NULL); + ctx->q.local_addrlen = sizeof(ctx->q.local_addr); + rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr, + &ctx->q.local_addrlen); + if(rv == -1) + return CURLE_QUIC_CONNECT_ERROR; + + ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid, + sizeof(ctx->scid), NULL, 0, + (struct sockaddr *)&ctx->q.local_addr, + ctx->q.local_addrlen, + &sockaddr->sa_addr, sockaddr->addrlen, + ctx->cfg, ctx->ssl, false); + if(!ctx->qconn) { + failf(data, "can't create quiche connection"); + return CURLE_OUT_OF_MEMORY; + } + + /* Known to not work on Windows */ +#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD) + { + int qfd; + (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd); + if(qfd != -1) + quiche_conn_set_qlog_fd(ctx->qconn, qfd, + "qlog title", "curl qlog"); + } +#endif + + result = cf_flush_egress(cf, data); + if(result) + return result; + + { + unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL; + unsigned alpn_len, offset = 0; + + /* Replace each ALPN length prefix by a comma. */ + while(offset < sizeof(alpn_protocols) - 1) { + alpn_len = alpn_protocols[offset]; + alpn_protocols[offset] = ','; + offset += 1 + alpn_len; + } + + DEBUGF(LOG_CF(data, cf, "Sent QUIC client Initial, ALPN: %s", + alpn_protocols + 1)); + } + + return CURLE_OK; +} + +static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct curltime now; + + if(cf->connected) { + *done = TRUE; + 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(); + + if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) { + /* Not time yet to attempt the next connect */ + DEBUGF(LOG_CF(data, cf, "waiting for reconnect time")); + goto out; + } + + if(!ctx->qconn) { + result = cf_connect_start(cf, data); + if(result) + goto out; + ctx->started_at = now; + result = cf_flush_egress(cf, data); + /* we do not expect to be able to recv anything yet */ + goto out; + } + + result = cf_process_ingress(cf, data); + if(result) + goto out; + + result = cf_flush_egress(cf, data); + if(result) + goto out; + + if(quiche_conn_is_established(ctx->qconn)) { + DEBUGF(LOG_CF(data, cf, "handshake complete after %dms", + (int)Curl_timediff(now, ctx->started_at))); + ctx->handshake_at = now; + result = cf_verify_peer(cf, data); + if(!result) { + DEBUGF(LOG_CF(data, cf, "peer verified")); + cf->connected = TRUE; + cf->conn->alpn = CURL_HTTP_VERSION_3; + *done = TRUE; + connkeep(cf->conn, "HTTP/3 default"); + } + } + else if(quiche_conn_is_draining(ctx->qconn)) { + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE right away. Our connection then enters the DRAINING + * state. + * This may be a stopping of the service or it may be that the server + * is reloading and a new instance will start serving soon. + * In any case, we tear down our socket and start over with a new one. + * We re-open the underlying UDP cf right now, but do not start + * connecting until called again. + */ + int reconn_delay_ms = 200; + + DEBUGF(LOG_CF(data, cf, "connect, remote closed, reconnect after %dms", + reconn_delay_ms)); + Curl_conn_cf_close(cf->next, data); + cf_quiche_ctx_clear(ctx); + result = Curl_conn_cf_connect(cf->next, data, FALSE, done); + if(!result && *done) { + *done = FALSE; + ctx->reconnect_at = Curl_now(); + ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000; + Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC); + result = CURLE_OK; + } + } + +out: +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(result && result != CURLE_AGAIN) { + const char *r_ip; + int r_port; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, + &r_ip, &r_port, NULL, NULL); + infof(data, "connect to %s port %u failed: %s", + r_ip, r_port, curl_easy_strerror(result)); + } +#endif + return result; +} + +static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + (void)data; + if(ctx) { + if(ctx->qconn) { + (void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0); + /* flushing the egress is not a failsafe way to deliver all the + outstanding packets, but we also don't want to get stuck here... */ + (void)cf_flush_egress(cf, data); + } + cf_quiche_ctx_clear(ctx); + } +} + +static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + (void)data; + cf_quiche_ctx_clear(ctx); + free(ctx); + cf->ctx = NULL; +} + +static CURLcode cf_quiche_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { + uint64_t max_streams = CONN_INUSE(cf->conn); + if(!ctx->goaway) { + max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn); + } + *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams; + DEBUGF(LOG_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1)); + return CURLE_OK; + } + case CF_QUERY_CONNECT_REPLY_MS: + if(ctx->got_first_byte) { + timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; + } + else + *pres1 = -1; + 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_quiche_destroy, + cf_quiche_connect, + cf_quiche_close, + Curl_cf_def_get_host, + cf_quiche_get_select_socks, + cf_quiche_data_pending, + cf_quiche_send, + cf_quiche_recv, + cf_quiche_data_event, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_quiche_query, +}; + +CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_quiche_ctx *ctx = NULL; + struct Curl_cfilter *cf = NULL, *udp_cf = NULL; + CURLcode result; + + (void)data; + (void)conn; + ctx = calloc(sizeof(*ctx), 1); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + 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; + + 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(udp_cf, data); + Curl_safefree(cf); + Curl_safefree(ctx); + } + + return result; +} + +bool Curl_conn_is_quiche(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; + + (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; +} + +#endif diff --git a/Utilities/cmcurl/lib/vquic/quiche.h b/Utilities/cmcurl/lib/vquic/curl_quiche.h index 2da65f5f48..bce781c1bc 100644 --- a/Utilities/cmcurl/lib/vquic/quiche.h +++ b/Utilities/cmcurl/lib/vquic/curl_quiche.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_VQUIC_QUICHE_H -#define HEADER_CURL_VQUIC_QUICHE_H +#ifndef HEADER_CURL_VQUIC_CURL_QUICHE_H +#define HEADER_CURL_VQUIC_CURL_QUICHE_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -31,28 +31,20 @@ #include <quiche.h> #include <openssl/ssl.h> -struct quic_handshake { - char *buf; /* pointer to the buffer */ - size_t alloclen; /* size of allocation */ - size_t len; /* size of content in buffer */ - size_t nread; /* how many bytes have been read */ -}; - -struct quicsocket { - quiche_config *cfg; - quiche_conn *conn; - quiche_h3_conn *h3c; - quiche_h3_config *h3config; - uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; - curl_socket_t sockfd; - uint32_t version; - SSL_CTX *sslctx; - SSL *ssl; - bool h3_recving; /* TRUE when in h3-body-reading state */ - struct sockaddr_storage local_addr; - socklen_t local_addrlen; -}; +struct Curl_cfilter; +struct Curl_easy; + +void Curl_quiche_ver(char *p, size_t len); + +CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_quiche(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); #endif -#endif /* HEADER_CURL_VQUIC_QUICHE_H */ +#endif /* HEADER_CURL_VQUIC_CURL_QUICHE_H */ diff --git a/Utilities/cmcurl/lib/vquic/msh3.c b/Utilities/cmcurl/lib/vquic/msh3.c deleted file mode 100644 index c3e58e726a..0000000000 --- a/Utilities/cmcurl/lib/vquic/msh3.c +++ /dev/null @@ -1,527 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, 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" - -#ifdef USE_MSH3 - -#include "urldata.h" -#include "timeval.h" -#include "multiif.h" -#include "sendf.h" -#include "connect.h" -#include "h2h3.h" -#include "msh3.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -/* #define DEBUG_HTTP3 1 */ -#ifdef DEBUG_HTTP3 -#define H3BUGF(x) x -#else -#define H3BUGF(x) do { } while(0) -#endif - -#define MSH3_REQ_INIT_BUF_LEN 8192 - -static CURLcode msh3_do_it(struct Curl_easy *data, bool *done); -static int msh3_getsock(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks); -static CURLcode msh3_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection); -static unsigned int msh3_conncheck(struct Curl_easy *data, - struct connectdata *conn, - unsigned int checks_to_perform); -static Curl_recv msh3_stream_recv; -static Curl_send msh3_stream_send; -static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, - void *IfContext, - const MSH3_HEADER *Header); -static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, - void *IfContext, uint32_t Length, - const uint8_t *Data); -static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, - bool Aborted, uint64_t AbortError); -static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext); - -static const struct Curl_handler msh3_curl_handler_http3 = { - "HTTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - msh3_do_it, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - msh3_getsock, /* proto_getsock */ - msh3_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - msh3_getsock, /* perform_getsock */ - msh3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - msh3_conncheck, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTPS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_STREAM /* flags */ -}; - -static const MSH3_REQUEST_IF msh3_request_if = { - msh3_header_received, - msh3_data_received, - msh3_complete, - msh3_shutdown -}; - -void Curl_quic_ver(char *p, size_t len) -{ - uint32_t v[4]; - MsH3Version(v); - (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]); -} - -CURLcode Curl_quic_connect(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t sockfd, - int sockindex, - const struct sockaddr *addr, - socklen_t addrlen) -{ - struct quicsocket *qs = &conn->hequic[sockindex]; - bool insecure = !conn->ssl_config.verifypeer; - memset(qs, 0, sizeof(*qs)); - - (void)sockfd; - (void)addr; /* TODO - Pass address along */ - (void)addrlen; - - H3BUGF(infof(data, "creating new api/connection")); - - qs->api = MsH3ApiOpen(); - if(!qs->api) { - failf(data, "can't create msh3 api"); - return CURLE_FAILED_INIT; - } - - qs->conn = MsH3ConnectionOpen(qs->api, - conn->host.name, - (uint16_t)conn->remote_port, - insecure); - if(!qs->conn) { - failf(data, "can't create msh3 connection"); - if(qs->api) { - MsH3ApiClose(qs->api); - } - return CURLE_FAILED_INIT; - } - - return CURLE_OK; -} - -CURLcode Curl_quic_is_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *connected) -{ - struct quicsocket *qs = &conn->hequic[sockindex]; - MSH3_CONNECTION_STATE state; - - state = MsH3ConnectionGetState(qs->conn, false); - if(state == MSH3_CONN_HANDSHAKE_FAILED || state == MSH3_CONN_DISCONNECTED) { - failf(data, "failed to connect, state=%u", (uint32_t)state); - return CURLE_COULDNT_CONNECT; - } - - if(state == MSH3_CONN_CONNECTED) { - H3BUGF(infof(data, "connection connected")); - *connected = true; - conn->quic = qs; - conn->recv[sockindex] = msh3_stream_recv; - conn->send[sockindex] = msh3_stream_send; - conn->handler = &msh3_curl_handler_http3; - conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->httpversion = 30; - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - /* TODO - Clean up other happy-eyeballs connection(s)? */ - } - - return CURLE_OK; -} - -static int msh3_getsock(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks) -{ - struct HTTP *stream = data->req.p.http; - int bitmap = GETSOCK_BLANK; - - socks[0] = conn->sock[FIRSTSOCKET]; - - if(stream->recv_error) { - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); - data->state.drain++; - } - else if(stream->recv_header_len || stream->recv_data_len) { - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); - data->state.drain++; - } - - H3BUGF(infof(data, "msh3_getsock %u", (uint32_t)data->state.drain)); - - return bitmap; -} - -static CURLcode msh3_do_it(struct Curl_easy *data, bool *done) -{ - struct HTTP *stream = data->req.p.http; - H3BUGF(infof(data, "msh3_do_it")); - stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN); - if(!stream->recv_buf) { - return CURLE_OUT_OF_MEMORY; - } - stream->req = ZERO_NULL; - msh3_lock_initialize(&stream->recv_lock); - stream->recv_buf_alloc = MSH3_REQ_INIT_BUF_LEN; - stream->recv_header_len = 0; - stream->recv_header_complete = false; - stream->recv_data_len = 0; - stream->recv_data_complete = false; - stream->recv_error = CURLE_OK; - return Curl_http(data, done); -} - -static unsigned int msh3_conncheck(struct Curl_easy *data, - struct connectdata *conn, - unsigned int checks_to_perform) -{ - (void)data; - (void)conn; - (void)checks_to_perform; - H3BUGF(infof(data, "msh3_conncheck")); - return CONNRESULT_NONE; -} - -static void disconnect(struct quicsocket *qs) -{ - if(qs->conn) { - MsH3ConnectionClose(qs->conn); - qs->conn = ZERO_NULL; - } - if(qs->api) { - MsH3ApiClose(qs->api); - qs->api = ZERO_NULL; - } -} - -static CURLcode msh3_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead_connection) -{ - (void)data; - (void)dead_connection; - H3BUGF(infof(data, "disconnecting (msh3)")); - disconnect(conn->quic); - return CURLE_OK; -} - -void Curl_quic_disconnect(struct Curl_easy *data, struct connectdata *conn, - int tempindex) -{ - (void)data; - if(conn->transport == TRNSPRT_QUIC) { - H3BUGF(infof(data, "disconnecting QUIC index %u", tempindex)); - disconnect(&conn->hequic[tempindex]); - } -} - -/* Requires stream->recv_lock to be held */ -static bool msh3request_ensure_room(struct HTTP *stream, size_t len) -{ - uint8_t *new_recv_buf; - const size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len; - if(cur_recv_len + len > stream->recv_buf_alloc) { - size_t new_recv_buf_alloc_len = stream->recv_buf_alloc; - do { - new_recv_buf_alloc_len <<= 1; /* TODO - handle overflow */ - } while(cur_recv_len + len > new_recv_buf_alloc_len); - new_recv_buf = malloc(new_recv_buf_alloc_len); - if(!new_recv_buf) { - return false; - } - if(cur_recv_len) { - memcpy(new_recv_buf, stream->recv_buf, cur_recv_len); - } - stream->recv_buf_alloc = new_recv_buf_alloc_len; - free(stream->recv_buf); - stream->recv_buf = new_recv_buf; - } - return true; -} - -static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request, - void *IfContext, - const MSH3_HEADER *Header) -{ - struct HTTP *stream = IfContext; - size_t total_len; - (void)Request; - - if(stream->recv_header_complete) { - H3BUGF(printf("* ignoring header after data\n")); - return; - } - - msh3_lock_acquire(&stream->recv_lock); - - if((Header->NameLength == 7) && - !strncmp(H2H3_PSEUDO_STATUS, (char *)Header->Name, 7)) { - total_len = 9 + Header->ValueLength; - if(!msh3request_ensure_room(stream, total_len)) { - /* TODO - handle error */ - goto release_lock; - } - msnprintf((char *)stream->recv_buf + stream->recv_header_len, - stream->recv_buf_alloc - stream->recv_header_len, - "HTTP/3 %.*s\n", (int)Header->ValueLength, Header->Value); - } - else { - total_len = Header->NameLength + 4 + Header->ValueLength; - if(!msh3request_ensure_room(stream, total_len)) { - /* TODO - handle error */ - goto release_lock; - } - msnprintf((char *)stream->recv_buf + stream->recv_header_len, - stream->recv_buf_alloc - stream->recv_header_len, - "%.*s: %.*s\n", - (int)Header->NameLength, Header->Name, - (int)Header->ValueLength, Header->Value); - } - - stream->recv_header_len += total_len - 1; /* don't include null-terminator */ - -release_lock: - msh3_lock_release(&stream->recv_lock); -} - -static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request, - void *IfContext, uint32_t Length, - const uint8_t *Data) -{ - struct HTTP *stream = IfContext; - size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len; - (void)Request; - H3BUGF(printf("* msh3_data_received %u. %zu buffered, %zu allocated\n", - Length, cur_recv_len, stream->recv_buf_alloc)); - msh3_lock_acquire(&stream->recv_lock); - if(!stream->recv_header_complete) { - H3BUGF(printf("* Headers complete!\n")); - if(!msh3request_ensure_room(stream, 2)) { - /* TODO - handle error */ - goto release_lock; - } - stream->recv_buf[stream->recv_header_len++] = '\r'; - stream->recv_buf[stream->recv_header_len++] = '\n'; - stream->recv_header_complete = true; - cur_recv_len += 2; - } - if(!msh3request_ensure_room(stream, Length)) { - /* TODO - handle error */ - goto release_lock; - } - memcpy(stream->recv_buf + cur_recv_len, Data, Length); - stream->recv_data_len += (size_t)Length; -release_lock: - msh3_lock_release(&stream->recv_lock); -} - -static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext, - bool Aborted, uint64_t AbortError) -{ - struct HTTP *stream = IfContext; - (void)Request; - (void)AbortError; - H3BUGF(printf("* msh3_complete, aborted=%s\n", Aborted ? "true" : "false")); - msh3_lock_acquire(&stream->recv_lock); - if(Aborted) { - stream->recv_error = CURLE_HTTP3; /* TODO - how do we pass AbortError? */ - } - stream->recv_header_complete = true; - stream->recv_data_complete = true; - msh3_lock_release(&stream->recv_lock); -} - -static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext) -{ - struct HTTP *stream = IfContext; - (void)Request; - (void)stream; -} - -static ssize_t msh3_stream_send(struct Curl_easy *data, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct HTTP *stream = data->req.p.http; - struct quicsocket *qs = conn->quic; - struct h2h3req *hreq; - - (void)sockindex; - /* Sizes must match for cast below to work" */ - DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo)); - - H3BUGF(infof(data, "msh3_stream_send %zu", len)); - - if(!stream->req) { - *curlcode = Curl_pseudo_headers(data, mem, len, &hreq); - if(*curlcode) { - failf(data, "Curl_pseudo_headers failed"); - return -1; - } - H3BUGF(infof(data, "starting request with %zu headers", hreq->entries)); - stream->req = MsH3RequestOpen(qs->conn, &msh3_request_if, stream, - (MSH3_HEADER*)hreq->header, hreq->entries); - Curl_pseudo_free(hreq); - if(!stream->req) { - failf(data, "request open failed"); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - *curlcode = CURLE_OK; - return len; - } - H3BUGF(infof(data, "send %zd body bytes on request %p", len, - (void *)stream->req)); - *curlcode = CURLE_SEND_ERROR; - return -1; -} - -static ssize_t msh3_stream_recv(struct Curl_easy *data, - int sockindex, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - struct HTTP *stream = data->req.p.http; - size_t outsize = 0; - (void)sockindex; - H3BUGF(infof(data, "msh3_stream_recv %zu", buffersize)); - - if(stream->recv_error) { - failf(data, "request aborted"); - *curlcode = stream->recv_error; - return -1; - } - - msh3_lock_acquire(&stream->recv_lock); - - if(stream->recv_header_len) { - outsize = buffersize; - if(stream->recv_header_len < outsize) { - outsize = stream->recv_header_len; - } - memcpy(buf, stream->recv_buf, outsize); - if(outsize < stream->recv_header_len + stream->recv_data_len) { - memmove(stream->recv_buf, stream->recv_buf + outsize, - stream->recv_header_len + stream->recv_data_len - outsize); - } - stream->recv_header_len -= outsize; - H3BUGF(infof(data, "returned %zu bytes of headers", outsize)); - } - else if(stream->recv_data_len) { - outsize = buffersize; - if(stream->recv_data_len < outsize) { - outsize = stream->recv_data_len; - } - memcpy(buf, stream->recv_buf, outsize); - if(outsize < stream->recv_data_len) { - memmove(stream->recv_buf, stream->recv_buf + outsize, - stream->recv_data_len - outsize); - } - stream->recv_data_len -= outsize; - H3BUGF(infof(data, "returned %zu bytes of data", outsize)); - } - else if(stream->recv_data_complete) { - H3BUGF(infof(data, "receive complete")); - } - - msh3_lock_release(&stream->recv_lock); - - return (ssize_t)outsize; -} - -CURLcode Curl_quic_done_sending(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - H3BUGF(infof(data, "Curl_quic_done_sending")); - if(conn->handler == &msh3_curl_handler_http3) { - struct HTTP *stream = data->req.p.http; - stream->upload_done = TRUE; - } - - return CURLE_OK; -} - -void Curl_quic_done(struct Curl_easy *data, bool premature) -{ - struct HTTP *stream = data->req.p.http; - (void)premature; - H3BUGF(infof(data, "Curl_quic_done")); - if(stream) { - if(stream->recv_buf) { - Curl_safefree(stream->recv_buf); - msh3_lock_uninitialize(&stream->recv_lock); - } - if(stream->req) { - MsH3RequestClose(stream->req); - stream->req = ZERO_NULL; - } - } -} - -bool Curl_quic_data_pending(const struct Curl_easy *data) -{ - struct HTTP *stream = data->req.p.http; - H3BUGF(infof((struct Curl_easy *)data, "Curl_quic_data_pending")); - return stream->recv_header_len || stream->recv_data_len; -} - -/* - * Called from transfer.c:Curl_readwrite when neither HTTP level read - * nor write is performed. It is a good place to handle timer expiry - * for QUIC transport. - */ -CURLcode Curl_quic_idle(struct Curl_easy *data) -{ - (void)data; - H3BUGF(infof(data, "Curl_quic_idle")); - return CURLE_OK; -} - -#endif /* USE_MSH3 */ diff --git a/Utilities/cmcurl/lib/vquic/ngtcp2.c b/Utilities/cmcurl/lib/vquic/ngtcp2.c deleted file mode 100644 index f16b469946..0000000000 --- a/Utilities/cmcurl/lib/vquic/ngtcp2.c +++ /dev/null @@ -1,2266 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, 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" - -#ifdef USE_NGTCP2 -#include <ngtcp2/ngtcp2.h> -#include <nghttp3/nghttp3.h> - -#ifdef USE_OPENSSL -#include <openssl/err.h> -#ifdef OPENSSL_IS_BORINGSSL -#include <ngtcp2/ngtcp2_crypto_boringssl.h> -#else -#include <ngtcp2/ngtcp2_crypto_openssl.h> -#endif -#include "vtls/openssl.h" -#elif defined(USE_GNUTLS) -#include <ngtcp2/ngtcp2_crypto_gnutls.h> -#include "vtls/gtls.h" -#elif defined(USE_WOLFSSL) -#include <ngtcp2/ngtcp2_crypto_wolfssl.h> -#include "vtls/wolfssl.h" -#endif - -#include "urldata.h" -#include "sendf.h" -#include "strdup.h" -#include "rand.h" -#include "ngtcp2.h" -#include "multiif.h" -#include "strcase.h" -#include "cfilters.h" -#include "connect.h" -#include "strerror.h" -#include "dynbuf.h" -#include "vquic.h" -#include "h2h3.h" -#include "vtls/keylog.h" -#include "vtls/vtls.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -/* #define DEBUG_NGTCP2 */ -#ifdef CURLDEBUG -#define DEBUG_HTTP3 -#endif -#ifdef DEBUG_HTTP3 -#define H3BUGF(x) x -#else -#define H3BUGF(x) do { } while(0) -#endif - -#define H3_ALPN_H3_29 "\x5h3-29" -#define H3_ALPN_H3 "\x2h3" - -/* - * This holds outgoing HTTP/3 stream data that is used by nghttp3 until acked. - * It is used as a circular buffer. Add new bytes at the end until it reaches - * the far end, then start over at index 0 again. - */ - -#define H3_SEND_SIZE (256*1024) -struct h3out { - uint8_t buf[H3_SEND_SIZE]; - size_t used; /* number of bytes used in the buffer */ - size_t windex; /* index in the buffer where to start writing the next - data block */ -}; - -#define QUIC_MAX_STREAMS (256*1024) -#define QUIC_MAX_DATA (1*1024*1024) -#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS) - -#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 - -/* ngtcp2 default congestion controller does not perform pacing. Limit - the maximum packet burst to MAX_PKT_BURST packets. */ -#define MAX_PKT_BURST 10 - -static CURLcode ng_process_ingress(struct Curl_easy *data, - curl_socket_t sockfd, - struct quicsocket *qs); -static CURLcode ng_flush_egress(struct Curl_easy *data, int sockfd, - struct quicsocket *qs); -static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, - uint64_t datalen, void *user_data, - void *stream_user_data); - -static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) -{ - struct quicsocket *qs = conn_ref->user_data; - return qs->qconn; -} - -static ngtcp2_tstamp timestamp(void) -{ - struct curltime ct = Curl_now(); - return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS; -} - -#ifdef DEBUG_NGTCP2 -static void quic_printf(void *user_data, const char *fmt, ...) -{ - va_list ap; - (void)user_data; /* TODO, use this to do infof() instead long-term */ - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, "\n"); -} -#endif - -static void qlog_callback(void *user_data, uint32_t flags, - const void *data, size_t datalen) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - (void)flags; - if(qs->qlogfd != -1) { - ssize_t rc = write(qs->qlogfd, data, datalen); - if(rc == -1) { - /* on write error, stop further write attempts */ - close(qs->qlogfd); - qs->qlogfd = -1; - } - } - -} - -static void quic_settings(struct quicsocket *qs, - uint64_t stream_buffer_size) -{ - ngtcp2_settings *s = &qs->settings; - ngtcp2_transport_params *t = &qs->transport_params; - ngtcp2_settings_default(s); - ngtcp2_transport_params_default(t); -#ifdef DEBUG_NGTCP2 - s->log_printf = quic_printf; -#else - s->log_printf = NULL; -#endif - s->initial_ts = timestamp(); - t->initial_max_stream_data_bidi_local = stream_buffer_size; - t->initial_max_stream_data_bidi_remote = QUIC_MAX_STREAMS; - t->initial_max_stream_data_uni = QUIC_MAX_STREAMS; - t->initial_max_data = QUIC_MAX_DATA; - t->initial_max_streams_bidi = 1; - t->initial_max_streams_uni = 3; - t->max_idle_timeout = QUIC_IDLE_TIMEOUT; - if(qs->qlogfd != -1) { - s->qlog.write = qlog_callback; - } -} - -#ifdef USE_OPENSSL -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#elif defined(USE_GNUTLS) -static int keylog_callback(gnutls_session_t session, const char *label, - const gnutls_datum_t *secret) -{ - gnutls_datum_t crandom; - gnutls_datum_t srandom; - - gnutls_session_get_random(session, &crandom, &srandom); - if(crandom.size != 32) { - return -1; - } - - 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 quicsocket *qs); - -#ifdef USE_OPENSSL -static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); - -#ifdef OPENSSL_IS_BORINGSSL - if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); - return NULL; - } -#else - if(ngtcp2_crypto_openssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_openssl_configure_client_context failed"); - return NULL; - } -#endif - - SSL_CTX_set_default_verify_paths(ssl_ctx); - -#ifdef OPENSSL_IS_BORINGSSL - if(SSL_CTX_set1_curves_list(ssl_ctx, QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_curves_list failed"); - return NULL; - } -#else - if(SSL_CTX_set_ciphersuites(ssl_ctx, QUIC_CIPHERS) != 1) { - char error_buffer[256]; - ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); - failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer); - return NULL; - } - - if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_groups_list failed"); - return NULL; - } -#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); - } - - if(conn->ssl_config.verifypeer) { - const char * const ssl_cafile = conn->ssl_config.CAfile; - const char * const ssl_capath = conn->ssl_config.CApath; - - if(ssl_cafile || ssl_capath) { - SSL_CTX_set_verify(ssl_ctx, 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(ssl_ctx, ssl_cafile, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - return NULL; - } - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); - } -#ifdef CURL_CA_FALLBACK - else { - /* verifying the peer without any CA certificates won't work so - use openssl's built-in default as fallback */ - SSL_CTX_set_default_verify_paths(ssl_ctx); - } -#endif - } - return ssl_ctx; -} - -static CURLcode quic_set_client_cert(struct Curl_easy *data, - struct quicsocket *qs) -{ - SSL_CTX *ssl_ctx = qs->sslctx; - const struct ssl_config_data *ssl_config; - - ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET); - DEBUGASSERT(ssl_config); - - if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob - || ssl_config->cert_type) { - return Curl_ossl_set_client_cert( - data, ssl_ctx, ssl_config->primary.clientcert, - ssl_config->primary.cert_blob, ssl_config->cert_type, - ssl_config->key, ssl_config->key_blob, - ssl_config->key_type, ssl_config->key_passwd); - } - - return CURLE_OK; -} - -/** SSL callbacks ***/ - -static CURLcode quic_init_ssl(struct quicsocket *qs, - struct Curl_easy *data, - struct connectdata *conn) -{ - 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 = qs->conn->host.name; - - (void)data; - (void)conn; - DEBUGASSERT(!qs->ssl); - qs->ssl = SSL_new(qs->sslctx); - - SSL_set_app_data(qs->ssl, &qs->conn_ref); - SSL_set_connect_state(qs->ssl); - SSL_set_quic_use_legacy_codepoint(qs->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(qs->ssl, alpn, (int)alpnlen); - - /* set SNI */ - SSL_set_tlsext_host_name(qs->ssl, hostname); - return CURLE_OK; -} -#elif defined(USE_GNUTLS) -static CURLcode quic_init_ssl(struct quicsocket *qs, - struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result; - gnutls_datum_t alpn[2]; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = qs->conn->host.name; - long * const pverifyresult = &data->set.ssl.certverifyresult; - int rc; - - DEBUGASSERT(qs->gtls == NULL); - qs->gtls = calloc(1, sizeof(*(qs->gtls))); - if(!qs->gtls) - return CURLE_OUT_OF_MEMORY; - - result = gtls_client_init(data, &conn->ssl_config, &data->set.ssl, - hostname, qs->gtls, pverifyresult); - if(result) - return result; - - gnutls_session_set_ptr(qs->gtls->session, &qs->conn_ref); - - if(ngtcp2_crypto_gnutls_configure_client_session(qs->gtls->session) != 0) { - H3BUGF(fprintf(stderr, - "ngtcp2_crypto_gnutls_configure_client_session failed\n")); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = gnutls_priority_set_direct(qs->gtls->session, QUIC_PRIORITY, NULL); - if(rc < 0) { - H3BUGF(fprintf(stderr, "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(qs->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(qs->gtls->session, alpn, 2, GNUTLS_ALPN_MANDATORY); - - return CURLE_OK; -} -#elif defined(USE_WOLFSSL) - -static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); - - if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); - return NULL; - } - - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - - if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) { - char error_buffer[256]; - ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); - failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer); - return NULL; - } - - if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) { - failf(data, "SSL_CTX_set1_groups_list failed"); - return NULL; - } - - /* 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"); - return NULL; -#endif - } - - if(conn->ssl_config.verifypeer) { - const char * const ssl_cafile = conn->ssl_config.CAfile; - const char * const ssl_capath = conn->ssl_config.CApath; - - if(ssl_cafile || ssl_capath) { - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); - /* tell wolfSSL where to find CA certificates that are used to verify - the server's certificate. */ - if(!wolfSSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - return NULL; - } - 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); - } - - return ssl_ctx; -} - -/** SSL callbacks ***/ - -static CURLcode quic_init_ssl(struct quicsocket *qs, - struct Curl_easy *data, - struct connectdata *conn) -{ - 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 = qs->conn->host.name; - - (void)data; - (void)conn; - DEBUGASSERT(!qs->ssl); - qs->ssl = SSL_new(qs->sslctx); - - wolfSSL_set_app_data(qs->ssl, &qs->conn_ref); - wolfSSL_set_connect_state(qs->ssl); - wolfSSL_set_quic_use_legacy_codepoint(qs->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(qs->ssl, alpn, (int)alpnlen); - - /* set SNI */ - wolfSSL_UseSNI(qs->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; - (void)tconn; - return 0; -} - -static void extend_stream_window(ngtcp2_conn *tconn, - struct HTTP *stream) -{ - size_t thismuch = stream->unacked_window; - ngtcp2_conn_extend_max_stream_offset(tconn, stream->stream3_id, thismuch); - ngtcp2_conn_extend_max_offset(tconn, thismuch); - stream->unacked_window = 0; -} - - -static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, - int64_t stream_id, uint64_t offset, - const uint8_t *buf, size_t buflen, - void *user_data, void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - nghttp3_ssize nconsumed; - int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; - (void)offset; - (void)stream_user_data; - - nconsumed = - nghttp3_conn_read_stream(qs->h3conn, stream_id, buf, buflen, fin); - if(nconsumed < 0) { - ngtcp2_connection_close_error_set_application_error( - &qs->last_error, nghttp3_err_infer_quic_app_error_code((int)nconsumed), - NULL, 0); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - /* number of bytes inside buflen which consists of framing overhead - * including QPACK HEADERS. In other words, it does not consume payload of - * DATA frame. */ - ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed); - ngtcp2_conn_extend_max_offset(tconn, nconsumed); - - return 0; -} - -static int -cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, - uint64_t offset, uint64_t datalen, void *user_data, - void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)stream_id; - (void)tconn; - (void)offset; - (void)datalen; - (void)stream_user_data; - - rv = nghttp3_conn_add_ack_offset(qs->h3conn, stream_id, datalen); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, - int64_t stream_id, uint64_t app_error_code, - void *user_data, void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)tconn; - (void)stream_user_data; - /* stream is closed... */ - - if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { - app_error_code = NGHTTP3_H3_NO_ERROR; - } - - rv = nghttp3_conn_close_stream(qs->h3conn, stream_id, - app_error_code); - if(rv) { - ngtcp2_connection_close_error_set_application_error( - &qs->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, - uint64_t final_size, uint64_t app_error_code, - void *user_data, void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)tconn; - (void)final_size; - (void)app_error_code; - (void)stream_user_data; - - rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id, - uint64_t app_error_code, void *user_data, - void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)tconn; - (void)app_error_code; - (void)stream_user_data; - - rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn, - uint64_t max_streams, - void *user_data) -{ - (void)tconn; - (void)max_streams; - (void)user_data; - - return 0; -} - -static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, - uint64_t max_data, void *user_data, - void *stream_user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - int rv; - (void)tconn; - (void)max_data; - (void)stream_user_data; - - rv = nghttp3_conn_unblock_stream(qs->h3conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static void cb_rand(uint8_t *dest, size_t destlen, - const ngtcp2_rand_ctx *rand_ctx) -{ - CURLcode result; - (void)rand_ctx; - - result = Curl_rand(NULL, dest, destlen); - if(result) { - /* cb_rand is only used for non-cryptographic context. If Curl_rand - failed, just fill 0 and call it *random*. */ - memset(dest, 0, destlen); - } -} - -static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid, - uint8_t *token, size_t cidlen, - void *user_data) -{ - CURLcode result; - (void)tconn; - (void)user_data; - - result = Curl_rand(NULL, cid->data, cidlen); - if(result) - return NGTCP2_ERR_CALLBACK_FAILURE; - cid->datalen = cidlen; - - result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN); - if(result) - return NGTCP2_ERR_CALLBACK_FAILURE; - - return 0; -} - -static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_crypto_level level, - void *user_data) -{ - struct quicsocket *qs = (struct quicsocket *)user_data; - (void)tconn; - - if(level != NGTCP2_CRYPTO_LEVEL_APPLICATION) { - return 0; - } - - if(init_ngh3_conn(qs) != CURLE_OK) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static ngtcp2_callbacks ng_callbacks = { - ngtcp2_crypto_client_initial_cb, - NULL, /* recv_client_initial */ - ngtcp2_crypto_recv_crypto_data_cb, - cb_handshake_completed, - NULL, /* recv_version_negotiation */ - ngtcp2_crypto_encrypt_cb, - ngtcp2_crypto_decrypt_cb, - ngtcp2_crypto_hp_mask_cb, - cb_recv_stream_data, - cb_acked_stream_data_offset, - NULL, /* stream_open */ - cb_stream_close, - NULL, /* recv_stateless_reset */ - ngtcp2_crypto_recv_retry_cb, - cb_extend_max_local_streams_bidi, - NULL, /* extend_max_local_streams_uni */ - cb_rand, - cb_get_new_connection_id, - NULL, /* remove_connection_id */ - ngtcp2_crypto_update_key_cb, /* update_key */ - NULL, /* path_validation */ - NULL, /* select_preferred_addr */ - cb_stream_reset, - NULL, /* extend_max_remote_streams_bidi */ - NULL, /* extend_max_remote_streams_uni */ - cb_extend_max_stream_data, - NULL, /* dcid_status */ - NULL, /* handshake_confirmed */ - NULL, /* recv_new_token */ - ngtcp2_crypto_delete_crypto_aead_ctx_cb, - ngtcp2_crypto_delete_crypto_cipher_ctx_cb, - NULL, /* recv_datagram */ - NULL, /* ack_datagram */ - NULL, /* lost_datagram */ - ngtcp2_crypto_get_path_challenge_data_cb, - cb_stream_stop_sending, - NULL, /* version_negotiation */ - cb_recv_rx_key, - NULL, /* recv_tx_key */ - NULL, /* early_data_rejected */ -}; - -/* - * Might be called twice for happy eyeballs. - */ -CURLcode Curl_quic_connect(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t sockfd, - int sockindex, - const struct sockaddr *addr, - socklen_t addrlen) -{ - int rc; - int rv; - CURLcode result; - ngtcp2_path path; /* TODO: this must be initialized properly */ - struct quicsocket *qs = &conn->hequic[sockindex]; - char ipbuf[40]; - int port; - int qfd; - - if(qs->conn) - Curl_quic_disconnect(data, conn, sockindex); - qs->conn = conn; - - /* extract the used address as a string */ - if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) { - char buffer[STRERROR_LEN]; - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - infof(data, "Connect socket %d over QUIC to %s:%d", - sockfd, ipbuf, port); - - qs->version = NGTCP2_PROTO_VER_MAX; -#ifdef USE_OPENSSL - qs->sslctx = quic_ssl_ctx(data); - if(!qs->sslctx) - return CURLE_QUIC_CONNECT_ERROR; - - result = quic_set_client_cert(data, qs); - if(result) - return result; -#elif defined(USE_WOLFSSL) - qs->sslctx = quic_ssl_ctx(data); - if(!qs->sslctx) - return CURLE_QUIC_CONNECT_ERROR; -#endif - - result = quic_init_ssl(qs, data, conn); - if(result) - return result; - - qs->dcid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, qs->dcid.data, NGTCP2_MAX_CIDLEN); - if(result) - return result; - - qs->scid.datalen = NGTCP2_MAX_CIDLEN; - result = Curl_rand(data, qs->scid.data, NGTCP2_MAX_CIDLEN); - if(result) - return result; - - (void)Curl_qlogdir(data, qs->scid.data, NGTCP2_MAX_CIDLEN, &qfd); - qs->qlogfd = qfd; /* -1 if failure above */ - quic_settings(qs, data->set.buffer_size); - - qs->local_addrlen = sizeof(qs->local_addr); - rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr, - &qs->local_addrlen); - if(rv == -1) - return CURLE_QUIC_CONNECT_ERROR; - - ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr, - qs->local_addrlen); - ngtcp2_addr_init(&path.remote, addr, addrlen); - - rc = ngtcp2_conn_client_new(&qs->qconn, &qs->dcid, &qs->scid, &path, - NGTCP2_PROTO_VER_V1, &ng_callbacks, - &qs->settings, &qs->transport_params, NULL, qs); - if(rc) - return CURLE_QUIC_CONNECT_ERROR; - -#ifdef USE_GNUTLS - ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->gtls->session); -#else - ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->ssl); -#endif - - ngtcp2_connection_close_error_default(&qs->last_error); - -#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG) - qs->no_gso = FALSE; -#else - qs->no_gso = TRUE; -#endif - - qs->num_blocked_pkt = 0; - qs->num_blocked_pkt_sent = 0; - memset(&qs->blocked_pkt, 0, sizeof(qs->blocked_pkt)); - - qs->pktbuflen = NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE * MAX_PKT_BURST; - qs->pktbuf = malloc(qs->pktbuflen); - if(!qs->pktbuf) { - ngtcp2_conn_del(qs->qconn); - qs->qconn = NULL; - return CURLE_OUT_OF_MEMORY; - } - - qs->conn_ref.get_conn = get_conn; - qs->conn_ref.user_data = qs; - - return CURLE_OK; -} - -/* - * Store ngtcp2 version info in this buffer. - */ -void Curl_quic_ver(char *p, size_t len) -{ - const ngtcp2_info *ng2 = ngtcp2_version(0); - const nghttp3_info *ht3 = nghttp3_version(0); - (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s", - ng2->version_str, ht3->version_str); -} - -static int ng_getsock(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t *socks) -{ - struct SingleRequest *k = &data->req; - int bitmap = GETSOCK_BLANK; - struct HTTP *stream = data->req.p.http; - struct quicsocket *qs = conn->quic; - - socks[0] = conn->sock[FIRSTSOCKET]; - - /* in an HTTP/2 connection we can basically always get a frame so we should - always be ready for one */ - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); - - /* we're still uploading or the HTTP/2 layer wants to send data */ - if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND && - (!stream->h3out || stream->h3out->used < H3_SEND_SIZE) && - ngtcp2_conn_get_cwnd_left(qs->qconn) && - ngtcp2_conn_get_max_data_left(qs->qconn) && - nghttp3_conn_is_stream_writable(qs->h3conn, stream->stream3_id)) - bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); - - return bitmap; -} - -static void qs_disconnect(struct quicsocket *qs) -{ - char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; - ngtcp2_tstamp ts; - ngtcp2_ssize rc; - - if(!qs->conn) /* already closed */ - return; - ts = timestamp(); - rc = ngtcp2_conn_write_connection_close(qs->qconn, NULL, /* path */ - NULL, /* pkt_info */ - (uint8_t *)buffer, sizeof(buffer), - &qs->last_error, ts); - if(rc > 0) { - while((send(qs->conn->sock[FIRSTSOCKET], buffer, rc, 0) == -1) && - SOCKERRNO == EINTR); - } - - qs->conn = NULL; - if(qs->qlogfd != -1) { - close(qs->qlogfd); - qs->qlogfd = -1; - } -#ifdef USE_OPENSSL - if(qs->ssl) - SSL_free(qs->ssl); - qs->ssl = NULL; - SSL_CTX_free(qs->sslctx); -#elif defined(USE_GNUTLS) - if(qs->gtls) { - if(qs->gtls->cred) - gnutls_certificate_free_credentials(qs->gtls->cred); - if(qs->gtls->session) - gnutls_deinit(qs->gtls->session); - free(qs->gtls); - qs->gtls = NULL; - } -#elif defined(USE_WOLFSSL) - if(qs->ssl) - wolfSSL_free(qs->ssl); - qs->ssl = NULL; - wolfSSL_CTX_free(qs->sslctx); -#endif - free(qs->pktbuf); - nghttp3_conn_del(qs->h3conn); - ngtcp2_conn_del(qs->qconn); -} - -void Curl_quic_disconnect(struct Curl_easy *data, - struct connectdata *conn, - int tempindex) -{ - (void)data; - if(conn->transport == TRNSPRT_QUIC) - qs_disconnect(&conn->hequic[tempindex]); -} - -static CURLcode ng_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) -{ - (void)dead_connection; - Curl_quic_disconnect(data, conn, 0); - Curl_quic_disconnect(data, conn, 1); - return CURLE_OK; -} - -static unsigned int ng_conncheck(struct Curl_easy *data, - struct connectdata *conn, - unsigned int checks_to_perform) -{ - (void)data; - (void)conn; - (void)checks_to_perform; - return CONNRESULT_NONE; -} - -static const struct Curl_handler Curl_handler_http3 = { - "HTTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ng_getsock, /* proto_getsock */ - ng_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ng_getsock, /* perform_getsock */ - ng_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ng_conncheck, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTPS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_STREAM /* flags */ -}; - -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_easy *data = stream_user_data; - struct HTTP *stream = data->req.p.http; - (void)conn; - (void)stream_id; - (void)app_error_code; - (void)user_data; - H3BUGF(infof(data, "cb_h3_stream_close CALLED")); - - stream->closed = TRUE; - stream->error3 = app_error_code; - Curl_expire(data, 0, EXPIRE_QUIC); - /* make sure that ngh3_stream_recv is called again to complete the transfer - even if there are no more packets to be received from the server. */ - data->state.drain = 1; - return 0; -} - -/* - * write_data() copies data to the stream's receive buffer. If not enough - * space is available in the receive buffer, it copies the rest to the - * stream's overflow buffer. - */ -static CURLcode write_data(struct HTTP *stream, const void *mem, size_t memlen) -{ - CURLcode result = CURLE_OK; - const char *buf = mem; - size_t ncopy = memlen; - /* copy as much as possible to the receive buffer */ - if(stream->len) { - size_t len = CURLMIN(ncopy, stream->len); - memcpy(stream->mem, buf, len); - stream->len -= len; - stream->memlen += len; - stream->mem += len; - buf += len; - ncopy -= len; - } - /* copy the rest to the overflow buffer */ - if(ncopy) - result = Curl_dyn_addn(&stream->overflow, buf, ncopy); - return result; -} - -static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream_id, - const uint8_t *buf, size_t buflen, - void *user_data, void *stream_user_data) -{ - struct Curl_easy *data = stream_user_data; - struct HTTP *stream = data->req.p.http; - CURLcode result = CURLE_OK; - (void)conn; - - result = write_data(stream, buf, buflen); - if(result) { - return -1; - } - stream->unacked_window += buflen; - (void)stream_id; - (void)user_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 quicsocket *qs = user_data; - (void)conn; - (void)stream_user_data; - (void)stream_id; - - ngtcp2_conn_extend_max_stream_offset(qs->qconn, stream_id, consumed); - ngtcp2_conn_extend_max_offset(qs->qconn, consumed); - return 0; -} - -/* Decode HTTP status code. Returns -1 if no valid status code was - decoded. (duplicate from http2.c) */ -static int decode_status_code(const uint8_t *value, size_t len) -{ - int i; - int res; - - if(len != 3) { - return -1; - } - - res = 0; - - for(i = 0; i < 3; ++i) { - char c = value[i]; - - if(c < '0' || c > '9') { - return -1; - } - - res *= 10; - res += c - '0'; - } - - return res; -} - -static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, - int fin, void *user_data, void *stream_user_data) -{ - struct Curl_easy *data = stream_user_data; - struct HTTP *stream = data->req.p.http; - CURLcode result = CURLE_OK; - (void)conn; - (void)stream_id; - (void)user_data; - (void)fin; - - /* add a CRLF only if we've received some headers */ - if(stream->firstheader) { - result = write_data(stream, "\r\n", 2); - if(result) { - return -1; - } - } - - if(stream->status_code / 100 != 1) { - stream->bodystarted = TRUE; - } - 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) -{ - nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); - nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); - struct Curl_easy *data = stream_user_data; - struct HTTP *stream = data->req.p.http; - CURLcode result = CURLE_OK; - (void)conn; - (void)stream_id; - (void)token; - (void)flags; - (void)user_data; - - if(token == NGHTTP3_QPACK_TOKEN__STATUS) { - char line[14]; /* status line is always 13 characters long */ - size_t ncopy; - stream->status_code = decode_status_code(h3val.base, h3val.len); - DEBUGASSERT(stream->status_code != -1); - ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", - stream->status_code); - result = write_data(stream, line, ncopy); - if(result) { - return -1; - } - } - else { - /* store as an HTTP1-style header */ - result = write_data(stream, h3name.base, h3name.len); - if(result) { - return -1; - } - result = write_data(stream, ": ", 2); - if(result) { - return -1; - } - result = write_data(stream, h3val.base, h3val.len); - if(result) { - return -1; - } - result = write_data(stream, "\r\n", 2); - if(result) { - return -1; - } - } - - stream->firstheader = TRUE; - 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 quicsocket *qs = user_data; - int rv; - (void)conn; - (void)stream_user_data; - - rv = ngtcp2_conn_shutdown_stream_read(qs->qconn, stream_id, app_error_code); - if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - 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 quicsocket *qs = user_data; - int rv; - (void)conn; - (void)stream_user_data; - - rv = ngtcp2_conn_shutdown_stream_write(qs->qconn, stream_id, app_error_code); - if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - - return 0; -} - -static nghttp3_callbacks ngh3_callbacks = { - cb_h3_acked_stream_data, /* 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 */ -}; - -static int init_ngh3_conn(struct quicsocket *qs) -{ - CURLcode result; - int rc; - int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id; - - if(ngtcp2_conn_get_max_local_streams_uni(qs->qconn) < 3) { - return CURLE_QUIC_CONNECT_ERROR; - } - - nghttp3_settings_default(&qs->h3settings); - - rc = nghttp3_conn_client_new(&qs->h3conn, - &ngh3_callbacks, - &qs->h3settings, - nghttp3_mem_default(), - qs); - if(rc) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - - rc = ngtcp2_conn_open_uni_stream(qs->qconn, &ctrl_stream_id, NULL); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - rc = nghttp3_conn_bind_control_stream(qs->h3conn, ctrl_stream_id); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_enc_stream_id, NULL); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_dec_stream_id, NULL); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - rc = nghttp3_conn_bind_qpack_streams(qs->h3conn, qpack_enc_stream_id, - qpack_dec_stream_id); - if(rc) { - result = CURLE_QUIC_CONNECT_ERROR; - goto fail; - } - - return CURLE_OK; - fail: - - return result; -} - -static Curl_recv ngh3_stream_recv; -static Curl_send ngh3_stream_send; - -static size_t drain_overflow_buffer(struct HTTP *stream) -{ - size_t overlen = Curl_dyn_len(&stream->overflow); - size_t ncopy = CURLMIN(overlen, stream->len); - if(ncopy > 0) { - memcpy(stream->mem, Curl_dyn_ptr(&stream->overflow), ncopy); - stream->len -= ncopy; - stream->mem += ncopy; - stream->memlen += ncopy; - if(ncopy != overlen) - /* make the buffer only keep the tail */ - (void)Curl_dyn_tail(&stream->overflow, overlen - ncopy); - else - Curl_dyn_reset(&stream->overflow); - } - return ncopy; -} - -/* incoming data frames on the h3 stream */ -static ssize_t ngh3_stream_recv(struct Curl_easy *data, - int sockindex, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[sockindex]; - struct HTTP *stream = data->req.p.http; - struct quicsocket *qs = conn->quic; - - if(!stream->memlen) { - /* remember where to store incoming data for this stream and how big the - buffer is */ - stream->mem = buf; - stream->len = buffersize; - } - /* else, there's data in the buffer already */ - - /* if there's data in the overflow buffer from a previous call, copy as much - as possible to the receive buffer before receiving more */ - drain_overflow_buffer(stream); - - if(ng_process_ingress(data, sockfd, qs)) { - *curlcode = CURLE_RECV_ERROR; - return -1; - } - if(ng_flush_egress(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - if(stream->memlen) { - ssize_t memlen = stream->memlen; - /* data arrived */ - *curlcode = CURLE_OK; - /* reset to allow more data to come */ - stream->memlen = 0; - stream->mem = buf; - stream->len = buffersize; - /* extend the stream window with the data we're consuming and send out - any additional packets to tell the server that we can receive more */ - extend_stream_window(qs->qconn, stream); - if(ng_flush_egress(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - return memlen; - } - - if(stream->closed) { - if(stream->error3 != NGHTTP3_H3_NO_ERROR) { - failf(data, - "HTTP/3 stream %" PRId64 " was not closed cleanly: (err %" PRIu64 - ")", - stream->stream3_id, stream->error3); - *curlcode = CURLE_HTTP3; - return -1; - } - - if(!stream->bodystarted) { - failf(data, - "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting" - " all response header fields, treated as error", - stream->stream3_id); - *curlcode = CURLE_HTTP3; - return -1; - } - - *curlcode = CURLE_OK; - return 0; - } - - infof(data, "ngh3_stream_recv returns 0 bytes and EAGAIN"); - *curlcode = CURLE_AGAIN; - return -1; -} - -/* this amount of data has now been acked on this stream */ -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_easy *data = stream_user_data; - struct HTTP *stream = data->req.p.http; - (void)user_data; - - if(!data->set.postfields) { - stream->h3out->used -= datalen; - H3BUGF(infof(data, - "cb_h3_acked_stream_data, %zd bytes, %zd left unacked", - datalen, stream->h3out->used)); - DEBUGASSERT(stream->h3out->used < H3_SEND_SIZE); - - if(stream->h3out->used == 0) { - int rv = nghttp3_conn_resume_stream(conn, stream_id); - if(rv) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - } - } - return 0; -} - -static nghttp3_ssize cb_h3_readfunction(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_easy *data = stream_user_data; - size_t nread; - struct HTTP *stream = data->req.p.http; - (void)conn; - (void)stream_id; - (void)user_data; - (void)veccnt; - - if(data->set.postfields) { - vec[0].base = data->set.postfields; - vec[0].len = data->state.infilesize; - *pflags = NGHTTP3_DATA_FLAG_EOF; - return 1; - } - - if(stream->upload_len && H3_SEND_SIZE <= stream->h3out->used) { - return NGHTTP3_ERR_WOULDBLOCK; - } - - nread = CURLMIN(stream->upload_len, H3_SEND_SIZE - stream->h3out->used); - if(nread > 0) { - /* nghttp3 wants us to hold on to the data until it tells us it is okay to - delete it. Append the data at the end of the h3out buffer. Since we can - only return consecutive data, copy the amount that fits and the next - part comes in next invoke. */ - struct h3out *out = stream->h3out; - if(nread + out->windex > H3_SEND_SIZE) - nread = H3_SEND_SIZE - out->windex; - - memcpy(&out->buf[out->windex], stream->upload_mem, nread); - - /* that's the chunk we return to nghttp3 */ - vec[0].base = &out->buf[out->windex]; - vec[0].len = nread; - - out->windex += nread; - out->used += nread; - - if(out->windex == H3_SEND_SIZE) - out->windex = 0; /* wrap */ - stream->upload_mem += nread; - stream->upload_len -= nread; - if(data->state.infilesize != -1) { - stream->upload_left -= nread; - if(!stream->upload_left) - *pflags = NGHTTP3_DATA_FLAG_EOF; - } - H3BUGF(infof(data, "cb_h3_readfunction %zd bytes%s (at %zd unacked)", - nread, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"", - out->used)); - } - if(stream->upload_done && !stream->upload_len && - (stream->upload_left <= 0)) { - H3BUGF(infof(data, "cb_h3_readfunction sets EOF")); - *pflags = NGHTTP3_DATA_FLAG_EOF; - return nread ? 1 : 0; - } - else if(!nread) { - return NGHTTP3_ERR_WOULDBLOCK; - } - return 1; -} - -/* Index where :authority header field will appear in request header - field list. */ -#define AUTHORITY_DST_IDX 3 - -static CURLcode http_request(struct Curl_easy *data, const void *mem, - size_t len) -{ - struct connectdata *conn = data->conn; - struct HTTP *stream = data->req.p.http; - size_t nheader; - struct quicsocket *qs = conn->quic; - CURLcode result = CURLE_OK; - nghttp3_nv *nva = NULL; - int64_t stream3_id; - int rc; - struct h3out *h3out = NULL; - struct h2h3req *hreq = NULL; - - rc = ngtcp2_conn_open_bidi_stream(qs->qconn, &stream3_id, NULL); - if(rc) { - failf(data, "can get bidi streams"); - result = CURLE_SEND_ERROR; - goto fail; - } - - stream->stream3_id = stream3_id; - stream->h3req = TRUE; /* senf off! */ - Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE); - - result = Curl_pseudo_headers(data, mem, len, &hreq); - if(result) - goto fail; - nheader = hreq->entries; - - nva = malloc(sizeof(nghttp3_nv) * nheader); - if(!nva) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - else { - unsigned int i; - for(i = 0; i < nheader; i++) { - nva[i].name = (unsigned char *)hreq->header[i].name; - nva[i].namelen = hreq->header[i].namelen; - nva[i].value = (unsigned char *)hreq->header[i].value; - nva[i].valuelen = hreq->header[i].valuelen; - nva[i].flags = NGHTTP3_NV_FLAG_NONE; - } - } - - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - case HTTPREQ_PUT: { - nghttp3_data_reader data_reader; - 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, but not zero */ - - data_reader.read_data = cb_h3_readfunction; - - h3out = calloc(sizeof(struct h3out), 1); - if(!h3out) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - stream->h3out = h3out; - - rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id, - nva, nheader, &data_reader, data); - if(rc) { - result = CURLE_SEND_ERROR; - goto fail; - } - break; - } - default: - stream->upload_left = 0; /* nothing left to send */ - rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id, - nva, nheader, NULL, data); - if(rc) { - result = CURLE_SEND_ERROR; - goto fail; - } - break; - } - - Curl_safefree(nva); - - infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)", - stream3_id, (void *)data); - - Curl_pseudo_free(hreq); - return CURLE_OK; - -fail: - free(nva); - Curl_pseudo_free(hreq); - return result; -} -static ssize_t ngh3_stream_send(struct Curl_easy *data, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - ssize_t sent = 0; - struct connectdata *conn = data->conn; - struct quicsocket *qs = conn->quic; - curl_socket_t sockfd = conn->sock[sockindex]; - struct HTTP *stream = data->req.p.http; - - if(stream->closed) { - *curlcode = CURLE_HTTP3; - return -1; - } - - if(!stream->h3req) { - CURLcode result = http_request(data, mem, len); - if(result) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - /* Assume that mem of length len only includes HTTP/1.1 style - header fields. In other words, it does not contain request - body. */ - sent = len; - } - else { - H3BUGF(infof(data, "ngh3_stream_send() wants to send %zd bytes", - len)); - if(!stream->upload_len) { - stream->upload_mem = mem; - stream->upload_len = len; - (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id); - } - else { - *curlcode = CURLE_AGAIN; - return -1; - } - } - - if(ng_flush_egress(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - /* Reset post upload buffer after resumed. */ - if(stream->upload_mem) { - if(data->set.postfields) { - sent = len; - } - else { - sent = len - stream->upload_len; - } - - stream->upload_mem = NULL; - stream->upload_len = 0; - - if(sent == 0) { - *curlcode = CURLE_AGAIN; - return -1; - } - } - - *curlcode = CURLE_OK; - return sent; -} - -static CURLcode ng_has_connected(struct Curl_easy *data, - struct connectdata *conn, int tempindex) -{ - CURLcode result = CURLE_OK; - const char *hostname, *disp_hostname; - int port; - char *snihost; - - Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &disp_hostname, &port); - snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) - return CURLE_PEER_FAILED_VERIFICATION; - - conn->recv[FIRSTSOCKET] = ngh3_stream_recv; - conn->send[FIRSTSOCKET] = ngh3_stream_send; - conn->handler = &Curl_handler_http3; - conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->httpversion = 30; - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - conn->quic = &conn->hequic[tempindex]; - - if(conn->ssl_config.verifyhost) { -#ifdef USE_OPENSSL - X509 *server_cert; - server_cert = SSL_get_peer_certificate(conn->quic->ssl); - if(!server_cert) { - return CURLE_PEER_FAILED_VERIFICATION; - } - result = Curl_ossl_verifyhost(data, conn, server_cert); - X509_free(server_cert); - if(result) - return result; -#elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(data, conn->quic->gtls->session, - &conn->ssl_config, &data->set.ssl, - hostname, disp_hostname, - data->set.str[STRING_SSL_PINNEDPUBLICKEY]); - if(result) - return result; -#elif defined(USE_WOLFSSL) - if(wolfSSL_check_domain_name(conn->quic->ssl, snihost) == SSL_FAILURE) - return CURLE_PEER_FAILED_VERIFICATION; -#endif - infof(data, "Verified certificate just fine"); - } - else - infof(data, "Skipped certificate verification"); -#ifdef USE_OPENSSL - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, conn->quic->ssl); -#endif - return result; -} - -/* - * There can be multiple connection attempts going on in parallel. - */ -CURLcode Curl_quic_is_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *done) -{ - CURLcode result; - struct quicsocket *qs = &conn->hequic[sockindex]; - curl_socket_t sockfd = conn->tempsock[sockindex]; - - result = ng_process_ingress(data, sockfd, qs); - if(result) - goto error; - - result = ng_flush_egress(data, sockfd, qs); - if(result) - goto error; - - if(ngtcp2_conn_get_handshake_completed(qs->qconn)) { - result = ng_has_connected(data, conn, sockindex); - if(!result) - *done = TRUE; - } - - return result; - error: - (void)qs_disconnect(qs); - return result; - -} - -static CURLcode ng_process_ingress(struct Curl_easy *data, - curl_socket_t sockfd, - struct quicsocket *qs) -{ - ssize_t recvd; - int rv; - uint8_t buf[65536]; - size_t bufsize = sizeof(buf); - struct sockaddr_storage remote_addr; - socklen_t remote_addrlen; - ngtcp2_path path; - ngtcp2_tstamp ts = timestamp(); - ngtcp2_pkt_info pi = { 0 }; - - for(;;) { - remote_addrlen = sizeof(remote_addr); - while((recvd = recvfrom(sockfd, (char *)buf, bufsize, 0, - (struct sockaddr *)&remote_addr, - &remote_addrlen)) == -1 && - SOCKERRNO == EINTR) - ; - if(recvd == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) - break; - - failf(data, "ngtcp2: recvfrom() unexpectedly returned %zd", recvd); - return CURLE_RECV_ERROR; - } - - ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr, - qs->local_addrlen); - ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr, - remote_addrlen); - - rv = ngtcp2_conn_read_pkt(qs->qconn, &path, &pi, buf, recvd, ts); - if(rv) { - if(!qs->last_error.error_code) { - if(rv == NGTCP2_ERR_CRYPTO) { - ngtcp2_connection_close_error_set_transport_error_tls_alert( - &qs->last_error, ngtcp2_conn_get_tls_alert(qs->qconn), NULL, 0); - } - else { - ngtcp2_connection_close_error_set_transport_error_liberr( - &qs->last_error, rv, NULL, 0); - } - } - - if(rv == NGTCP2_ERR_CRYPTO) - /* this is a "TLS problem", but a failed certificate verification - is a common reason for this */ - return CURLE_PEER_FAILED_VERIFICATION; - return CURLE_RECV_ERROR; - } - } - - return CURLE_OK; -} - -static CURLcode do_sendmsg(size_t *sent, struct Curl_easy *data, int sockfd, - struct quicsocket *qs, const uint8_t *pkt, - size_t pktlen, size_t gsolen); - -static CURLcode send_packet_no_gso(size_t *psent, struct Curl_easy *data, - int sockfd, struct quicsocket *qs, - const uint8_t *pkt, size_t pktlen, - size_t gsolen) -{ - const uint8_t *p, *end = pkt + pktlen; - size_t sent; - - *psent = 0; - - for(p = pkt; p < end; p += gsolen) { - size_t len = CURLMIN(gsolen, (size_t)(end - p)); - CURLcode curlcode = do_sendmsg(&sent, data, sockfd, qs, p, len, len); - if(curlcode != CURLE_OK) { - return curlcode; - } - *psent += sent; - } - - return CURLE_OK; -} - -static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd, - struct quicsocket *qs, const uint8_t *pkt, - size_t pktlen, size_t gsolen) -{ -#ifdef HAVE_SENDMSG - struct iovec msg_iov; - struct msghdr msg = {0}; - ssize_t sent; -#if defined(__linux__) && defined(UDP_SEGMENT) - uint8_t msg_ctrl[32]; - struct cmsghdr *cm; -#endif - - *psent = 0; - msg_iov.iov_base = (uint8_t *)pkt; - msg_iov.iov_len = pktlen; - msg.msg_iov = &msg_iov; - msg.msg_iovlen = 1; - -#if defined(__linux__) && defined(UDP_SEGMENT) - if(pktlen > gsolen) { - /* Only set this, when we need it. macOS, for example, - * does not seem to like a msg_control of length 0. */ - msg.msg_control = msg_ctrl; - assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t))); - msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); - cm = CMSG_FIRSTHDR(&msg); - cm->cmsg_level = SOL_UDP; - cm->cmsg_type = UDP_SEGMENT; - cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); - *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff; - } -#endif - - - while((sent = sendmsg(sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR) - ; - - if(sent == -1) { - switch(SOCKERRNO) { - case EAGAIN: -#if EAGAIN != EWOULDBLOCK - case EWOULDBLOCK: -#endif - return CURLE_AGAIN; - case EMSGSIZE: - /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */ - break; - case EIO: - if(pktlen > gsolen) { - /* GSO failure */ - failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent, - SOCKERRNO); - qs->no_gso = TRUE; - return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen, - gsolen); - } - /* FALLTHROUGH */ - default: - failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO); - return CURLE_SEND_ERROR; - } - } - else { - assert(pktlen == (size_t)sent); - } -#else - ssize_t sent; - (void)qs; - (void)gsolen; - - *psent = 0; - - while((sent = send(sockfd, (const char *)pkt, pktlen, 0)) == -1 && - SOCKERRNO == EINTR) - ; - - if(sent == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { - return CURLE_AGAIN; - } - else { - failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO); - if(SOCKERRNO != EMSGSIZE) { - return CURLE_SEND_ERROR; - } - /* UDP datagram is too large; caused by PMTUD. Just let it be - lost. */ - } - } -#endif - - *psent = pktlen; - - return CURLE_OK; -} - -static CURLcode send_packet(size_t *psent, struct Curl_easy *data, int sockfd, - struct quicsocket *qs, const uint8_t *pkt, - size_t pktlen, size_t gsolen) -{ - if(qs->no_gso && pktlen > gsolen) { - return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen, gsolen); - } - - return do_sendmsg(psent, data, sockfd, qs, pkt, pktlen, gsolen); -} - -static void push_blocked_pkt(struct quicsocket *qs, const uint8_t *pkt, - size_t pktlen, size_t gsolen) -{ - struct blocked_pkt *blkpkt; - - assert(qs->num_blocked_pkt < - sizeof(qs->blocked_pkt) / sizeof(qs->blocked_pkt[0])); - - blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt++]; - - blkpkt->pkt = pkt; - blkpkt->pktlen = pktlen; - blkpkt->gsolen = gsolen; -} - -static CURLcode send_blocked_pkt(struct Curl_easy *data, int sockfd, - struct quicsocket *qs) -{ - size_t sent; - CURLcode curlcode; - struct blocked_pkt *blkpkt; - - for(; qs->num_blocked_pkt_sent < qs->num_blocked_pkt; - ++qs->num_blocked_pkt_sent) { - blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt_sent]; - curlcode = send_packet(&sent, data, sockfd, qs, blkpkt->pkt, - blkpkt->pktlen, blkpkt->gsolen); - - if(curlcode) { - if(curlcode == CURLE_AGAIN) { - blkpkt->pkt += sent; - blkpkt->pktlen -= sent; - } - return curlcode; - } - } - - qs->num_blocked_pkt = 0; - qs->num_blocked_pkt_sent = 0; - - return CURLE_OK; -} - -static CURLcode ng_flush_egress(struct Curl_easy *data, - int sockfd, - struct quicsocket *qs) -{ - int rv; - size_t sent; - ngtcp2_ssize outlen; - uint8_t *outpos = qs->pktbuf; - size_t max_udp_payload_size = - ngtcp2_conn_get_max_tx_udp_payload_size(qs->qconn); - size_t path_max_udp_payload_size = - ngtcp2_conn_get_path_max_tx_udp_payload_size(qs->qconn); - size_t max_pktcnt = - CURLMIN(MAX_PKT_BURST, qs->pktbuflen / max_udp_payload_size); - size_t pktcnt = 0; - size_t gsolen; - ngtcp2_path_storage ps; - ngtcp2_tstamp ts = timestamp(); - ngtcp2_tstamp expiry; - ngtcp2_duration timeout; - int64_t stream_id; - nghttp3_ssize veccnt; - int fin; - nghttp3_vec vec[16]; - ngtcp2_ssize ndatalen; - uint32_t flags; - CURLcode curlcode; - - rv = ngtcp2_conn_handle_expiry(qs->qconn, ts); - if(rv) { - failf(data, "ngtcp2_conn_handle_expiry returned error: %s", - ngtcp2_strerror(rv)); - ngtcp2_connection_close_error_set_transport_error_liberr(&qs->last_error, - rv, NULL, 0); - return CURLE_SEND_ERROR; - } - - if(qs->num_blocked_pkt) { - curlcode = send_blocked_pkt(data, sockfd, qs); - if(curlcode) { - if(curlcode == CURLE_AGAIN) { - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return curlcode; - } - } - - ngtcp2_path_storage_zero(&ps); - - for(;;) { - veccnt = 0; - stream_id = -1; - fin = 0; - - if(qs->h3conn && ngtcp2_conn_get_max_data_left(qs->qconn)) { - veccnt = nghttp3_conn_writev_stream(qs->h3conn, &stream_id, &fin, vec, - sizeof(vec) / sizeof(vec[0])); - if(veccnt < 0) { - failf(data, "nghttp3_conn_writev_stream returned error: %s", - nghttp3_strerror((int)veccnt)); - ngtcp2_connection_close_error_set_application_error( - &qs->last_error, - nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0); - return CURLE_SEND_ERROR; - } - } - - flags = NGTCP2_WRITE_STREAM_FLAG_MORE | - (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0); - outlen = ngtcp2_conn_writev_stream(qs->qconn, &ps.path, NULL, outpos, - max_udp_payload_size, - &ndatalen, flags, stream_id, - (const ngtcp2_vec *)vec, veccnt, ts); - if(outlen == 0) { - if(outpos != qs->pktbuf) { - curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf, - outpos - qs->pktbuf, gsolen); - if(curlcode) { - if(curlcode == CURLE_AGAIN) { - push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent, - gsolen); - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return curlcode; - } - } - - break; - } - if(outlen < 0) { - switch(outlen) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: - assert(ndatalen == -1); - nghttp3_conn_block_stream(qs->h3conn, stream_id); - continue; - case NGTCP2_ERR_STREAM_SHUT_WR: - assert(ndatalen == -1); - nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id); - continue; - case NGTCP2_ERR_WRITE_MORE: - assert(ndatalen >= 0); - rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen); - if(rv) { - failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", - nghttp3_strerror(rv)); - return CURLE_SEND_ERROR; - } - continue; - default: - assert(ndatalen == -1); - failf(data, "ngtcp2_conn_writev_stream returned error: %s", - ngtcp2_strerror((int)outlen)); - ngtcp2_connection_close_error_set_transport_error_liberr( - &qs->last_error, (int)outlen, NULL, 0); - return CURLE_SEND_ERROR; - } - } - else if(ndatalen >= 0) { - rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen); - if(rv) { - failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", - nghttp3_strerror(rv)); - return CURLE_SEND_ERROR; - } - } - - outpos += outlen; - - if(pktcnt == 0) { - gsolen = outlen; - } - else if((size_t)outlen > gsolen || - (gsolen > path_max_udp_payload_size && - (size_t)outlen != gsolen)) { - /* Packet larger than path_max_udp_payload_size is PMTUD probe - packet and it might not be sent because of EMSGSIZE. Send - them separately to minimize the loss. */ - curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf, - outpos - outlen - qs->pktbuf, gsolen); - if(curlcode) { - if(curlcode == CURLE_AGAIN) { - push_blocked_pkt(qs, qs->pktbuf + sent, - outpos - outlen - qs->pktbuf - sent, gsolen); - push_blocked_pkt(qs, outpos - outlen, outlen, outlen); - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return curlcode; - } - curlcode = send_packet(&sent, data, sockfd, qs, outpos - outlen, outlen, - outlen); - if(curlcode) { - if(curlcode == CURLE_AGAIN) { - assert(0 == sent); - push_blocked_pkt(qs, outpos - outlen, outlen, outlen); - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return curlcode; - } - - pktcnt = 0; - outpos = qs->pktbuf; - continue; - } - - if(++pktcnt >= max_pktcnt || (size_t)outlen < gsolen) { - curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf, - outpos - qs->pktbuf, gsolen); - if(curlcode) { - if(curlcode == CURLE_AGAIN) { - push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent, - gsolen); - Curl_expire(data, 1, EXPIRE_QUIC); - return CURLE_OK; - } - return curlcode; - } - - pktcnt = 0; - outpos = qs->pktbuf; - } - } - - expiry = ngtcp2_conn_get_expiry(qs->qconn); - if(expiry != UINT64_MAX) { - if(expiry <= ts) { - timeout = 0; - } - else { - timeout = expiry - ts; - if(timeout % NGTCP2_MILLISECONDS) { - timeout += NGTCP2_MILLISECONDS; - } - } - Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC); - } - - return CURLE_OK; -} - -/* - * Called from transfer.c:done_sending when we stop HTTP/3 uploading. - */ -CURLcode Curl_quic_done_sending(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - DEBUGASSERT(conn); - if(conn->handler == &Curl_handler_http3) { - /* only for HTTP/3 transfers */ - struct HTTP *stream = data->req.p.http; - struct quicsocket *qs = conn->quic; - stream->upload_done = TRUE; - (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id); - } - - return CURLE_OK; -} - -/* - * Called from http.c:Curl_http_done when a request completes. - */ -void Curl_quic_done(struct Curl_easy *data, bool premature) -{ - (void)premature; - if(data->conn->handler == &Curl_handler_http3) { - /* only for HTTP/3 transfers */ - struct HTTP *stream = data->req.p.http; - Curl_dyn_free(&stream->overflow); - free(stream->h3out); - } -} - -/* - * Called from transfer.c:data_pending to know if we should keep looping - * to receive more data from the connection. - */ -bool Curl_quic_data_pending(const struct Curl_easy *data) -{ - /* We may have received more data than we're able to hold in the receive - buffer and allocated an overflow buffer. Since it's possible that - there's no more data coming on the socket, we need to keep reading - until the overflow buffer is empty. */ - const struct HTTP *stream = data->req.p.http; - return Curl_dyn_len(&stream->overflow) > 0; -} - -/* - * Called from transfer.c:Curl_readwrite when neither HTTP level read - * nor write is performed. It is a good place to handle timer expiry - * for QUIC transport. - */ -CURLcode Curl_quic_idle(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - struct quicsocket *qs = conn->quic; - - if(ngtcp2_conn_get_expiry(qs->qconn) > timestamp()) { - return CURLE_OK; - } - - if(ng_flush_egress(data, sockfd, qs)) { - return CURLE_SEND_ERROR; - } - - return CURLE_OK; -} - -#endif diff --git a/Utilities/cmcurl/lib/vquic/quiche.c b/Utilities/cmcurl/lib/vquic/quiche.c deleted file mode 100644 index 2b9a0410f1..0000000000 --- a/Utilities/cmcurl/lib/vquic/quiche.c +++ /dev/null @@ -1,892 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, 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" - -#ifdef USE_QUICHE -#include <quiche.h> -#include <openssl/err.h> -#include <openssl/ssl.h> -#include "urldata.h" -#include "sendf.h" -#include "strdup.h" -#include "rand.h" -#include "quic.h" -#include "strcase.h" -#include "multiif.h" -#include "connect.h" -#include "strerror.h" -#include "vquic.h" -#include "transfer.h" -#include "h2h3.h" -#include "vtls/openssl.h" -#include "vtls/keylog.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -#define DEBUG_HTTP3 -/* #define DEBUG_QUICHE */ -#ifdef DEBUG_HTTP3 -#define H3BUGF(x) x -#else -#define H3BUGF(x) do { } while(0) -#endif - -#define QUIC_MAX_STREAMS (256*1024) -#define QUIC_MAX_DATA (1*1024*1024) -#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */ - -static CURLcode process_ingress(struct Curl_easy *data, - curl_socket_t sockfd, - struct quicsocket *qs); - -static CURLcode flush_egress(struct Curl_easy *data, curl_socket_t sockfd, - struct quicsocket *qs); - -static CURLcode http_request(struct Curl_easy *data, const void *mem, - size_t len); -static Curl_recv h3_stream_recv; -static Curl_send h3_stream_send; - -static int quiche_getsock(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks) -{ - struct SingleRequest *k = &data->req; - int bitmap = GETSOCK_BLANK; - - socks[0] = conn->sock[FIRSTSOCKET]; - - /* in an HTTP/2 connection we can basically always get a frame so we should - always be ready for one */ - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); - - /* we're still uploading or the HTTP/2 layer wants to send data */ - if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) - bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); - - return bitmap; -} - -static CURLcode qs_disconnect(struct Curl_easy *data, - struct quicsocket *qs) -{ - DEBUGASSERT(qs); - if(qs->conn) { - (void)quiche_conn_close(qs->conn, TRUE, 0, NULL, 0); - /* flushing the egress is not a failsafe way to deliver all the - outstanding packets, but we also don't want to get stuck here... */ - (void)flush_egress(data, qs->sockfd, qs); - quiche_conn_free(qs->conn); - qs->conn = NULL; - } - if(qs->h3config) - quiche_h3_config_free(qs->h3config); - if(qs->h3c) - quiche_h3_conn_free(qs->h3c); - if(qs->cfg) { - quiche_config_free(qs->cfg); - qs->cfg = NULL; - } - return CURLE_OK; -} - -static CURLcode quiche_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) -{ - struct quicsocket *qs = conn->quic; - (void)dead_connection; - return qs_disconnect(data, qs); -} - -void Curl_quic_disconnect(struct Curl_easy *data, - struct connectdata *conn, - int tempindex) -{ - if(conn->transport == TRNSPRT_QUIC) - qs_disconnect(data, &conn->hequic[tempindex]); -} - -static unsigned int quiche_conncheck(struct Curl_easy *data, - struct connectdata *conn, - unsigned int checks_to_perform) -{ - (void)data; - (void)conn; - (void)checks_to_perform; - return CONNRESULT_NONE; -} - -static CURLcode quiche_do(struct Curl_easy *data, bool *done) -{ - struct HTTP *stream = data->req.p.http; - stream->h3req = FALSE; /* not sent */ - return Curl_http(data, done); -} - -static const struct Curl_handler Curl_handler_http3 = { - "HTTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - quiche_do, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - quiche_getsock, /* proto_getsock */ - quiche_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - quiche_getsock, /* perform_getsock */ - quiche_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - quiche_conncheck, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTPS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_STREAM /* flags */ -}; - -#ifdef DEBUG_QUICHE -static void quiche_debug_log(const char *line, void *argp) -{ - (void)argp; - fprintf(stderr, "%s\n", line); -} -#endif - -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} - -static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data) -{ - SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); - - SSL_CTX_set_alpn_protos(ssl_ctx, - (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL, - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1); - - SSL_CTX_set_default_verify_paths(ssl_ctx); - - /* 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); - } - - { - struct connectdata *conn = data->conn; - if(conn->ssl_config.verifypeer) { - const char * const ssl_cafile = conn->ssl_config.CAfile; - const char * const ssl_capath = conn->ssl_config.CApath; - if(ssl_cafile || ssl_capath) { - SSL_CTX_set_verify(ssl_ctx, 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(ssl_ctx, ssl_cafile, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - return NULL; - } - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); - } -#ifdef CURL_CA_FALLBACK - else { - /* verifying the peer without any CA certificates won't work so - use openssl's built-in default as fallback */ - SSL_CTX_set_default_verify_paths(ssl_ctx); - } -#endif - } - } - return ssl_ctx; -} - -static int quic_init_ssl(struct quicsocket *qs, struct connectdata *conn) -{ - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = conn->host.name; - - DEBUGASSERT(!qs->ssl); - qs->ssl = SSL_new(qs->sslctx); - - SSL_set_app_data(qs->ssl, qs); - - /* set SNI */ - SSL_set_tlsext_host_name(qs->ssl, hostname); - return 0; -} - - -CURLcode Curl_quic_connect(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t sockfd, - int sockindex, - const struct sockaddr *addr, socklen_t addrlen) -{ - CURLcode result; - struct quicsocket *qs = &conn->hequic[sockindex]; - char ipbuf[40]; - int port; - int rv; - -#ifdef DEBUG_QUICHE - /* initialize debug log callback only once */ - static int debug_log_init = 0; - if(!debug_log_init) { - quiche_enable_debug_logging(quiche_debug_log, NULL); - debug_log_init = 1; - } -#endif - - (void)addr; - (void)addrlen; - - qs->sockfd = sockfd; - qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION); - if(!qs->cfg) { - failf(data, "can't create quiche config"); - return CURLE_FAILED_INIT; - } - - quiche_config_set_max_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT); - quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA); - quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA); - quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg, - QUIC_MAX_DATA); - quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA); - quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS); - quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS); - quiche_config_set_application_protos(qs->cfg, - (uint8_t *) - QUICHE_H3_APPLICATION_PROTOCOL, - sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - - 1); - - qs->sslctx = quic_ssl_ctx(data); - if(!qs->sslctx) - return CURLE_QUIC_CONNECT_ERROR; - - if(quic_init_ssl(qs, conn)) - return CURLE_QUIC_CONNECT_ERROR; - - result = Curl_rand(data, qs->scid, sizeof(qs->scid)); - if(result) - return result; - - qs->local_addrlen = sizeof(qs->local_addr); - rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr, - &qs->local_addrlen); - if(rv == -1) - return CURLE_QUIC_CONNECT_ERROR; - - qs->conn = quiche_conn_new_with_tls((const uint8_t *) qs->scid, - sizeof(qs->scid), NULL, 0, - (struct sockaddr *)&qs->local_addr, - qs->local_addrlen, addr, addrlen, - qs->cfg, qs->ssl, false); - if(!qs->conn) { - failf(data, "can't create quiche connection"); - return CURLE_OUT_OF_MEMORY; - } - - /* Known to not work on Windows */ -#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD) - { - int qfd; - (void)Curl_qlogdir(data, qs->scid, sizeof(qs->scid), &qfd); - if(qfd != -1) - quiche_conn_set_qlog_fd(qs->conn, qfd, - "qlog title", "curl qlog"); - } -#endif - - result = flush_egress(data, sockfd, qs); - if(result) - return result; - - /* extract the used address as a string */ - if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) { - char buffer[STRERROR_LEN]; - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - infof(data, "Connect socket %d over QUIC to %s:%ld", - sockfd, ipbuf, port); - - Curl_persistconninfo(data, conn, NULL, -1); - - { - unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL; - unsigned alpn_len, offset = 0; - - /* Replace each ALPN length prefix by a comma. */ - while(offset < sizeof(alpn_protocols) - 1) { - alpn_len = alpn_protocols[offset]; - alpn_protocols[offset] = ','; - offset += 1 + alpn_len; - } - - infof(data, "Sent QUIC client Initial, ALPN: %s", - alpn_protocols + 1); - } - - return CURLE_OK; -} - -static CURLcode quiche_has_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - int tempindex) -{ - CURLcode result; - struct quicsocket *qs = conn->quic = &conn->hequic[tempindex]; - - conn->recv[sockindex] = h3_stream_recv; - conn->send[sockindex] = h3_stream_send; - conn->handler = &Curl_handler_http3; - conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->httpversion = 30; - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - - if(conn->ssl_config.verifyhost) { - X509 *server_cert; - server_cert = SSL_get_peer_certificate(qs->ssl); - if(!server_cert) { - return CURLE_PEER_FAILED_VERIFICATION; - } - result = Curl_ossl_verifyhost(data, conn, server_cert); - X509_free(server_cert); - if(result) - return result; - infof(data, "Verified certificate just fine"); - } - else - infof(data, "Skipped certificate verification"); - - qs->h3config = quiche_h3_config_new(); - if(!qs->h3config) - return CURLE_OUT_OF_MEMORY; - - /* Create a new HTTP/3 connection on the QUIC connection. */ - qs->h3c = quiche_h3_conn_new_with_transport(qs->conn, qs->h3config); - if(!qs->h3c) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - if(conn->hequic[1-tempindex].cfg) { - qs = &conn->hequic[1-tempindex]; - quiche_config_free(qs->cfg); - quiche_conn_free(qs->conn); - qs->cfg = NULL; - qs->conn = NULL; - } - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, qs->ssl); - - return CURLE_OK; - fail: - quiche_h3_config_free(qs->h3config); - quiche_h3_conn_free(qs->h3c); - return result; -} - -/* - * This function gets polled to check if this QUIC connection has connected. - */ -CURLcode Curl_quic_is_connected(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *done) -{ - CURLcode result; - struct quicsocket *qs = &conn->hequic[sockindex]; - curl_socket_t sockfd = conn->tempsock[sockindex]; - - result = process_ingress(data, sockfd, qs); - if(result) - goto error; - - result = flush_egress(data, sockfd, qs); - if(result) - goto error; - - if(quiche_conn_is_established(qs->conn)) { - *done = TRUE; - result = quiche_has_connected(data, conn, 0, sockindex); - DEBUGF(infof(data, "quiche established connection")); - } - - return result; - error: - qs_disconnect(data, qs); - return result; -} - -static CURLcode process_ingress(struct Curl_easy *data, int sockfd, - struct quicsocket *qs) -{ - ssize_t recvd; - uint8_t *buf = (uint8_t *)data->state.buffer; - size_t bufsize = data->set.buffer_size; - struct sockaddr_storage from; - socklen_t from_len; - quiche_recv_info recv_info; - - DEBUGASSERT(qs->conn); - - /* in case the timeout expired */ - quiche_conn_on_timeout(qs->conn); - - do { - from_len = sizeof(from); - - recvd = recvfrom(sockfd, buf, bufsize, 0, - (struct sockaddr *)&from, &from_len); - - if((recvd < 0) && ((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK))) - break; - - if(recvd < 0) { - failf(data, "quiche: recvfrom() unexpectedly returned %zd " - "(errno: %d, socket %d)", recvd, SOCKERRNO, sockfd); - return CURLE_RECV_ERROR; - } - - recv_info.from = (struct sockaddr *) &from; - recv_info.from_len = from_len; - recv_info.to = (struct sockaddr *) &qs->local_addr; - recv_info.to_len = qs->local_addrlen; - - recvd = quiche_conn_recv(qs->conn, buf, recvd, &recv_info); - if(recvd == QUICHE_ERR_DONE) - break; - - if(recvd < 0) { - if(QUICHE_ERR_TLS_FAIL == recvd) { - long verify_ok = SSL_get_verify_result(qs->ssl); - if(verify_ok != X509_V_OK) { - failf(data, "SSL certificate problem: %s", - X509_verify_cert_error_string(verify_ok)); - - return CURLE_PEER_FAILED_VERIFICATION; - } - } - - failf(data, "quiche_conn_recv() == %zd", recvd); - - return CURLE_RECV_ERROR; - } - } while(1); - - return CURLE_OK; -} - -/* - * flush_egress drains the buffers and sends off data. - * Calls failf() on errors. - */ -static CURLcode flush_egress(struct Curl_easy *data, int sockfd, - struct quicsocket *qs) -{ - ssize_t sent; - uint8_t out[1200]; - int64_t timeout_ns; - quiche_send_info send_info; - - do { - sent = quiche_conn_send(qs->conn, out, sizeof(out), &send_info); - if(sent == QUICHE_ERR_DONE) - break; - - if(sent < 0) { - failf(data, "quiche_conn_send returned %zd", sent); - return CURLE_SEND_ERROR; - } - - sent = send(sockfd, out, sent, 0); - if(sent < 0) { - failf(data, "send() returned %zd", sent); - return CURLE_SEND_ERROR; - } - } while(1); - - /* time until the next timeout event, as nanoseconds. */ - timeout_ns = quiche_conn_timeout_as_nanos(qs->conn); - if(timeout_ns) - /* expire uses milliseconds */ - Curl_expire(data, (timeout_ns + 999999) / 1000000, EXPIRE_QUIC); - - return CURLE_OK; -} - -struct h3h1header { - char *dest; - size_t destlen; /* left to use */ - size_t nlen; /* used */ -}; - -static int cb_each_header(uint8_t *name, size_t name_len, - uint8_t *value, size_t value_len, - void *argp) -{ - struct h3h1header *headers = (struct h3h1header *)argp; - size_t olen = 0; - - if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) { - msnprintf(headers->dest, - headers->destlen, "HTTP/3 %.*s\n", - (int) value_len, value); - } - else if(!headers->nlen) { - return CURLE_HTTP3; - } - else { - msnprintf(headers->dest, - headers->destlen, "%.*s: %.*s\n", - (int)name_len, name, (int) value_len, value); - } - olen = strlen(headers->dest); - headers->destlen -= olen; - headers->nlen += olen; - headers->dest += olen; - return 0; -} - -static ssize_t h3_stream_recv(struct Curl_easy *data, - int sockindex, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - ssize_t recvd = -1; - ssize_t rcode; - struct connectdata *conn = data->conn; - struct quicsocket *qs = conn->quic; - curl_socket_t sockfd = conn->sock[sockindex]; - quiche_h3_event *ev; - int rc; - struct h3h1header headers; - struct HTTP *stream = data->req.p.http; - headers.dest = buf; - headers.destlen = buffersize; - headers.nlen = 0; - - if(process_ingress(data, sockfd, qs)) { - infof(data, "h3_stream_recv returns on ingress"); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - - if(qs->h3_recving) { - /* body receiving state */ - rcode = quiche_h3_recv_body(qs->h3c, qs->conn, stream->stream3_id, - (unsigned char *)buf, buffersize); - if(rcode <= 0) { - recvd = -1; - qs->h3_recving = FALSE; - /* fall through into the while loop below */ - } - else - recvd = rcode; - } - - while(recvd < 0) { - int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev); - if(s < 0) - /* nothing more to do */ - break; - - if(s != stream->stream3_id) { - /* another transfer, ignore for now */ - infof(data, "Got h3 for stream %u, expects %u", - s, stream->stream3_id); - continue; - } - - switch(quiche_h3_event_type(ev)) { - case QUICHE_H3_EVENT_HEADERS: - rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers); - if(rc) { - *curlcode = rc; - failf(data, "Error in HTTP/3 response header"); - break; - } - recvd = headers.nlen; - break; - case QUICHE_H3_EVENT_DATA: - if(!stream->firstbody) { - /* add a header-body separator CRLF */ - buf[0] = '\r'; - buf[1] = '\n'; - buf += 2; - buffersize -= 2; - stream->firstbody = TRUE; - recvd = 2; /* two bytes already */ - } - else - recvd = 0; - rcode = quiche_h3_recv_body(qs->h3c, qs->conn, s, (unsigned char *)buf, - buffersize); - if(rcode <= 0) { - recvd = -1; - break; - } - qs->h3_recving = TRUE; - recvd += rcode; - break; - - case QUICHE_H3_EVENT_RESET: - streamclose(conn, "Stream reset"); - *curlcode = CURLE_PARTIAL_FILE; - return -1; - - case QUICHE_H3_EVENT_FINISHED: - streamclose(conn, "End of stream"); - recvd = 0; /* end of stream */ - break; - default: - break; - } - - quiche_h3_event_free(ev); - } - if(flush_egress(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - *curlcode = (-1 == recvd)? CURLE_AGAIN : CURLE_OK; - if(recvd >= 0) - /* Get this called again to drain the event queue */ - Curl_expire(data, 0, EXPIRE_QUIC); - - data->state.drain = (recvd >= 0) ? 1 : 0; - return recvd; -} - -static ssize_t h3_stream_send(struct Curl_easy *data, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - ssize_t sent; - struct connectdata *conn = data->conn; - struct quicsocket *qs = conn->quic; - curl_socket_t sockfd = conn->sock[sockindex]; - struct HTTP *stream = data->req.p.http; - - if(!stream->h3req) { - CURLcode result = http_request(data, mem, len); - if(result) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - sent = len; - } - else { - sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id, - (uint8_t *)mem, len, FALSE); - if(sent == QUICHE_H3_ERR_DONE) { - sent = 0; - } - else if(sent < 0) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - } - - if(flush_egress(data, sockfd, qs)) { - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - *curlcode = CURLE_OK; - return sent; -} - -/* - * Store quiche version info in this buffer. - */ -void Curl_quic_ver(char *p, size_t len) -{ - (void)msnprintf(p, len, "quiche/%s", quiche_version()); -} - -/* Index where :authority header field will appear in request header - field list. */ -#define AUTHORITY_DST_IDX 3 - -static CURLcode http_request(struct Curl_easy *data, const void *mem, - size_t len) -{ - struct connectdata *conn = data->conn; - struct HTTP *stream = data->req.p.http; - size_t nheader; - int64_t stream3_id; - quiche_h3_header *nva = NULL; - struct quicsocket *qs = conn->quic; - CURLcode result = CURLE_OK; - struct h2h3req *hreq = NULL; - - stream->h3req = TRUE; /* senf off! */ - - result = Curl_pseudo_headers(data, mem, len, &hreq); - if(result) - goto fail; - nheader = hreq->entries; - - nva = malloc(sizeof(quiche_h3_header) * nheader); - if(!nva) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - else { - unsigned int i; - for(i = 0; i < nheader; i++) { - nva[i].name = (unsigned char *)hreq->header[i].name; - nva[i].name_len = hreq->header[i].namelen; - nva[i].value = (unsigned char *)hreq->header[i].value; - nva[i].value_len = hreq->header[i].valuelen; - } - } - - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - case HTTPREQ_PUT: - if(data->state.infilesize != -1) - stream->upload_left = data->state.infilesize; - else - /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown, but not zero */ - - stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader, - stream->upload_left ? FALSE: TRUE); - if((stream3_id >= 0) && data->set.postfields) { - ssize_t sent = quiche_h3_send_body(qs->h3c, qs->conn, stream3_id, - (uint8_t *)data->set.postfields, - stream->upload_left, TRUE); - if(sent <= 0) { - failf(data, "quiche_h3_send_body failed"); - result = CURLE_SEND_ERROR; - } - stream->upload_left = 0; /* nothing left to send */ - } - break; - default: - stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader, - TRUE); - break; - } - - Curl_safefree(nva); - - if(stream3_id < 0) { - H3BUGF(infof(data, "quiche_h3_send_request returned %d", - stream3_id)); - result = CURLE_SEND_ERROR; - goto fail; - } - - infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)", - stream3_id, (void *)data); - stream->stream3_id = stream3_id; - - Curl_pseudo_free(hreq); - return CURLE_OK; - -fail: - free(nva); - Curl_pseudo_free(hreq); - return result; -} - -/* - * Called from transfer.c:done_sending when we stop HTTP/3 uploading. - */ -CURLcode Curl_quic_done_sending(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - DEBUGASSERT(conn); - if(conn->handler == &Curl_handler_http3) { - /* only for HTTP/3 transfers */ - ssize_t sent; - struct HTTP *stream = data->req.p.http; - struct quicsocket *qs = conn->quic; - stream->upload_done = TRUE; - sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id, - NULL, 0, TRUE); - if(sent < 0) - return CURLE_SEND_ERROR; - } - - return CURLE_OK; -} - -/* - * Called from http.c:Curl_http_done when a request completes. - */ -void Curl_quic_done(struct Curl_easy *data, bool premature) -{ - (void)data; - (void)premature; -} - -/* - * Called from transfer.c:data_pending to know if we should keep looping - * to receive more data from the connection. - */ -bool Curl_quic_data_pending(const struct Curl_easy *data) -{ - (void)data; - return FALSE; -} - -/* - * Called from transfer.c:Curl_readwrite when neither HTTP level read - * nor write is performed. It is a good place to handle timer expiry - * for QUIC transport. - */ -CURLcode Curl_quic_idle(struct Curl_easy *data) -{ - (void)data; - return CURLE_OK; -} - -#endif diff --git a/Utilities/cmcurl/lib/vquic/vquic.c b/Utilities/cmcurl/lib/vquic/vquic.c index e52a4f301d..5f4f30d715 100644 --- a/Utilities/cmcurl/lib/vquic/vquic.c +++ b/Utilities/cmcurl/lib/vquic/vquic.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -24,15 +24,26 @@ #include "curl_setup.h" -#ifdef ENABLE_QUIC - #ifdef HAVE_FCNTL_H #include <fcntl.h> #endif #include "urldata.h" #include "dynbuf.h" -#include "curl_printf.h" +#include "cfilters.h" +#include "curl_log.h" +#include "curl_msh3.h" +#include "curl_ngtcp2.h" +#include "curl_quiche.h" #include "vquic.h" +#include "vquic_int.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +#ifdef ENABLE_QUIC #ifdef O_BINARY #define QLOGMODE O_WRONLY|O_CREAT|O_BINARY @@ -40,6 +51,231 @@ #define QLOGMODE O_WRONLY|O_CREAT #endif +void Curl_quic_ver(char *p, size_t len) +{ +#ifdef USE_NGTCP2 + Curl_ngtcp2_ver(p, len); +#elif defined(USE_QUICHE) + Curl_quiche_ver(p, len); +#elif defined(USE_MSH3) + Curl_msh3_ver(p, len); +#endif +} + +CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx, size_t pktbuflen) +{ + qctx->num_blocked_pkt = 0; + qctx->num_blocked_pkt_sent = 0; + memset(&qctx->blocked_pkt, 0, sizeof(qctx->blocked_pkt)); + + qctx->pktbuflen = pktbuflen; + qctx->pktbuf = malloc(qctx->pktbuflen); + if(!qctx->pktbuf) + return CURLE_OUT_OF_MEMORY; + +#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG) + qctx->no_gso = FALSE; +#else + qctx->no_gso = TRUE; +#endif + + return CURLE_OK; +} + +void vquic_ctx_free(struct cf_quic_ctx *qctx) +{ + free(qctx->pktbuf); + qctx->pktbuf = NULL; +} + +static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, + size_t gsolen, size_t *psent); + +static CURLcode do_sendmsg(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, size_t gsolen, + size_t *psent) +{ +#ifdef HAVE_SENDMSG + struct iovec msg_iov; + struct msghdr msg = {0}; + ssize_t sent; +#if defined(__linux__) && defined(UDP_SEGMENT) + uint8_t msg_ctrl[32]; + struct cmsghdr *cm; +#endif + + *psent = 0; + msg_iov.iov_base = (uint8_t *)pkt; + msg_iov.iov_len = pktlen; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + +#if defined(__linux__) && defined(UDP_SEGMENT) + if(pktlen > gsolen) { + /* Only set this, when we need it. macOS, for example, + * does not seem to like a msg_control of length 0. */ + msg.msg_control = msg_ctrl; + assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t))); + msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); + cm = CMSG_FIRSTHDR(&msg); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff; + } +#endif + + + while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR) + ; + + if(sent == -1) { + switch(SOCKERRNO) { + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + return CURLE_AGAIN; + case EMSGSIZE: + /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */ + break; + case EIO: + if(pktlen > gsolen) { + /* GSO failure */ + failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent, + SOCKERRNO); + qctx->no_gso = TRUE; + return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); + } + /* FALLTHROUGH */ + default: + failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO); + return CURLE_SEND_ERROR; + } + } + else { + assert(pktlen == (size_t)sent); + } +#else + ssize_t sent; + (void)gsolen; + + *psent = 0; + + while((sent = send(qctx->sockfd, (const char *)pkt, pktlen, 0)) == -1 && + SOCKERRNO == EINTR) + ; + + if(sent == -1) { + if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { + return CURLE_AGAIN; + } + else { + failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO); + if(SOCKERRNO != EMSGSIZE) { + return CURLE_SEND_ERROR; + } + /* UDP datagram is too large; caused by PMTUD. Just let it be + lost. */ + } + } +#endif + (void)cf; + *psent = pktlen; + + return CURLE_OK; +} + +static CURLcode send_packet_no_gso(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, + size_t gsolen, size_t *psent) +{ + const uint8_t *p, *end = pkt + pktlen; + size_t sent; + + *psent = 0; + + for(p = pkt; p < end; p += gsolen) { + size_t len = CURLMIN(gsolen, (size_t)(end - p)); + CURLcode curlcode = do_sendmsg(cf, data, qctx, p, len, len, &sent); + if(curlcode != CURLE_OK) { + return curlcode; + } + *psent += sent; + } + + return CURLE_OK; +} + +CURLcode vquic_send_packet(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, size_t gsolen, + size_t *psent) +{ + if(qctx->no_gso && pktlen > gsolen) { + return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent); + } + + return do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent); +} + + + +void vquic_push_blocked_pkt(struct Curl_cfilter *cf, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, size_t gsolen) +{ + struct vquic_blocked_pkt *blkpkt; + + (void)cf; + assert(qctx->num_blocked_pkt < + sizeof(qctx->blocked_pkt) / sizeof(qctx->blocked_pkt[0])); + + blkpkt = &qctx->blocked_pkt[qctx->num_blocked_pkt++]; + + blkpkt->pkt = pkt; + blkpkt->pktlen = pktlen; + blkpkt->gsolen = gsolen; +} + +CURLcode vquic_send_blocked_pkt(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx) +{ + size_t sent; + CURLcode curlcode; + struct vquic_blocked_pkt *blkpkt; + + (void)cf; + for(; qctx->num_blocked_pkt_sent < qctx->num_blocked_pkt; + ++qctx->num_blocked_pkt_sent) { + blkpkt = &qctx->blocked_pkt[qctx->num_blocked_pkt_sent]; + curlcode = vquic_send_packet(cf, data, qctx, blkpkt->pkt, + blkpkt->pktlen, blkpkt->gsolen, &sent); + + if(curlcode) { + if(curlcode == CURLE_AGAIN) { + blkpkt->pkt += sent; + blkpkt->pktlen -= sent; + } + return curlcode; + } + } + + qctx->num_blocked_pkt = 0; + qctx->num_blocked_pkt_sent = 0; + + return CURLE_OK; +} + /* * If the QLOGDIR environment variable is set, open and return a file * descriptor to write the log to. @@ -84,4 +320,76 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, return CURLE_OK; } + +CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport) +{ + (void)transport; + DEBUGASSERT(transport == TRNSPRT_QUIC); +#ifdef USE_NGTCP2 + return Curl_cf_ngtcp2_create(pcf, data, conn, ai); +#elif defined(USE_QUICHE) + return Curl_cf_quiche_create(pcf, data, conn, ai); +#elif defined(USE_MSH3) + return Curl_cf_msh3_create(pcf, data, conn, ai); +#else + *pcf = NULL; + (void)data; + (void)conn; + (void)ai; + return CURLE_NOT_BUILT_IN; +#endif +} + +bool Curl_conn_is_http3(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex) +{ +#ifdef USE_NGTCP2 + return Curl_conn_is_ngtcp2(data, conn, sockindex); +#elif defined(USE_QUICHE) + return Curl_conn_is_quiche(data, conn, sockindex); +#elif defined(USE_MSH3) + return Curl_conn_is_msh3(data, conn, sockindex); +#else + return ((conn->handler->protocol & PROTO_FAMILY_HTTP) && + (conn->httpversion == 30)); +#endif +} + +CURLcode Curl_conn_may_http3(struct Curl_easy *data, + const struct connectdata *conn) +{ + if(!(conn->handler->flags & PROTOPT_SSL)) { + failf(data, "HTTP/3 requested for non-HTTPS URL"); + return CURLE_URL_MALFORMAT; + } +#ifndef CURL_DISABLE_PROXY + if(conn->bits.socksproxy) { + failf(data, "HTTP/3 is not supported over a SOCKS proxy"); + return CURLE_URL_MALFORMAT; + } + if(conn->bits.httpproxy && conn->bits.tunnel_proxy) { + failf(data, "HTTP/3 is not supported over a HTTP proxy"); + return CURLE_URL_MALFORMAT; + } #endif + + return CURLE_OK; +} + +#else /* ENABLE_QUIC */ + +CURLcode Curl_conn_may_http3(struct Curl_easy *data, + const struct connectdata *conn) +{ + (void)conn; + (void)data; + DEBUGF(infof(data, "QUIC is not supported in this build")); + return CURLE_NOT_BUILT_IN; +} + +#endif /* !ENABLE_QUIC */ diff --git a/Utilities/cmcurl/lib/vquic/vquic.h b/Utilities/cmcurl/lib/vquic/vquic.h index 8f599a8f49..dc73957aaf 100644 --- a/Utilities/cmcurl/lib/vquic/vquic.h +++ b/Utilities/cmcurl/lib/vquic/vquic.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -27,10 +27,38 @@ #include "curl_setup.h" #ifdef ENABLE_QUIC +struct Curl_cfilter; +struct Curl_easy; +struct connectdata; +struct Curl_addrinfo; + +void Curl_quic_ver(char *p, size_t len); + CURLcode Curl_qlogdir(struct Curl_easy *data, unsigned char *scid, size_t scidlen, int *qlogfdp); -#endif + + +CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai, + int transport); + +bool Curl_conn_is_http3(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); + +extern struct Curl_cftype Curl_cft_http3; + +#else /* ENABLE_QUIC */ + +#define Curl_conn_is_http3(a,b,c) FALSE + +#endif /* !ENABLE_QUIC */ + +CURLcode Curl_conn_may_http3(struct Curl_easy *data, + const struct connectdata *conn); #endif /* HEADER_CURL_VQUIC_QUIC_H */ diff --git a/Utilities/cmcurl/lib/vquic/ngtcp2.h b/Utilities/cmcurl/lib/vquic/vquic_int.h index 2265999e22..42aba39b06 100644 --- a/Utilities/cmcurl/lib/vquic/ngtcp2.h +++ b/Utilities/cmcurl/lib/vquic/vquic_int.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_VQUIC_NGTCP2_H -#define HEADER_CURL_VQUIC_NGTCP2_H +#ifndef HEADER_CURL_VQUIC_QUIC_INT_H +#define HEADER_CURL_VQUIC_QUIC_INT_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -26,68 +26,47 @@ #include "curl_setup.h" -#ifdef USE_NGTCP2 +#ifdef ENABLE_QUIC -#ifdef HAVE_NETINET_UDP_H -#include <netinet/udp.h> -#endif - -#include <ngtcp2/ngtcp2_crypto.h> -#include <nghttp3/nghttp3.h> -#ifdef USE_OPENSSL -#include <openssl/ssl.h> -#elif defined(USE_WOLFSSL) -#include <wolfssl/options.h> -#include <wolfssl/ssl.h> -#include <wolfssl/quic.h> -#endif - -struct gtls_instance; - -struct blocked_pkt { +struct vquic_blocked_pkt { const uint8_t *pkt; size_t pktlen; size_t gsolen; }; -struct quicsocket { - struct connectdata *conn; /* point back to the connection */ - ngtcp2_conn *qconn; - ngtcp2_cid dcid; - ngtcp2_cid scid; - uint32_t version; - ngtcp2_settings settings; - ngtcp2_transport_params transport_params; - ngtcp2_connection_close_error 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_quic_ctx { + curl_socket_t sockfd; struct sockaddr_storage local_addr; socklen_t local_addrlen; - bool no_gso; + struct vquic_blocked_pkt blocked_pkt[2]; uint8_t *pktbuf; - size_t pktbuflen; /* the number of entries in blocked_pkt */ size_t num_blocked_pkt; - /* the number of processed entries in blocked_pkt */ size_t num_blocked_pkt_sent; /* the packets blocked by sendmsg (EAGAIN or EWOULDBLOCK) */ - struct blocked_pkt blocked_pkt[2]; - - nghttp3_conn *h3conn; - nghttp3_settings h3settings; - int qlogfd; + size_t pktbuflen; + /* the number of processed entries in blocked_pkt */ + bool no_gso; }; -#include "urldata.h" +CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx, size_t pktbuflen); +void vquic_ctx_free(struct cf_quic_ctx *qctx); + +CURLcode vquic_send_packet(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, size_t gsolen, + size_t *psent); + +void vquic_push_blocked_pkt(struct Curl_cfilter *cf, + struct cf_quic_ctx *qctx, + const uint8_t *pkt, size_t pktlen, size_t gsolen); + +CURLcode vquic_send_blocked_pkt(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct cf_quic_ctx *qctx); + -#endif +#endif /* !ENABLE_QUIC */ -#endif /* HEADER_CURL_VQUIC_NGTCP2_H */ +#endif /* HEADER_CURL_VQUIC_QUIC_INT_H */ diff --git a/Utilities/cmcurl/lib/vssh/libssh.c b/Utilities/cmcurl/lib/vssh/libssh.c index d9fa58acf8..11153181d4 100644 --- a/Utilities/cmcurl/lib/vssh/libssh.c +++ b/Utilities/cmcurl/lib/vssh/libssh.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2017 - 2022 Red Hat, Inc. + * Copyright (C) Red Hat, Inc. * * Authors: Nikos Mavrogiannopoulos, Tomas Mraz, Stanislav Zidek, * Robert Kolcun, Andreas Schneider diff --git a/Utilities/cmcurl/lib/vssh/libssh2.c b/Utilities/cmcurl/lib/vssh/libssh2.c index ce9229f8fa..4703eb572d 100644 --- a/Utilities/cmcurl/lib/vssh/libssh2.c +++ b/Utilities/cmcurl/lib/vssh/libssh2.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -144,7 +144,7 @@ const struct Curl_handler Curl_handler_scp = { scp_disconnect, /* disconnect */ ZERO_NULL, /* readwrite */ ZERO_NULL, /* connection_check */ - ssh_attach, + ssh_attach, /* attach */ PORT_SSH, /* defport */ CURLPROTO_SCP, /* protocol */ CURLPROTO_SCP, /* family */ @@ -173,7 +173,7 @@ const struct Curl_handler Curl_handler_sftp = { sftp_disconnect, /* disconnect */ ZERO_NULL, /* readwrite */ ZERO_NULL, /* connection_check */ - ssh_attach, + ssh_attach, /* attach */ PORT_SSH, /* defport */ CURLPROTO_SFTP, /* protocol */ CURLPROTO_SFTP, /* family */ @@ -840,6 +840,8 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) #endif static const char * const hostkey_method_ssh_rsa = "ssh-rsa"; + static const char * const hostkey_method_ssh_rsa_all + = "rsa-sha2-256,rsa-sha2-512,ssh-rsa"; static const char * const hostkey_method_ssh_dss = "ssh-dss"; @@ -914,7 +916,16 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data) break; #endif case LIBSSH2_KNOWNHOST_KEY_SSHRSA: - hostkey_method = hostkey_method_ssh_rsa; +#ifdef HAVE_LIBSSH2_VERSION + if(libssh2_version(0x010900)) + /* since 1.9.0 libssh2_session_method_pref() works as expected */ + hostkey_method = hostkey_method_ssh_rsa_all; + else +#endif + /* old libssh2 which cannot correctly remove unsupported methods due + * to bug in src/kex.c or does not support the new methods anyways. + */ + hostkey_method = hostkey_method_ssh_rsa; break; case LIBSSH2_KNOWNHOST_KEY_SSHDSS: hostkey_method = hostkey_method_ssh_dss; diff --git a/Utilities/cmcurl/lib/vssh/ssh.h b/Utilities/cmcurl/lib/vssh/ssh.h index 13bb8aa2d6..7c25555076 100644 --- a/Utilities/cmcurl/lib/vssh/ssh.h +++ b/Utilities/cmcurl/lib/vssh/ssh.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vssh/wolfssh.c b/Utilities/cmcurl/lib/vssh/wolfssh.c index 6a8fb560d6..17d59ecd23 100644 --- a/Utilities/cmcurl/lib/vssh/wolfssh.c +++ b/Utilities/cmcurl/lib/vssh/wolfssh.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/bearssl.c b/Utilities/cmcurl/lib/vtls/bearssl.c index d9c0ce0eed..7e3eb79ce8 100644 --- a/Utilities/cmcurl/lib/vtls/bearssl.c +++ b/Utilities/cmcurl/lib/vtls/bearssl.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org> + * Copyright (C) Michael Forney, <mforney@mforney.org> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -58,7 +58,7 @@ struct ssl_backend_data { unsigned char buf[BR_SSL_BUFSIZE_BIDI]; br_x509_trust_anchor *anchors; size_t anchors_len; - const char *protocols[2]; + const char *protocols[ALPN_ENTRIES_MAX]; /* SSL client context is active */ bool active; /* size of pending write, yet to be flushed */ @@ -691,29 +691,17 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, Curl_ssl_sessionid_unlock(data); } - if(cf->conn->bits.tls_enable_alpn) { - int cur = 0; - - /* NOTE: when adding more protocols here, increase the size of the - * protocols array in `struct ssl_backend_data`. - */ + if(connssl->alpn) { + struct alpn_proto_buf proto; + size_t i; -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - backend->protocols[cur++] = ALPN_H2; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); + for(i = 0; i < connssl->alpn->count; ++i) { + backend->protocols[i] = connssl->alpn->entries[i]; } -#endif - - backend->protocols[cur++] = ALPN_HTTP_1_1; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - br_ssl_engine_set_protocol_names(&backend->ctx.eng, - backend->protocols, cur); + br_ssl_engine_set_protocol_names(&backend->ctx.eng, backend->protocols, + connssl->alpn->count); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } if((1 == Curl_inet_pton(AF_INET, hostname, &addr)) @@ -862,26 +850,11 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, DEBUGASSERT(backend); if(cf->conn->bits.tls_enable_alpn) { - const char *protocol; - - protocol = br_ssl_engine_get_selected_protocol(&backend->ctx.eng); - if(protocol) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, protocol); + const char *proto; -#ifdef USE_HTTP2 - if(!strcmp(protocol, ALPN_H2)) - cf->conn->alpn = CURL_HTTP_VERSION_2; - else -#endif - if(!strcmp(protocol, ALPN_HTTP_1_1)) - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - else - infof(data, "ALPN, unrecognized protocol %s", protocol); - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - } - else - infof(data, VTLS_INFOF_NO_ALPN); + proto = br_ssl_engine_get_selected_protocol(&backend->ctx.eng); + Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, + proto? strlen(proto) : 0); } if(ssl_config->primary.sessionid) { @@ -977,7 +950,7 @@ static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, { CURLcode ret; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); timediff_t timeout_ms; int what; diff --git a/Utilities/cmcurl/lib/vtls/bearssl.h b/Utilities/cmcurl/lib/vtls/bearssl.h index 5125359961..b3651b092c 100644 --- a/Utilities/cmcurl/lib/vtls/bearssl.h +++ b/Utilities/cmcurl/lib/vtls/bearssl.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org> + * Copyright (C) Michael Forney, <mforney@mforney.org> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/vtls/gskit.c b/Utilities/cmcurl/lib/vtls/gskit.c index 2074dcaa48..59fd27ce4e 100644 --- a/Utilities/cmcurl/lib/vtls/gskit.c +++ b/Utilities/cmcurl/lib/vtls/gskit.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -499,7 +499,7 @@ static void cancel_async_handshake(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(BACKEND); - if(QsoCancelOperation(cf->conn->sock[cf->sockindex], 0) > 0) + if(QsoCancelOperation(Curl_conn_cf_get_socket(cf, data), 0) > 0) QsoWaitForIOCompletion(BACKEND->iocport, &cstat, (struct timeval *) NULL); } @@ -532,7 +532,7 @@ static int pipe_ssloverssl(struct Curl_cfilter *cf, int directions) DEBUGASSERT(connssl_next->backend); n = 1; fds[0].fd = BACKEND->remotefd; - fds[1].fd = cf->conn->sock[cf->sockindex]; + fds[1].fd = Curl_conn_cf_get_socket(cf, data); if(directions & SOS_READ) { fds[0].events |= POLLOUT; @@ -847,7 +847,7 @@ static CURLcode gskit_connect_step1(struct Curl_cfilter *cf, result = set_numeric(data, BACKEND->handle, GSK_OS400_READ_TIMEOUT, 1); if(!result) result = set_numeric(data, BACKEND->handle, GSK_FD, BACKEND->localfd >= 0? - BACKEND->localfd: cf->conn->sock[cf->sockindex]); + BACKEND->localfd: Curl_conn_cf_get_socket(cf, data)); if(!result) result = set_ciphers(cf, data, BACKEND->handle, &protoflags); if(!protoflags) { @@ -1208,7 +1208,7 @@ static int gskit_shutdown(struct Curl_cfilter *cf, close_one(cf, data); rc = 0; - what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), SSL_SHUTDOWN_TIMEOUT); while(loop--) { @@ -1230,7 +1230,7 @@ static int gskit_shutdown(struct Curl_cfilter *cf, notify alert from the server. No way to gsk_secure_soc_read() now, so use read(). */ - nread = read(cf->conn->sock[cf->sockindex], buf, sizeof(buf)); + nread = read(Curl_conn_cf_get_socket(cf, data), buf, sizeof(buf)); if(nread < 0) { char buffer[STRERROR_LEN]; @@ -1241,7 +1241,7 @@ static int gskit_shutdown(struct Curl_cfilter *cf, if(nread <= 0) break; - what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0); + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); } return rc; diff --git a/Utilities/cmcurl/lib/vtls/gskit.h b/Utilities/cmcurl/lib/vtls/gskit.h index cf923f6b85..c71e6a0117 100644 --- a/Utilities/cmcurl/lib/vtls/gskit.h +++ b/Utilities/cmcurl/lib/vtls/gskit.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/gtls.c b/Utilities/cmcurl/lib/vtls/gtls.c index 104dce6093..07dfaa437c 100644 --- a/Utilities/cmcurl/lib/vtls/gtls.c +++ b/Utilities/cmcurl/lib/vtls/gtls.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -84,7 +84,7 @@ static ssize_t gtls_push(void *s, const void *buf, size_t blen) { struct Curl_cfilter *cf = s; struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result; @@ -102,7 +102,7 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen) { struct Curl_cfilter *cf = s; struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result; @@ -214,7 +214,7 @@ static CURLcode handshake(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct ssl_backend_data *backend = connssl->backend; gnutls_session_t session; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); DEBUGASSERT(backend); session = backend->gtls.session; @@ -434,12 +434,10 @@ CURLcode gtls_client_init(struct Curl_easy *data, } #ifdef USE_GNUTLS_SRP - if((config->authtype == CURL_TLSAUTH_SRP) && - Curl_auth_allowed_to_host(data)) { + if(config->username && Curl_auth_allowed_to_host(data)) { infof(data, "Using TLS-SRP username: %s", config->username); - rc = gnutls_srp_allocate_client_credentials( - >ls->srp_client_cred); + rc = gnutls_srp_allocate_client_credentials(>ls->srp_client_cred); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_srp_allocate_client_cred() failed: %s", gnutls_strerror(rc)); @@ -581,7 +579,7 @@ CURLcode gtls_client_init(struct Curl_easy *data, #ifdef USE_GNUTLS_SRP /* Only add SRP to the cipher list if SRP is requested. Otherwise * GnuTLS will disable TLS 1.3 support. */ - if(config->authtype == CURL_TLSAUTH_SRP) { + if(config->username) { size_t len = strlen(prioritylist); char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1); @@ -646,7 +644,7 @@ CURLcode gtls_client_init(struct Curl_easy *data, #ifdef USE_GNUTLS_SRP /* put the credentials to the current session */ - if(config->authtype == CURL_TLSAUTH_SRP) { + if(config->username) { rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_SRP, gtls->srp_client_cred); if(rc != GNUTLS_E_SUCCESS) { @@ -700,32 +698,22 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) if(result) return result; - if(cf->conn->bits.tls_enable_alpn) { - int cur = 0; - gnutls_datum_t protocols[2]; + if(connssl->alpn) { + struct alpn_proto_buf proto; + gnutls_datum_t alpn[ALPN_ENTRIES_MAX]; + size_t i; -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur].data = (unsigned char *)ALPN_H2; - protocols[cur].size = ALPN_H2_LENGTH; - cur++; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); + for(i = 0; i < connssl->alpn->count; ++i) { + alpn[i].data = (unsigned char *)connssl->alpn->entries[i]; + alpn[i].size = (unsigned)strlen(connssl->alpn->entries[i]); } -#endif - - protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1; - protocols[cur].size = ALPN_HTTP_1_1_LENGTH; - cur++; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - if(gnutls_alpn_set_protocols(backend->gtls.session, protocols, cur, 0)) { + if(gnutls_alpn_set_protocols(backend->gtls.session, alpn, + (unsigned)connssl->alpn->count, 0)) { failf(data, "failed setting ALPN"); return CURLE_SSL_CONNECT_ERROR; } + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } /* This might be a reconnect, so we check for a session ID in the cache @@ -860,10 +848,8 @@ Curl_gtls_verifyserver(struct Curl_easy *data, config->verifyhost || config->issuercert) { #ifdef USE_GNUTLS_SRP - if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP - && ssl_config->primary.username - && !config->verifypeer - && gnutls_cipher_get(session)) { + if(ssl_config->primary.username && !config->verifypeer && + gnutls_cipher_get(session)) { /* no peer cert, but auth is ok if we have SRP user and cipher and no peer verify */ } @@ -1271,28 +1257,10 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, int rc; rc = gnutls_alpn_get_selected_protocol(session, &proto); - if(rc == 0) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, proto.size, - proto.data); - -#ifdef USE_HTTP2 - if(proto.size == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, proto.data, - ALPN_H2_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(proto.size == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, proto.data, ALPN_HTTP_1_1_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - } + if(rc == 0) + Curl_alpn_set_negotiated(cf, data, proto.data, proto.size); else - infof(data, VTLS_INFOF_NO_ALPN); - - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, NULL, 0); } if(ssl_config->primary.sessionid) { @@ -1516,7 +1484,7 @@ static int gtls_shutdown(struct Curl_cfilter *cf, char buf[120]; while(!done) { - int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], + int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), SSL_SHUTDOWN_TIMEOUT); if(what > 0) { /* Something to read, let's do it and hope that it is the close @@ -1556,8 +1524,7 @@ static int gtls_shutdown(struct Curl_cfilter *cf, gnutls_certificate_free_credentials(backend->gtls.cred); #ifdef USE_GNUTLS_SRP - if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP - && ssl_config->primary.username != NULL) + if(ssl_config->primary.username) gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); #endif diff --git a/Utilities/cmcurl/lib/vtls/gtls.h b/Utilities/cmcurl/lib/vtls/gtls.h index 49c1c47637..ac141e1c61 100644 --- a/Utilities/cmcurl/lib/vtls/gtls.h +++ b/Utilities/cmcurl/lib/vtls/gtls.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.c b/Utilities/cmcurl/lib/vtls/hostcheck.c index 2a648f20a9..e827dc58f3 100644 --- a/Utilities/cmcurl/lib/vtls/hostcheck.c +++ b/Utilities/cmcurl/lib/vtls/hostcheck.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/hostcheck.h b/Utilities/cmcurl/lib/vtls/hostcheck.h index d3c4eab56d..22a1ac2e56 100644 --- a/Utilities/cmcurl/lib/vtls/hostcheck.h +++ b/Utilities/cmcurl/lib/vtls/hostcheck.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/keylog.c b/Utilities/cmcurl/lib/vtls/keylog.c index 1952a690ca..d37bb183e7 100644 --- a/Utilities/cmcurl/lib/vtls/keylog.c +++ b/Utilities/cmcurl/lib/vtls/keylog.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/keylog.h b/Utilities/cmcurl/lib/vtls/keylog.h index 5d3c675b3e..eff5bf38f3 100644 --- a/Utilities/cmcurl/lib/vtls/keylog.h +++ b/Utilities/cmcurl/lib/vtls/keylog.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.c b/Utilities/cmcurl/lib/vtls/mbedtls.c index 0b81662b93..7f0f4e3668 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls.c +++ b/Utilities/cmcurl/lib/vtls/mbedtls.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2010 - 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -159,15 +159,14 @@ static void mbed_debug(void *context, int level, const char *f_name, static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen) { struct Curl_cfilter *cf = bio; - struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result; DEBUGASSERT(data); nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, &result); - /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"), - blen, (int)nwritten, result)); */ + DEBUGF(LOG_CF(data, cf, "bio_cf_out_write(len=%zu) -> %zd, err=%d", + blen, nwritten, result)); if(nwritten < 0 && CURLE_AGAIN == result) { nwritten = MBEDTLS_ERR_SSL_WANT_WRITE; } @@ -177,8 +176,7 @@ static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen) static int bio_cf_read(void *bio, unsigned char *buf, size_t blen) { struct Curl_cfilter *cf = bio; - struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result; @@ -188,8 +186,8 @@ static int bio_cf_read(void *bio, unsigned char *buf, size_t blen) return 0; nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, blen, &result); - /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"), - blen, (int)nread, result)); */ + DEBUGF(LOG_CF(data, cf, "bio_cf_in_read(len=%zu) -> %zd, err=%d", + blen, nread, result)); if(nread < 0 && CURLE_AGAIN == result) { nread = MBEDTLS_ERR_SSL_WANT_READ; } @@ -648,14 +646,13 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #ifdef HAS_ALPN - if(cf->conn->bits.tls_enable_alpn) { - const char **p = &backend->protocols[0]; -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2) - *p++ = ALPN_H2; -#endif - *p++ = ALPN_HTTP_1_1; - *p = NULL; + if(connssl->alpn) { + struct alpn_proto_buf proto; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + backend->protocols[i] = connssl->alpn->entries[i]; + } /* this function doesn't clone the protocols array, which is why we need to keep it around */ if(mbedtls_ssl_conf_alpn_protocols(&backend->config, @@ -663,8 +660,8 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) failf(data, "Failed setting ALPN protocols"); return CURLE_SSL_CONNECT_ERROR; } - for(p = &backend->protocols[0]; *p; ++p) - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, *p); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif @@ -844,28 +841,11 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) } #ifdef HAS_ALPN - if(cf->conn->bits.tls_enable_alpn) { - const char *next_protocol = mbedtls_ssl_get_alpn_protocol(&backend->ssl); + if(connssl->alpn) { + const char *proto = mbedtls_ssl_get_alpn_protocol(&backend->ssl); - if(next_protocol) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, next_protocol); -#ifdef USE_HTTP2 - if(!strncmp(next_protocol, ALPN_H2, ALPN_H2_LENGTH) && - !next_protocol[ALPN_H2_LENGTH]) { - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH) && - !next_protocol[ALPN_HTTP_1_1_LENGTH]) { - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - } - else { - infof(data, VTLS_INFOF_NO_ALPN); - } - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, + proto? strlen(proto) : 0); } #endif @@ -1081,7 +1061,7 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, { CURLcode retcode; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); timediff_t timeout_ms; int what; diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.h b/Utilities/cmcurl/lib/vtls/mbedtls.h index ec3b43bf9c..d8a0a06eb6 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls.h +++ b/Utilities/cmcurl/lib/vtls/mbedtls.h @@ -7,8 +7,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c index 7d019ede53..bcb7106a63 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c +++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2010, 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h index 22e8725ab0..2b0bd41c8b 100644 --- a/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h +++ b/Utilities/cmcurl/lib/vtls/mbedtls_threadlock.h @@ -7,8 +7,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms diff --git a/Utilities/cmcurl/lib/vtls/nss.c b/Utilities/cmcurl/lib/vtls/nss.c index 03694d22ba..774cbdd7ba 100644 --- a/Utilities/cmcurl/lib/vtls/nss.c +++ b/Utilities/cmcurl/lib/vtls/nss.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -873,11 +873,11 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg) #endif case SSL_NEXT_PROTO_NO_SUPPORT: case SSL_NEXT_PROTO_NO_OVERLAP: - infof(data, VTLS_INFOF_NO_ALPN); + Curl_alpn_set_negotiated(cf, data, NULL, 0); return; #ifdef SSL_ENABLE_ALPN case SSL_NEXT_PROTO_SELECTED: - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, buflen, buf); + Curl_alpn_set_negotiated(cf, data, buf, buflen); break; #endif default: @@ -885,25 +885,6 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg) break; } -#ifdef USE_HTTP2 - if(buflen == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, buf, ALPN_H2_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(buflen == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - - /* This callback might get called when PR_Recv() is used within - * close_one() during a connection shutdown. At that point there might not - * be any "bundle" associated with the connection anymore. - */ - if(conn->bundle) - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); } } @@ -1897,7 +1878,7 @@ static CURLcode nss_setup_connect(struct Curl_cfilter *cf, PRFileDesc *nspr_io_stub = NULL; PRBool ssl_no_cache; PRBool ssl_cbc_random_iv; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); struct ssl_connect_data *connssl = cf->ctx; struct ssl_backend_data *backend = connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); @@ -2163,27 +2144,17 @@ static CURLcode nss_setup_connect(struct Curl_cfilter *cf, #endif #if defined(SSL_ENABLE_ALPN) - if(cf->conn->bits.tls_enable_alpn) { - int cur = 0; - unsigned char protocols[128]; - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur++] = ALPN_H2_LENGTH; - memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH); - cur += ALPN_H2_LENGTH; - } -#endif - protocols[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; + if(connssl->alpn) { + struct alpn_proto_buf proto; - if(SSL_SetNextProtoNego(backend->handle, protocols, cur) != SECSuccess) + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result || SSL_SetNextProtoNego(backend->handle, proto.data, proto.len) + != SECSuccess) { + failf(data, "Error setting ALPN"); goto error; + } + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif @@ -2393,6 +2364,19 @@ static ssize_t nss_send(struct Curl_cfilter *cf, return rc; /* number of bytes */ } +static bool +nss_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + PRFileDesc *fd = connssl->backend->handle->lower; + char buf; + + (void) data; + + /* Returns true in case of error to force reading. */ + return PR_Recv(fd, (void *) &buf, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT) != 0; +} + static ssize_t nss_recv(struct Curl_cfilter *cf, struct Curl_easy *data, /* transfer */ char *buf, /* store read data here */ @@ -2543,7 +2527,7 @@ const struct Curl_ssl Curl_ssl_nss = { nss_check_cxn, /* check_cxn */ /* NSS has no shutdown function provided and thus always fail */ Curl_none_shutdown, /* shutdown */ - Curl_none_data_pending, /* data_pending */ + nss_data_pending, /* data_pending */ nss_random, /* random */ nss_cert_status_request, /* cert_status_request */ nss_connect, /* connect */ diff --git a/Utilities/cmcurl/lib/vtls/nssg.h b/Utilities/cmcurl/lib/vtls/nssg.h index 454a38f1fb..ad7eef5801 100644 --- a/Utilities/cmcurl/lib/vtls/nssg.h +++ b/Utilities/cmcurl/lib/vtls/nssg.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/openssl.c b/Utilities/cmcurl/lib/vtls/openssl.c index c5085be3c3..e3a50bd6ef 100644 --- a/Utilities/cmcurl/lib/vtls/openssl.c +++ b/Utilities/cmcurl/lib/vtls/openssl.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -96,6 +96,7 @@ #include "curl_memory.h" #include "memdebug.h" + /* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS renegotiations when built with BoringSSL. Renegotiating is non-compliant with HTTP/2 and "an extremely dangerous protocol feature". Beware. @@ -262,6 +263,12 @@ #define HAVE_OPENSSL_VERSION #endif +#ifdef OPENSSL_IS_BORINGSSL +typedef uint32_t sslerr_t; +#else +typedef unsigned long sslerr_t; +#endif + /* * Whether the OpenSSL version has the API needed to support sharing an * X509_STORE between connections. The API is: @@ -285,17 +292,17 @@ #endif /* !LIBRESSL_VERSION_NUMBER */ struct ssl_backend_data { - struct Curl_easy *logger; /* transfer handle to pass trace logs to, only - using sockindex 0 */ /* these ones requires specific SSL-types */ SSL_CTX* ctx; SSL* handle; X509* server_cert; + BIO_METHOD *bio_method; CURLcode io_result; /* result of last BIO cfilter operation */ #ifndef HAVE_KEYLOG_CALLBACK /* Set to true once a valid keylog entry has been created to avoid dupes. */ bool keylog_done; #endif + bool x509_store_setup; /* x509 store has been set up */ }; #if defined(HAVE_SSL_X509_STORE_SHARE) @@ -710,14 +717,14 @@ static int bio_cf_out_write(BIO *bio, const char *buf, int blen) { struct Curl_cfilter *cf = BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result = CURLE_SEND_ERROR; DEBUGASSERT(data); nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); - /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"), - blen, (int)nwritten, result)); */ + DEBUGF(LOG_CF(data, cf, "bio_cf_out_write(len=%d) -> %d, err=%d", + blen, (int)nwritten, result)); BIO_clear_retry_flags(bio); connssl->backend->io_result = result; if(nwritten < 0) { @@ -731,7 +738,7 @@ static int bio_cf_in_read(BIO *bio, char *buf, int blen) { struct Curl_cfilter *cf = BIO_get_data(bio); struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result = CURLE_RECV_ERROR; @@ -741,64 +748,75 @@ static int bio_cf_in_read(BIO *bio, char *buf, int blen) return 0; nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); - /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"), - blen, (int)nread, result)); */ + DEBUGF(LOG_CF(data, cf, "bio_cf_in_read(len=%d) -> %d, err=%d", + blen, (int)nread, result)); BIO_clear_retry_flags(bio); connssl->backend->io_result = result; if(nread < 0) { if(CURLE_AGAIN == result) BIO_set_retry_read(bio); } + + /* Before returning server replies to the SSL instance, we need + * to have setup the x509 store or verification will fail. */ + if(!connssl->backend->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, connssl->backend->ctx); + if(result) { + connssl->backend->io_result = result; + return -1; + } + connssl->backend->x509_store_setup = TRUE; + } + return (int)nread; } -static BIO_METHOD *bio_cf_method = NULL; - #if USE_PRE_1_1_API static BIO_METHOD bio_cf_meth_1_0 = { - BIO_TYPE_MEM, - "OpenSSL CF BIO", - bio_cf_out_write, - bio_cf_in_read, - NULL, /* puts is never called */ - NULL, /* gets is never called */ - bio_cf_ctrl, - bio_cf_create, - bio_cf_destroy, - NULL + BIO_TYPE_MEM, + "OpenSSL CF BIO", + bio_cf_out_write, + bio_cf_in_read, + NULL, /* puts is never called */ + NULL, /* gets is never called */ + bio_cf_ctrl, + bio_cf_create, + bio_cf_destroy, + NULL }; -static void bio_cf_init_methods(void) +static BIO_METHOD *bio_cf_method_create(void) { - bio_cf_method = &bio_cf_meth_1_0; + return &bio_cf_meth_1_0; } -#define bio_cf_free_methods() Curl_nop_stmt +#define bio_cf_method_free(m) Curl_nop_stmt #else -static void bio_cf_init_methods(void) +static BIO_METHOD *bio_cf_method_create(void) { - bio_cf_method = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO"); - BIO_meth_set_write(bio_cf_method, &bio_cf_out_write); - BIO_meth_set_read(bio_cf_method, &bio_cf_in_read); - BIO_meth_set_ctrl(bio_cf_method, &bio_cf_ctrl); - BIO_meth_set_create(bio_cf_method, &bio_cf_create); - BIO_meth_set_destroy(bio_cf_method, &bio_cf_destroy); + BIO_METHOD *m = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO"); + if(m) { + BIO_meth_set_write(m, &bio_cf_out_write); + BIO_meth_set_read(m, &bio_cf_in_read); + BIO_meth_set_ctrl(m, &bio_cf_ctrl); + BIO_meth_set_create(m, &bio_cf_create); + BIO_meth_set_destroy(m, &bio_cf_destroy); + } + return m; } -static void bio_cf_free_methods(void) +static void bio_cf_method_free(BIO_METHOD *m) { - BIO_meth_free(bio_cf_method); + if(m) + BIO_meth_free(m); } #endif -static bool ossl_attach_data(struct Curl_cfilter *cf, - struct Curl_easy *data); - /* * Number of bytes to read from the random number seed file. This must be * a finite value (because some entropy "files" like /dev/urandom have @@ -930,54 +948,6 @@ static char *ossl_strerror(unsigned long error, char *buf, size_t size) return buf; } -/* Return an extra data index for the transfer data. - * This index can be used with SSL_get_ex_data() and SSL_set_ex_data(). - */ -static int ossl_get_ssl_data_index(void) -{ - static int ssl_ex_data_data_index = -1; - if(ssl_ex_data_data_index < 0) { - ssl_ex_data_data_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - } - return ssl_ex_data_data_index; -} - -/* Return an extra data index for the associated Curl_cfilter instance. - * This index can be used with SSL_get_ex_data() and SSL_set_ex_data(). - */ -static int ossl_get_ssl_cf_index(void) -{ - static int ssl_ex_data_cf_index = -1; - if(ssl_ex_data_cf_index < 0) { - ssl_ex_data_cf_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - } - return ssl_ex_data_cf_index; -} - -/* Return an extra data index for the sockindex. - * This index can be used with SSL_get_ex_data() and SSL_set_ex_data(). - */ -static int ossl_get_ssl_sockindex_index(void) -{ - static int sockindex_index = -1; - if(sockindex_index < 0) { - sockindex_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - } - return sockindex_index; -} - -/* Return an extra data index for proxy boolean. - * This index can be used with SSL_get_ex_data() and SSL_set_ex_data(). - */ -static int ossl_get_proxy_index(void) -{ - static int proxy_index = -1; - if(proxy_index < 0) { - proxy_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - } - return proxy_index; -} - static int passwd_callback(char *buf, int num, int encrypting, void *global_passwd) { @@ -1254,7 +1224,7 @@ SSL_CTX_use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, if(ret) { X509 *ca; - unsigned long err; + sslerr_t err; if(!SSL_CTX_clear_chain_certs(ctx)) { ret = 0; @@ -1776,14 +1746,8 @@ static int ossl_init(void) OpenSSL_add_all_algorithms(); #endif - bio_cf_init_methods(); Curl_tls_keylog_open(); - /* Initialize the extra data indexes */ - if(ossl_get_ssl_data_index() < 0 || ossl_get_ssl_cf_index() < 0 || - ossl_get_ssl_sockindex_index() < 0 || ossl_get_proxy_index() < 0) - return 0; - return 1; } @@ -1822,7 +1786,6 @@ static void ossl_cleanup(void) #endif Curl_tls_keylog_close(); - bio_cf_free_methods(); } /* @@ -1840,7 +1803,10 @@ static int ossl_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef MSG_PEEK char buf; ssize_t nread; - nread = recv((RECV_TYPE_ARG1)cf->conn->sock[cf->sockindex], + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); + if(sock == CURL_SOCKET_BAD) + return 0; /* no socket, consider closed */ + nread = recv((RECV_TYPE_ARG1)sock, (RECV_TYPE_ARG2)&buf, (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK); if(nread == 0) @@ -1968,19 +1934,15 @@ static struct curl_slist *ossl_engines_list(struct Curl_easy *data) return list; } -#define set_logger(connssl, data) \ - connssl->backend->logger = data - static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; struct ssl_backend_data *backend = connssl->backend; + (void)data; DEBUGASSERT(backend); if(backend->handle) { - set_logger(connssl, data); - if(cf->next && cf->next->connected) { char buf[32]; /* Maybe the server has already sent a close notify alert. @@ -1997,6 +1959,11 @@ static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) if(backend->ctx) { SSL_CTX_free(backend->ctx); backend->ctx = NULL; + backend->x509_store_setup = FALSE; + } + if(backend->bio_method) { + bio_cf_method_free(backend->bio_method); + backend->bio_method = NULL; } } @@ -2034,7 +2001,7 @@ static int ossl_shutdown(struct Curl_cfilter *cf, if(backend->handle) { buffsize = (int)sizeof(buf); while(!done && loop--) { - int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], + int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), SSL_SHUTDOWN_TIMEOUT); if(what > 0) { ERR_clear_error(); @@ -2163,6 +2130,22 @@ static bool subj_alt_hostcheck(struct Curl_easy *data, return FALSE; } +static CURLcode +ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, + X509 *server_cert, const char *hostname, + const char *dispname); + +CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, + X509 *server_cert) +{ + const char *hostname, *dispname; + int port; + + (void)conn; + Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port); + return ossl_verifyhost(data, conn, server_cert, hostname, dispname); +} + /* Quote from RFC2818 section 3.1 "Server Identity" If a subjectAltName extension of type dNSName is present, that MUST @@ -2185,8 +2168,10 @@ static bool subj_alt_hostcheck(struct Curl_easy *data, This function is now used from ngtcp2 (QUIC) as well. */ -CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - X509 *server_cert) +static CURLcode +ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, + X509 *server_cert, const char *hostname, + const char *dispname) { bool matched = FALSE; int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */ @@ -2200,12 +2185,9 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, CURLcode result = CURLE_OK; bool dNSName = FALSE; /* if a dNSName field exists in the cert */ bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */ - const char *hostname, *dispname; - int port; size_t hostlen; (void)conn; - Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port); hostlen = strlen(hostname); #ifndef ENABLE_IPV6 @@ -2668,24 +2650,15 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, const void *buf, size_t len, SSL *ssl, void *userp) { - char unknown[32]; - const char *verstr = NULL; - struct connectdata *conn = userp; - int cf_idx = ossl_get_ssl_cf_index(); - struct ssl_connect_data *connssl; + const char *verstr = "???"; + struct Curl_cfilter *cf = userp; struct Curl_easy *data = NULL; - struct Curl_cfilter *cf; - - DEBUGASSERT(cf_idx >= 0); - cf = (struct Curl_cfilter*) SSL_get_ex_data(ssl, cf_idx); - DEBUGASSERT(cf); - connssl = cf->ctx; - DEBUGASSERT(connssl); - DEBUGASSERT(connssl->backend); - data = connssl->backend->logger; + char unknown[32]; - if(!conn || !data || !data->set.fdebug || - (direction != 0 && direction != 1)) + if(!cf) + return; + data = CF_DATA_CURRENT(cf); + if(!data || !data->set.fdebug || (direction && direction != 1)) return; switch(ssl_ver) { @@ -2730,6 +2703,9 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, * For TLS 1.3, skip notification of the decrypted inner Content-Type. */ if(ssl_ver +#ifdef SSL3_RT_HEADER + && content_type != SSL3_RT_HEADER +#endif #ifdef SSL3_RT_INNER_CONTENT_TYPE && content_type != SSL3_RT_INNER_CONTENT_TYPE #endif @@ -2765,7 +2741,7 @@ static void ossl_trace(int direction, int ssl_ver, int content_type, } txt_len = msnprintf(ssl_buf, sizeof(ssl_buf), - CFMSG(cf, "%s (%s), %s, %s (%d):\n"), + "%s (%s), %s, %s (%d):\n", verstr, direction?"OUT":"IN", tls_rt_name, msg_name, msg_type); if(0 <= txt_len && (unsigned)txt_len < sizeof(ssl_buf)) { @@ -2983,21 +2959,14 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) struct Curl_easy *data; struct Curl_cfilter *cf; const struct ssl_config_data *config; - curl_socket_t *sockindex_ptr; - int data_idx = ossl_get_ssl_data_index(); - int cf_idx = ossl_get_ssl_cf_index(); - int sockindex_idx = ossl_get_ssl_sockindex_index(); - int proxy_idx = ossl_get_proxy_index(); + struct ssl_connect_data *connssl; bool isproxy; - if(data_idx < 0 || cf_idx < 0 || sockindex_idx < 0 || proxy_idx < 0) - return 0; - - cf = (struct Curl_cfilter*) SSL_get_ex_data(ssl, cf_idx); - data = (struct Curl_easy *) SSL_get_ex_data(ssl, data_idx); + cf = (struct Curl_cfilter*) SSL_get_app_data(ssl); + connssl = cf? cf->ctx : NULL; + data = connssl? CF_DATA_CURRENT(cf) : NULL; /* The sockindex has been stored as a pointer to an array element */ - sockindex_ptr = (curl_socket_t*) SSL_get_ex_data(ssl, sockindex_idx); - if(!cf || !data || !sockindex_ptr) + if(!cf || !data) return 0; isproxy = Curl_ssl_cf_is_proxy(cf); @@ -3091,7 +3060,7 @@ static CURLcode load_cacert_from_memory(X509_STORE *store, BIO_free(cbio); /* if we didn't end up importing anything, treat that as an error */ - return (count > 0 ? CURLE_OK : CURLE_SSL_CACERT_BADFILE); + return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE; } static CURLcode populate_x509_store(struct Curl_cfilter *cf, @@ -3110,206 +3079,219 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf, const char * const ssl_crlfile = ssl_config->primary.CRLfile; const bool verifypeer = conn_config->verifypeer; bool imported_native_ca = false; + bool imported_ca_info_blob = false; if(!store) return CURLE_OUT_OF_MEMORY; + if(verifypeer) { #if defined(USE_WIN32_CRYPTO) - /* Import certificates from the Windows root certificate store if requested. - https://stackoverflow.com/questions/9507184/ - https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 - https://datatracker.ietf.org/doc/html/rfc5280 */ - if((conn_config->verifypeer || conn_config->verifyhost) && - (ssl_config->native_ca_store)) { - HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT")); - - if(hStore) { - PCCERT_CONTEXT pContext = NULL; - /* The array of enhanced key usage OIDs will vary per certificate and is - declared outside of the loop so that rather than malloc/free each - iteration we can grow it with realloc, when necessary. */ - CERT_ENHKEY_USAGE *enhkey_usage = NULL; - DWORD enhkey_usage_size = 0; - - /* This loop makes a best effort to import all valid certificates from - the MS root store. If a certificate cannot be imported it is skipped. - 'result' is used to store only hard-fail conditions (such as out of - memory) that cause an early break. */ - result = CURLE_OK; - for(;;) { - X509 *x509; - FILETIME now; - BYTE key_usage[2]; - DWORD req_size; - const unsigned char *encoded_cert; + /* Import certificates from the Windows root certificate store if + requested. + https://stackoverflow.com/questions/9507184/ + https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 + https://datatracker.ietf.org/doc/html/rfc5280 */ + if(ssl_config->native_ca_store) { + HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT")); + + if(hStore) { + PCCERT_CONTEXT pContext = NULL; + /* The array of enhanced key usage OIDs will vary per certificate and + is declared outside of the loop so that rather than malloc/free each + iteration we can grow it with realloc, when necessary. */ + CERT_ENHKEY_USAGE *enhkey_usage = NULL; + DWORD enhkey_usage_size = 0; + + /* This loop makes a best effort to import all valid certificates from + the MS root store. If a certificate cannot be imported it is + skipped. 'result' is used to store only hard-fail conditions (such + as out of memory) that cause an early break. */ + result = CURLE_OK; + for(;;) { + X509 *x509; + FILETIME now; + BYTE key_usage[2]; + DWORD req_size; + const unsigned char *encoded_cert; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - char cert_name[256]; + char cert_name[256]; #endif - pContext = CertEnumCertificatesInStore(hStore, pContext); - if(!pContext) - break; + pContext = CertEnumCertificatesInStore(hStore, pContext); + if(!pContext) + break; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, - NULL, cert_name, sizeof(cert_name))) { - strcpy(cert_name, "Unknown"); - } - infof(data, "SSL: Checking cert \"%s\"", cert_name); + if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + NULL, cert_name, sizeof(cert_name))) { + strcpy(cert_name, "Unknown"); + } + infof(data, "SSL: Checking cert \"%s\"", cert_name); #endif + encoded_cert = (const unsigned char *)pContext->pbCertEncoded; + if(!encoded_cert) + continue; - encoded_cert = (const unsigned char *)pContext->pbCertEncoded; - if(!encoded_cert) - continue; - - GetSystemTimeAsFileTime(&now); - if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || - CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) - continue; - - /* If key usage exists check for signing attribute */ - if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, - pContext->pCertInfo, - key_usage, sizeof(key_usage))) { - if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) + GetSystemTimeAsFileTime(&now); + if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || + CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) continue; - } - else if(GetLastError()) - continue; - - /* If enhanced key usage exists check for server auth attribute. - * - * Note "In a Microsoft environment, a certificate might also have EKU - * extended properties that specify valid uses for the certificate." - * The call below checks both, and behavior varies depending on what is - * found. For more details see CertGetEnhancedKeyUsage doc. - */ - if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { - if(req_size && req_size > enhkey_usage_size) { - void *tmp = realloc(enhkey_usage, req_size); - - if(!tmp) { - failf(data, "SSL: Out of memory allocating for OID list"); - result = CURLE_OUT_OF_MEMORY; - break; - } - enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; - enhkey_usage_size = req_size; + /* If key usage exists check for signing attribute */ + if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, + pContext->pCertInfo, + key_usage, sizeof(key_usage))) { + if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) + continue; } + else if(GetLastError()) + continue; - if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { - if(!enhkey_usage->cUsageIdentifier) { - /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is - good for all uses. If it returns zero, the certificate has no - valid uses." */ - if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) - continue; + /* If enhanced key usage exists check for server auth attribute. + * + * Note "In a Microsoft environment, a certificate might also have + * EKU extended properties that specify valid uses for the + * certificate." The call below checks both, and behavior varies + * depending on what is found. For more details see + * CertGetEnhancedKeyUsage doc. + */ + if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { + if(req_size && req_size > enhkey_usage_size) { + void *tmp = realloc(enhkey_usage, req_size); + + if(!tmp) { + failf(data, "SSL: Out of memory allocating for OID list"); + result = CURLE_OUT_OF_MEMORY; + break; + } + + enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; + enhkey_usage_size = req_size; } - else { - DWORD i; - bool found = false; - - for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { - if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, - enhkey_usage->rgpszUsageIdentifier[i])) { - found = true; - break; - } + + if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { + if(!enhkey_usage->cUsageIdentifier) { + /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate + is good for all uses. If it returns zero, the certificate + has no valid uses." */ + if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) + continue; } + else { + DWORD i; + bool found = false; + + for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { + if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, + enhkey_usage->rgpszUsageIdentifier[i])) { + found = true; + break; + } + } - if(!found) - continue; + if(!found) + continue; + } } + else + continue; } else continue; - } - else - continue; - x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); - if(!x509) - continue; + x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if(!x509) + continue; - /* Try to import the certificate. This may fail for legitimate reasons - such as duplicate certificate, which is allowed by MS but not - OpenSSL. */ - if(X509_STORE_add_cert(store, x509) == 1) { + /* Try to import the certificate. This may fail for legitimate + reasons such as duplicate certificate, which is allowed by MS but + not OpenSSL. */ + if(X509_STORE_add_cert(store, x509) == 1) { #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - infof(data, "SSL: Imported cert \"%s\"", cert_name); + infof(data, "SSL: Imported cert \"%s\"", cert_name); #endif - imported_native_ca = true; + imported_native_ca = true; + } + X509_free(x509); } - X509_free(x509); - } - free(enhkey_usage); - CertFreeCertificateContext(pContext); - CertCloseStore(hStore, 0); + free(enhkey_usage); + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); - if(result) - return result; + if(result) + return result; + } + if(imported_native_ca) + infof(data, "successfully imported Windows CA store"); + else + infof(data, "error importing Windows CA store, continuing anyway"); } - if(imported_native_ca) - infof(data, "successfully imported Windows CA store"); - else - infof(data, "error importing Windows CA store, continuing anyway"); - } #endif - - if(ca_info_blob) { - result = load_cacert_from_memory(store, ca_info_blob); - if(result) { - if(result == CURLE_OUT_OF_MEMORY || - (verifypeer && !imported_native_ca)) { + if(ca_info_blob) { + result = load_cacert_from_memory(store, ca_info_blob); + if(result) { failf(data, "error importing CA certificate blob"); return result; } - /* Only warn if no certificate verification is required. */ - infof(data, "error importing CA certificate blob, continuing anyway"); + else { + imported_ca_info_blob = true; + infof(data, "successfully imported CA certificate blob"); + } } - } - if(verifypeer && !imported_native_ca && (ssl_cafile || ssl_capath)) { + if(ssl_cafile || ssl_capath) { #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) - /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ - if(ssl_cafile && - !X509_STORE_load_file(store, ssl_cafile)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate file: %s", ssl_cafile); - return CURLE_SSL_CACERT_BADFILE; - } - if(ssl_capath && - !X509_STORE_load_path(store, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate path: %s", ssl_capath); - return CURLE_SSL_CACERT_BADFILE; - } + /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ + if(ssl_cafile && !X509_STORE_load_file(store, ssl_cafile)) { + if(!imported_native_ca && !imported_ca_info_blob) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate file: %s", ssl_cafile); + return CURLE_SSL_CACERT_BADFILE; + } + else + infof(data, "error setting certificate file, continuing anyway"); + } + if(ssl_capath && !X509_STORE_load_path(store, ssl_capath)) { + if(!imported_native_ca && !imported_ca_info_blob) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate path: %s", ssl_capath); + return CURLE_SSL_CACERT_BADFILE; + } + else + infof(data, "error setting certificate path, continuing anyway"); + } #else - /* tell OpenSSL where to find CA certificates that are used to verify the - server's certificate. */ - if(!X509_STORE_load_locations(store, 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; - } + /* tell OpenSSL where to find CA certificates that are used to verify the + server's certificate. */ + if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) { + if(!imported_native_ca && !imported_ca_info_blob) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate verify locations:" + " CAfile: %s CApath: %s", + ssl_cafile ? ssl_cafile : "none", + ssl_capath ? ssl_capath : "none"); + return CURLE_SSL_CACERT_BADFILE; + } + else { + infof(data, "error setting certificate verify locations," + " continuing anyway"); + } + } #endif - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); - } + infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); + infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); + } #ifdef CURL_CA_FALLBACK - if(verifypeer && - !ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) { - /* verifying the peer without any CA certificates won't - work so use openssl's built-in default as fallback */ - X509_STORE_set_default_paths(store); - } + if(!ssl_cafile && !ssl_capath && + !imported_native_ca && !imported_ca_info_blob) { + /* verifying the peer without any CA certificates won't + work so use openssl's built-in default as fallback */ + X509_STORE_set_default_paths(store); + } #endif + } if(ssl_crlfile) { /* tell OpenSSL where to find CRL file that is used to check certificate @@ -3440,9 +3422,9 @@ static void set_cached_x509_store(struct Curl_cfilter *cf, } } -static CURLcode set_up_x509_store(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct ssl_backend_data *backend) +CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); @@ -3462,10 +3444,10 @@ static CURLcode set_up_x509_store(struct Curl_cfilter *cf, cached_store = get_cached_x509_store(cf, data); if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) { - SSL_CTX_set_cert_store(backend->ctx, cached_store); + SSL_CTX_set_cert_store(ssl_ctx, cached_store); } else { - X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx); + X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx); result = populate_x509_store(cf, data, store); if(result == CURLE_OK && cache_criteria_met) { @@ -3476,11 +3458,11 @@ static CURLcode set_up_x509_store(struct Curl_cfilter *cf, return result; } #else /* HAVE_SSL_X509_STORE_SHARE */ -static CURLcode set_up_x509_store(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct ssl_backend_data *backend) +CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx) { - X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx); + X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx); return populate_x509_store(cf, data, store); } @@ -3510,9 +3492,6 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #endif #endif const long int ssl_version = conn_config->version; -#ifdef USE_OPENSSL_SRP - const enum CURL_TLSAUTH ssl_authtype = ssl_config->primary.authtype; -#endif char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; const char * const ssl_cert_type = ssl_config->cert_type; @@ -3580,8 +3559,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, if(data->set.fdebug && data->set.verbose) { /* the SSL trace callback is only used for verbose logging */ SSL_CTX_set_msg_callback(backend->ctx, ossl_trace); - SSL_CTX_set_msg_callback_arg(backend->ctx, cf->conn); - set_logger(connssl, data); + SSL_CTX_set_msg_callback_arg(backend->ctx, cf); } #endif @@ -3677,36 +3655,17 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, SSL_CTX_set_options(backend->ctx, ctx_options); #ifdef HAS_ALPN - if(cf->conn->bits.tls_enable_alpn) { - int cur = 0; - unsigned char protocols[128]; - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur++] = ALPN_H2_LENGTH; - - memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH); - cur += ALPN_H2_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - protocols[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); + if(connssl->alpn) { + struct alpn_proto_buf proto; - /* expects length prefixed preference ordered list of protocols in wire - * format - */ - if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) { + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result || + SSL_CTX_set_alpn_protos(backend->ctx, proto.data, proto.len)) { failf(data, "Error setting ALPN"); return CURLE_SSL_CONNECT_ERROR; } + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif @@ -3764,8 +3723,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #endif #ifdef USE_OPENSSL_SRP - if((ssl_authtype == CURL_TLSAUTH_SRP) && - Curl_auth_allowed_to_host(data)) { + if(ssl_config->primary.username && Curl_auth_allowed_to_host(data)) { char * const ssl_username = ssl_config->primary.username; char * const ssl_password = ssl_config->primary.password; infof(data, "Using TLS-SRP username: %s", ssl_username); @@ -3789,10 +3747,6 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, } #endif - result = set_up_x509_store(cf, data, backend); - if(result) - return result; - /* 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 @@ -3836,6 +3790,8 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, return CURLE_OUT_OF_MEMORY; } + SSL_set_app_data(backend->handle, cf); + #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ !defined(OPENSSL_NO_OCSP) if(conn_config->verifystatus) @@ -3863,13 +3819,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, } #endif - if(!ossl_attach_data(cf, data)) { - /* Maybe the internal errors of SSL_get_ex_new_index or SSL_set_ex_data */ - failf(data, "SSL: ossl_attach_data failed: %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); - return CURLE_SSL_CONNECT_ERROR; - } + SSL_set_app_data(backend->handle, cf); if(ssl_config->primary.sessionid) { Curl_ssl_sessionid_lock(data); @@ -3888,13 +3838,26 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, Curl_ssl_sessionid_unlock(data); } - bio = BIO_new(bio_cf_method); + backend->bio_method = bio_cf_method_create(); + if(!backend->bio_method) + return CURLE_OUT_OF_MEMORY; + bio = BIO_new(backend->bio_method); if(!bio) return CURLE_OUT_OF_MEMORY; BIO_set_data(bio, cf); +#ifdef HAVE_SSL_SET0_WBIO + /* with OpenSSL v1.1.1 we get an alternative to SSL_set_bio() that works + * without backward compat quirks. Every call takes one reference, so we + * up it and pass. SSL* then owns it and will free. + * We check on the function in configure, since libressl and friends + * each have their own versions to add support for this. */ + BIO_up_ref(bio); + SSL_set0_rbio(backend->handle, bio); + SSL_set0_wbio(backend->handle, bio); +#else SSL_set_bio(backend->handle, bio, bio); - +#endif connssl->connecting_state = ssl_connect_2; return CURLE_OK; @@ -3915,6 +3878,16 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, ERR_clear_error(); err = SSL_connect(backend->handle); + + if(!backend->x509_store_setup) { + /* After having send off the ClientHello, we prepare the x509 + * store to verify the coming certificate from the server */ + CURLcode result = Curl_ssl_setup_x509_store(cf, data, backend->ctx); + if(result) + return result; + backend->x509_store_setup = TRUE; + } + #ifndef HAVE_KEYLOG_CALLBACK if(Curl_tls_keylog_enabled()) { /* If key logging is enabled, wait for the handshake to complete and then @@ -3949,7 +3922,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, } else { /* untreated error */ - unsigned long errdetail; + sslerr_t errdetail; char error_buffer[256]=""; CURLcode result; long lerr; @@ -4043,26 +4016,8 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, const unsigned char *neg_protocol; unsigned int len; SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len); - if(len) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, len, neg_protocol); - -#ifdef USE_HTTP2 - if(len == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, neg_protocol, len)) { - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(len == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - } - else - infof(data, VTLS_INFOF_NO_ALPN); - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + return Curl_alpn_set_negotiated(cf, data, neg_protocol, len); } #endif @@ -4205,7 +4160,8 @@ static CURLcode servercert(struct Curl_cfilter *cf, BIO_free(mem); if(conn_config->verifyhost) { - result = Curl_ossl_verifyhost(data, conn, backend->server_cert); + result = ossl_verifyhost(data, conn, backend->server_cert, + connssl->hostname, connssl->dispname); if(result) { X509_free(backend->server_cert); backend->server_cert = NULL; @@ -4379,7 +4335,7 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, { CURLcode result = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); int what; /* check if the connection has already been established */ @@ -4418,8 +4374,9 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, } /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading || - connssl->connecting_state == ssl_connect_2_writing) { + if(!nonblocking && + (connssl->connecting_state == ssl_connect_2_reading || + connssl->connecting_state == ssl_connect_2_writing)) { curl_socket_t writefd = ssl_connect_2_writing == connssl->connecting_state?sockfd:CURL_SOCKET_BAD; @@ -4427,7 +4384,7 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, connssl->connecting_state?sockfd:CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0:timeout_ms); + timeout_ms); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); @@ -4435,11 +4392,6 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, goto out; } if(0 == what) { - if(nonblocking) { - *done = FALSE; - result = CURLE_OK; - goto out; - } /* timeout */ failf(data, "SSL connection timeout"); result = CURLE_OPERATION_TIMEDOUT; @@ -4527,7 +4479,7 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, 'size_t' */ int err; char error_buffer[256]; - unsigned long sslerror; + sslerr_t sslerror; int memlen; int rc; struct ssl_connect_data *connssl = cf->ctx; @@ -4539,7 +4491,6 @@ static ssize_t ossl_send(struct Curl_cfilter *cf, ERR_clear_error(); memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - set_logger(connssl, data); rc = SSL_write(backend->handle, mem, memlen); if(rc <= 0) { @@ -4636,7 +4587,6 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf, ERR_clear_error(); buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - set_logger(connssl, data); nread = (ssize_t)SSL_read(backend->handle, buf, buffsize); if(nread <= 0) { @@ -4854,89 +4804,6 @@ static void *ossl_get_internals(struct ssl_connect_data *connssl, (void *)backend->ctx : (void *)backend->handle; } -static bool ossl_attach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - const struct ssl_config_data *config; - - DEBUGASSERT(backend); - - /* If we don't have SSL context, do nothing. */ - if(!backend->handle) - return FALSE; - - config = Curl_ssl_cf_get_config(cf, data); - if(config->primary.sessionid) { - int data_idx = ossl_get_ssl_data_index(); - int cf_idx = ossl_get_ssl_cf_index(); - int sockindex_idx = ossl_get_ssl_sockindex_index(); - int proxy_idx = ossl_get_proxy_index(); - - if(data_idx >= 0 && cf_idx >= 0 && sockindex_idx >= 0 && - proxy_idx >= 0) { - int data_status, cf_status, sockindex_status, proxy_status; - - /* Store the data needed for the "new session" callback. - * The sockindex is stored as a pointer to an array element. */ - data_status = SSL_set_ex_data(backend->handle, data_idx, data); - cf_status = SSL_set_ex_data(backend->handle, cf_idx, cf); - sockindex_status = SSL_set_ex_data(backend->handle, sockindex_idx, - cf->conn->sock + cf->sockindex); -#ifndef CURL_DISABLE_PROXY - proxy_status = SSL_set_ex_data(backend->handle, proxy_idx, - Curl_ssl_cf_is_proxy(cf)? - (void *) 1 : NULL); -#else - proxy_status = SSL_set_ex_data(backend->handle, proxy_idx, NULL); -#endif - if(data_status && cf_status && sockindex_status && proxy_status) - return TRUE; - } - return FALSE; - } - return TRUE; -} - -/* - * Starting with TLS 1.3, the ossl_new_session_cb callback gets called after - * the handshake. If the transfer that sets up the callback gets killed before - * this callback arrives, we must make sure to properly clear the data to - * avoid UAF problems. A future optimization could be to instead store another - * transfer that might still be using the same connection. - */ - -static void ossl_detach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct ssl_connect_data *connssl = cf->ctx; - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - - /* If we don't have SSL context, do nothing. */ - if(!backend->handle) - return; - - if(ssl_config->primary.sessionid) { - int data_idx = ossl_get_ssl_data_index(); - int cf_idx = ossl_get_ssl_cf_index(); - int sockindex_idx = ossl_get_ssl_sockindex_index(); - int proxy_idx = ossl_get_proxy_index(); - - if(data_idx >= 0 && cf_idx >= 0 && sockindex_idx >= 0 && - proxy_idx >= 0) { - /* Disable references to data in "new session" callback to avoid - * accessing a stale pointer. */ - SSL_set_ex_data(backend->handle, data_idx, NULL); - SSL_set_ex_data(backend->handle, cf_idx, NULL); - SSL_set_ex_data(backend->handle, sockindex_idx, NULL); - SSL_set_ex_data(backend->handle, proxy_idx, NULL); - } - } -} - static void ossl_free_multi_ssl_backend_data( struct multi_ssl_backend_data *mbackend) { @@ -4990,8 +4857,8 @@ const struct Curl_ssl Curl_ssl_openssl = { #else NULL, /* sha256sum */ #endif - ossl_attach_data, /* use of data in this connection */ - ossl_detach_data, /* remote of data from this connection */ + NULL, /* use of data in this connection */ + NULL, /* remote of data from this connection */ ossl_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */ ossl_recv, /* recv decrypted data */ ossl_send, /* send data to encrypt */ diff --git a/Utilities/cmcurl/lib/vtls/openssl.h b/Utilities/cmcurl/lib/vtls/openssl.h index 9df4ecddba..950faab889 100644 --- a/Utilities/cmcurl/lib/vtls/openssl.h +++ b/Utilities/cmcurl/lib/vtls/openssl.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -56,5 +56,14 @@ CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl); +/** + * Setup the OpenSSL X509_STORE in `ssl_ctx` for the cfilter `cf` and + * easy handle `data`. Will allow reuse of a shared cache if suitable + * and configured. + */ +CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx); + #endif /* USE_OPENSSL */ #endif /* HEADER_CURL_SSLUSE_H */ diff --git a/Utilities/cmcurl/lib/vtls/rustls.c b/Utilities/cmcurl/lib/vtls/rustls.c index 27f4ec8d8c..003533dbb9 100644 --- a/Utilities/cmcurl/lib/vtls/rustls.c +++ b/Utilities/cmcurl/lib/vtls/rustls.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews, + * Copyright (C) Jacob Hoffman-Andrews, * <github@hoffman-andrews.com> * * This software is licensed as described in the file COPYING, which @@ -150,6 +150,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, size_t plain_bytes_copied = 0; rustls_result rresult = 0; char errorbuf[255]; + size_t errorlen; rustls_io_result io_error; DEBUGASSERT(backend); @@ -161,7 +162,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, io_error = rustls_connection_read_tls(rconn, read_cb, &io_ctx, &tls_bytes_read); if(io_error == EAGAIN || io_error == EWOULDBLOCK) { - infof(data, CFMSG(cf, "cr_recv: EAGAIN or EWOULDBLOCK")); + DEBUGF(LOG_CF(data, cf, "cr_recv: EAGAIN or EWOULDBLOCK")); } else if(io_error) { char buffer[STRERROR_LEN]; @@ -171,12 +172,13 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, return -1; } - infof(data, CFMSG(cf, "cr_recv: read %ld TLS bytes"), tls_bytes_read); + DEBUGF(LOG_CF(data, cf, "cr_recv: read %ld TLS bytes", tls_bytes_read)); rresult = rustls_connection_process_new_packets(rconn); if(rresult != RUSTLS_RESULT_OK) { - rustls_error(rresult, errorbuf, sizeof(errorbuf), &n); - failf(data, "%.*s", n, errorbuf); + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_connection_process_new_packets: %.*s", + errorlen, errorbuf); *err = map_error(rresult); return -1; } @@ -189,14 +191,21 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, plainlen - plain_bytes_copied, &n); if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) { - infof(data, CFMSG(cf, "cr_recv: got PLAINTEXT_EMPTY. " - "will try again later.")); + DEBUGF(LOG_CF(data, cf, "cr_recv: got PLAINTEXT_EMPTY. " + "will try again later.")); backend->data_pending = FALSE; break; } + else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) { + failf(data, "rustls: peer closed TCP connection " + "without first closing TLS connection"); + *err = CURLE_READ_ERROR; + return -1; + } else if(rresult != RUSTLS_RESULT_OK) { /* n always equals 0 in this case, don't need to check it */ - failf(data, "error in rustls_connection_read: %d", rresult); + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_connection_read: %.*s", errorlen, errorbuf); *err = CURLE_READ_ERROR; return -1; } @@ -207,7 +216,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, break; } else { - infof(data, CFMSG(cf, "cr_recv: got %ld plain bytes"), n); + DEBUGF(LOG_CF(data, cf, "cr_recv: got %ld plain bytes", n)); plain_bytes_copied += n; } } @@ -254,22 +263,25 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, size_t tlswritten_total = 0; rustls_result rresult; rustls_io_result io_error; + char errorbuf[256]; + size_t errorlen; DEBUGASSERT(backend); rconn = backend->conn; - infof(data, CFMSG(cf, "cr_send: %ld plain bytes"), plainlen); + DEBUGF(LOG_CF(data, cf, "cr_send: %ld plain bytes", plainlen)); if(plainlen > 0) { rresult = rustls_connection_write(rconn, plainbuf, plainlen, &plainwritten); if(rresult != RUSTLS_RESULT_OK) { - failf(data, "error in rustls_connection_write"); + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_connection_write: %.*s", errorlen, errorbuf); *err = CURLE_WRITE_ERROR; return -1; } else if(plainwritten == 0) { - failf(data, "EOF in rustls_connection_write"); + failf(data, "rustls_connection_write: EOF"); *err = CURLE_WRITE_ERROR; return -1; } @@ -282,8 +294,8 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx, &tlswritten); if(io_error == EAGAIN || io_error == EWOULDBLOCK) { - infof(data, CFMSG(cf, "cr_send: EAGAIN after %ld bytes"), - tlswritten_total); + DEBUGF(LOG_CF(data, cf, "cr_send: EAGAIN after %zu bytes", + tlswritten_total)); *err = CURLE_AGAIN; return -1; } @@ -299,7 +311,7 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data, *err = CURLE_WRITE_ERROR; return -1; } - infof(data, CFMSG(cf, "cr_send: wrote %ld TLS bytes"), tlswritten); + DEBUGF(LOG_CF(data, cf, "cr_send: wrote %zu TLS bytes", tlswritten)); tlswritten_total += tlswritten; } @@ -349,22 +361,25 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, char errorbuf[256]; size_t errorlen; int result; - rustls_slice_bytes alpn[2] = { - { (const uint8_t *)ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH }, - { (const uint8_t *)ALPN_H2, ALPN_H2_LENGTH }, - }; DEBUGASSERT(backend); rconn = backend->conn; config_builder = rustls_client_config_builder_new(); -#ifdef USE_HTTP2 - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 2); -#else - rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 1); -#endif - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); + if(connssl->alpn) { + struct alpn_proto_buf proto; + rustls_slice_bytes alpn[ALPN_ENTRIES_MAX]; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + alpn[i].data = (const uint8_t *)connssl->alpn->entries[i]; + alpn[i].len = strlen(connssl->alpn->entries[i]); + } + rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, + connssl->alpn->count); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } if(!verifypeer) { rustls_client_config_builder_dangerous_set_certificate_verifier( config_builder, cr_verify_none); @@ -384,7 +399,7 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, result = rustls_root_cert_store_add_pem(roots, ca_info_blob->data, ca_info_blob->len, verifypeer); if(result != RUSTLS_RESULT_OK) { - failf(data, "failed to parse trusted certificates from blob"); + failf(data, "rustls: failed to parse trusted certificates from blob"); rustls_root_cert_store_free(roots); rustls_client_config_free( rustls_client_config_builder_build(config_builder)); @@ -394,7 +409,7 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, result = rustls_client_config_builder_use_roots(config_builder, roots); rustls_root_cert_store_free(roots); if(result != RUSTLS_RESULT_OK) { - failf(data, "failed to load trusted certificates"); + failf(data, "rustls: failed to load trusted certificates"); rustls_client_config_free( rustls_client_config_builder_build(config_builder)); return CURLE_SSL_CACERT_BADFILE; @@ -404,7 +419,7 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, result = rustls_client_config_builder_load_roots_from_file( config_builder, ssl_cafile); if(result != RUSTLS_RESULT_OK) { - failf(data, "failed to load trusted certificates"); + failf(data, "rustls: failed to load trusted certificates"); rustls_client_config_free( rustls_client_config_builder_build(config_builder)); return CURLE_SSL_CACERT_BADFILE; @@ -416,7 +431,7 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, { char *snihost = Curl_ssl_snihost(data, hostname, NULL); if(!snihost) { - failf(data, "Failed to set SNI"); + failf(data, "rustls: failed to get SNI"); return CURLE_SSL_CONNECT_ERROR; } result = rustls_client_connection_new(backend->config, snihost, &rconn); @@ -439,29 +454,7 @@ cr_set_negotiated_alpn(struct Curl_cfilter *cf, struct Curl_easy *data, size_t len = 0; rustls_connection_get_alpn_protocol(rconn, &protocol, &len); - if(!protocol) { - infof(data, VTLS_INFOF_NO_ALPN); - return; - } - -#ifdef USE_HTTP2 - if(len == ALPN_H2_LENGTH && 0 == memcmp(ALPN_H2, protocol, len)) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_H2); - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(len == ALPN_HTTP_1_1_LENGTH && - 0 == memcmp(ALPN_HTTP_1_1, protocol, len)) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_HTTP_1_1); - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - else { - infof(data, "ALPN, negotiated an unrecognized protocol"); - } - - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, protocol, len); } static CURLcode @@ -469,7 +462,7 @@ cr_connect_nonblocking(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { struct ssl_connect_data *const connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); struct ssl_backend_data *const backend = connssl->backend; struct rustls_connection *rconn = NULL; CURLcode tmperr = CURLE_OK; @@ -573,7 +566,7 @@ cr_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *socks) { struct ssl_connect_data *const connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); struct ssl_backend_data *const backend = connssl->backend; struct rustls_connection *rconn = NULL; @@ -616,7 +609,7 @@ cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) rustls_connection_send_close_notify(backend->conn); n = cr_send(cf, data, NULL, 0, &tmperr); if(n < 0) { - failf(data, "error sending close notify: %d", tmperr); + failf(data, "rustls: error sending close_notify: %d", tmperr); } rustls_connection_free(backend->conn); diff --git a/Utilities/cmcurl/lib/vtls/rustls.h b/Utilities/cmcurl/lib/vtls/rustls.h index 6b393dd639..bfbe23de3e 100644 --- a/Utilities/cmcurl/lib/vtls/rustls.h +++ b/Utilities/cmcurl/lib/vtls/rustls.h @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews, + * Copyright (C) Jacob Hoffman-Andrews, * <github@hoffman-andrews.com> * * This software is licensed as described in the file COPYING, which diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c index 7eab9542af..452fa409a5 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.c +++ b/Utilities/cmcurl/lib/vtls/schannel.c @@ -5,9 +5,9 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de> - * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com> + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Marc Hoersken, <info@marc-hoersken.de> + * Copyright (C) Mark Salisbury, <mark.salisbury@hp.com> * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -1105,7 +1105,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef HAS_ALPN /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above. Also it doesn't seem to be supported for Wine, see curl bug #983. */ - backend->use_alpn = cf->conn->bits.tls_enable_alpn && + backend->use_alpn = connssl->alpn && !GetProcAddress(GetModuleHandle(TEXT("ntdll")), "wine_get_version") && curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, @@ -1196,6 +1196,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) int list_start_index = 0; unsigned int *extension_len = NULL; unsigned short* list_len = NULL; + struct alpn_proto_buf proto; /* The first four bytes will be an unsigned int indicating number of bytes of data in the rest of the buffer. */ @@ -1215,25 +1216,22 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) list_start_index = cur; -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2) { - alpn_buffer[cur++] = ALPN_H2_LENGTH; - memcpy(&alpn_buffer[cur], ALPN_H2, ALPN_H2_LENGTH); - cur += ALPN_H2_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; } -#endif - - alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); + memcpy(&alpn_buffer[cur], proto.data, proto.len); + cur += proto.len; *list_len = curlx_uitous(cur - list_start_index); *extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short); InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); + + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } else { InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); @@ -1727,40 +1725,23 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) if(alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { - unsigned char alpn = 0; - - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, - alpn_result.ProtocolIdSize, alpn_result.ProtocolId); + unsigned char prev_alpn = cf->conn->alpn; -#ifdef USE_HTTP2 - if(alpn_result.ProtocolIdSize == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, alpn_result.ProtocolId, ALPN_H2_LENGTH)) { - alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId, - ALPN_HTTP_1_1_LENGTH)) { - alpn = CURL_HTTP_VERSION_1_1; - } + Curl_alpn_set_negotiated(cf, data, alpn_result.ProtocolId, + alpn_result.ProtocolIdSize); if(backend->recv_renegotiating) { - if(alpn != cf->conn->alpn) { + if(prev_alpn != cf->conn->alpn && + prev_alpn != CURL_HTTP_VERSION_NONE) { + /* Renegotiation selected a different protocol now, we cannot + * deal with this */ failf(data, "schannel: server selected an ALPN protocol too late"); return CURLE_SSL_CONNECT_ERROR; } } - else - cf->conn->alpn = alpn; } else { if(!backend->recv_renegotiating) - infof(data, VTLS_INFOF_NO_ALPN); - } - - if(!backend->recv_renegotiating) { - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, NULL, 0); } } #endif @@ -1841,7 +1822,7 @@ schannel_connect_common(struct Curl_cfilter *cf, { CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); timediff_t timeout_ms; int what; @@ -2056,7 +2037,7 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, } else if(!timeout_ms) timeout_ms = TIMEDIFF_T_MAX; - what = SOCKET_WRITABLE(cf->conn->sock[cf->sockindex], timeout_ms); + what = SOCKET_WRITABLE(Curl_conn_cf_get_socket(cf, data), timeout_ms); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); diff --git a/Utilities/cmcurl/lib/vtls/schannel.h b/Utilities/cmcurl/lib/vtls/schannel.h index 6d4235a96c..7fae39fa0a 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.h +++ b/Utilities/cmcurl/lib/vtls/schannel.h @@ -7,8 +7,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012, Marc Hoersken, <info@marc-hoersken.de>, et al. - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>, et al. + * 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 @@ -179,8 +179,8 @@ struct ssl_backend_data { size_t encdata_offset, decdata_offset; unsigned char *encdata_buffer, *decdata_buffer; /* encdata_is_incomplete: if encdata contains only a partial record that - can't be decrypted without another Curl_read_plain (that is, status is - SEC_E_INCOMPLETE_MESSAGE) then set this true. after Curl_read_plain writes + can't be decrypted without another recv() (that is, status is + SEC_E_INCOMPLETE_MESSAGE) then set this true. after an recv() adds more bytes into encdata then set this back to false. */ bool encdata_is_incomplete; unsigned long req_flags, ret_flags; diff --git a/Utilities/cmcurl/lib/vtls/schannel_verify.c b/Utilities/cmcurl/lib/vtls/schannel_verify.c index e4992162e6..d75ee8dfe7 100644 --- a/Utilities/cmcurl/lib/vtls/schannel_verify.c +++ b/Utilities/cmcurl/lib/vtls/schannel_verify.c @@ -5,9 +5,9 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de> - * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com> - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Marc Hoersken, <info@marc-hoersken.de> + * Copyright (C) Mark Salisbury, <mark.salisbury@hp.com> + * 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 diff --git a/Utilities/cmcurl/lib/vtls/sectransp.c b/Utilities/cmcurl/lib/vtls/sectransp.c index d903c53d10..2e98169e26 100644 --- a/Utilities/cmcurl/lib/vtls/sectransp.c +++ b/Utilities/cmcurl/lib/vtls/sectransp.c @@ -5,8 +5,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2012 - 2017, Nick Zitzmann, <nickzman@gmail.com>. + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Nick Zitzmann, <nickzman@gmail.com>. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -137,14 +137,6 @@ #include "memdebug.h" -#define DEBUG_CF 0 - -#if DEBUG_CF -#define CF_DEBUGF(x) x -#else -#define CF_DEBUGF(x) do { } while(0) -#endif - /* From MacTypes.h (which we can't include because it isn't present in iOS: */ #define ioErr -36 #define paramErr -50 @@ -840,15 +832,15 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection, struct Curl_cfilter *cf = (struct Curl_cfilter *)connection; struct ssl_connect_data *connssl = cf->ctx; struct ssl_backend_data *backend = connssl->backend; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result; OSStatus rtn = noErr; DEBUGASSERT(data); nread = Curl_conn_cf_recv(cf->next, data, buf, *dataLength, &result); - CF_DEBUGF(infof(data, CFMSG(cf, "bio_read(len=%zu) -> %zd, result=%d"), - *dataLength, nread, result)); + DEBUGF(LOG_CF(data, cf, "bio_read(len=%zu) -> %zd, result=%d", + *dataLength, nread, result)); if(nread < 0) { switch(result) { case CURLE_OK: @@ -876,15 +868,15 @@ static OSStatus bio_cf_out_write(SSLConnectionRef connection, struct Curl_cfilter *cf = (struct Curl_cfilter *)connection; struct ssl_connect_data *connssl = cf->ctx; struct ssl_backend_data *backend = connssl->backend; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result; OSStatus rtn = noErr; DEBUGASSERT(data); nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, &result); - CF_DEBUGF(infof(data, CFMSG(cf, "bio_send(len=%zu) -> %zd, result=%d"), - *dataLength, nwritten, result)); + DEBUGF(LOG_CF(data, cf, "bio_send(len=%zu) -> %zd, result=%d", + *dataLength, nwritten, result)); if(nwritten <= 0) { if(result == CURLE_AGAIN) { rtn = errSSLWouldBlock; @@ -1644,7 +1636,6 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, const bool verifypeer = conn_config->verifypeer; char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; - bool isproxy = Curl_ssl_cf_is_proxy(cf); #ifdef ENABLE_IPV6 struct in6_addr addr; #else @@ -1657,7 +1648,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, DEBUGASSERT(backend); - CF_DEBUGF(infof(data, CFMSG(cf, "connect_step1"))); + DEBUGF(LOG_CF(data, cf, "connect_step1")); GetDarwinVersionNumber(&darwinver_maj, &darwinver_min); #endif /* CURL_BUILD_MAC */ @@ -1805,33 +1796,28 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ #if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(cf->conn->bits.tls_enable_alpn) { + if(connssl->alpn) { if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { + struct alpn_proto_buf proto; + size_t i; + CFStringRef cstr; CFMutableArrayRef alpnArr = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!isproxy || !cf->conn->bits.tunnel_proxy) -#endif - ) { - CFArrayAppendValue(alpnArr, CFSTR(ALPN_H2)); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); + for(i = 0; i < connssl->alpn->count; ++i) { + cstr = CFStringCreateWithCString(NULL, connssl->alpn->entries[i], + kCFStringEncodingUTF8); + if(!cstr) + return CURLE_OUT_OF_MEMORY; + CFArrayAppendValue(alpnArr, cstr); + CFRelease(cstr); } -#endif - - CFArrayAppendValue(alpnArr, CFSTR(ALPN_HTTP_1_1)); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - /* expects length prefixed preference ordered list of protocols in wire - * format - */ err = SSLSetALPNProtocols(backend->ssl_ctx, alpnArr); if(err != noErr) infof(data, "WARNING: failed to set ALPN protocols; OSStatus %d", err); CFRelease(alpnArr); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } } #endif @@ -2302,7 +2288,7 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, /* This is not a PEM file, probably a certificate in DER format. */ rc = append_cert_to_array(data, certbuf, buflen, array); if(rc != CURLE_OK) { - CF_DEBUGF(infof(data, CFMSG(cf, "append_cert for CA failed"))); + DEBUGF(LOG_CF(data, cf, "append_cert for CA failed")); result = rc; goto out; } @@ -2316,7 +2302,7 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, rc = append_cert_to_array(data, der, derlen, array); free(der); if(rc != CURLE_OK) { - CF_DEBUGF(infof(data, CFMSG(cf, "append_cert for CA failed"))); + DEBUGF(LOG_CF(data, cf, "append_cert for CA failed")); result = rc; goto out; } @@ -2332,7 +2318,7 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, goto out; } - CF_DEBUGF(infof(data, CFMSG(cf, "setting %d trust anchors"), n)); + DEBUGF(LOG_CF(data, cf, "setting %d trust anchors", n)); ret = SecTrustSetAnchorCertificates(trust, array); if(ret != noErr) { failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret); @@ -2354,11 +2340,11 @@ static CURLcode verify_cert_buf(struct Curl_cfilter *cf, switch(trust_eval) { case kSecTrustResultUnspecified: /* what does this really mean? */ - CF_DEBUGF(infof(data, CFMSG(cf, "trust result: Unspecified"))); + DEBUGF(LOG_CF(data, cf, "trust result: Unspecified")); result = CURLE_OK; goto out; case kSecTrustResultProceed: - CF_DEBUGF(infof(data, CFMSG(cf, "trust result: Proceed"))); + DEBUGF(LOG_CF(data, cf, "trust result: Proceed")); result = CURLE_OK; goto out; @@ -2391,7 +2377,7 @@ static CURLcode verify_cert(struct Curl_cfilter *cf, size_t buflen; if(ca_info_blob) { - CF_DEBUGF(infof(data, CFMSG(cf, "verify_peer, CA from config blob"))); + DEBUGF(LOG_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; @@ -2401,8 +2387,7 @@ static CURLcode verify_cert(struct Curl_cfilter *cf, certbuf[ca_info_blob->len]='\0'; } else if(cafile) { - CF_DEBUGF(infof(data, CFMSG(cf, "verify_peer, CA from file '%s'"), - cafile)); + DEBUGF(LOG_CF(data, cf, "verify_peer, CA from file '%s'", cafile)); if(read_cert(cafile, &certbuf, &buflen) < 0) { failf(data, "SSL: failed to read or invalid CA certificate"); return CURLE_SSL_CACERT_BADFILE; @@ -2538,7 +2523,7 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf, || ssl_connect_2_reading == connssl->connecting_state || ssl_connect_2_writing == connssl->connecting_state); DEBUGASSERT(backend); - CF_DEBUGF(infof(data, CFMSG(cf, "connect_step2"))); + DEBUGF(LOG_CF(data, cf, "connect_step2")); /* Here goes nothing: */ check_handshake: @@ -3003,7 +2988,7 @@ static CURLcode sectransp_connect_step3(struct Curl_cfilter *cf, { struct ssl_connect_data *connssl = cf->ctx; - CF_DEBUGF(infof(data, CFMSG(cf, "connect_step3"))); + DEBUGF(LOG_CF(data, cf, "connect_step3")); /* There is no step 3! * Well, okay, let's collect server certificates, and if verbose mode is on, * let's print the details of the server certificates. */ @@ -3022,7 +3007,7 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, { CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); int what; /* check if the connection has already been established */ @@ -3112,7 +3097,7 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, } if(ssl_connect_done == connssl->connecting_state) { - CF_DEBUGF(infof(data, CFMSG(cf, "connected"))); + DEBUGF(LOG_CF(data, cf, "connected")); connssl->state = ssl_connection_complete; *done = TRUE; } @@ -3158,7 +3143,7 @@ static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGASSERT(backend); if(backend->ssl_ctx) { - CF_DEBUGF(infof(data, CFMSG(cf, "close"))); + DEBUGF(LOG_CF(data, cf, "close")); (void)SSLClose(backend->ssl_ctx); #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS if(SSLCreateContext) @@ -3200,9 +3185,10 @@ static int sectransp_shutdown(struct Curl_cfilter *cf, rc = 0; - what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], SSL_SHUTDOWN_TIMEOUT); + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), + SSL_SHUTDOWN_TIMEOUT); - CF_DEBUGF(infof(data, CFMSG(cf, "shutdown"))); + DEBUGF(LOG_CF(data, cf, "shutdown")); while(loop--) { if(what < 0) { /* anything that gets here is fatally bad */ @@ -3229,7 +3215,7 @@ static int sectransp_shutdown(struct Curl_cfilter *cf, if(nread <= 0) break; - what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0); + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); } return rc; @@ -3271,7 +3257,7 @@ static int sectransp_check_cxn(struct Curl_cfilter *cf, DEBUGASSERT(backend); if(backend->ssl_ctx) { - CF_DEBUGF(infof(data, CFMSG(cf, "check connection"))); + DEBUGF(LOG_CF(data, cf, "check connection")); err = SSLGetSessionState(backend->ssl_ctx, &state); if(err == noErr) return state == kSSLConnected || state == kSSLHandshake; @@ -3292,7 +3278,7 @@ static bool sectransp_data_pending(struct Curl_cfilter *cf, DEBUGASSERT(backend); if(backend->ssl_ctx) { /* SSL is in use */ - CF_DEBUGF(infof(data, CFMSG(cf, "data_pending"))); + DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data_pending")); err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer); if(err == noErr) return buffer > 0UL; diff --git a/Utilities/cmcurl/lib/vtls/sectransp.h b/Utilities/cmcurl/lib/vtls/sectransp.h index 2d53b7c480..0f1085ad91 100644 --- a/Utilities/cmcurl/lib/vtls/sectransp.h +++ b/Utilities/cmcurl/lib/vtls/sectransp.h @@ -7,8 +7,8 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2014, Nick Zitzmann, <nickzman@gmail.com>. - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) Nick Zitzmann, <nickzman@gmail.com>. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/vtls.c b/Utilities/cmcurl/lib/vtls/vtls.c index 873ee6bac7..15f6844c7b 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.c +++ b/Utilities/cmcurl/lib/vtls/vtls.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -73,6 +73,7 @@ #include "curl_memory.h" #include "memdebug.h" + /* convenience macro to check if this handle is using a shared SSL session */ #define SSLSESSION_SHARED(data) (data->share && \ (data->share->specifier & \ @@ -150,7 +151,6 @@ Curl_ssl_config_matches(struct ssl_primary_config *data, #ifdef USE_TLS_SRP !Curl_timestrcmp(data->username, needle->username) && !Curl_timestrcmp(data->password, needle->password) && - (data->authtype == needle->authtype) && #endif strcasecompare(data->cipher_list, needle->cipher_list) && strcasecompare(data->cipher_list13, needle->cipher_list13) && @@ -173,9 +173,6 @@ Curl_clone_primary_ssl_config(struct ssl_primary_config *source, dest->verifystatus = source->verifystatus; dest->sessionid = source->sessionid; dest->ssl_options = source->ssl_options; -#ifdef USE_TLS_SRP - dest->authtype = source->authtype; -#endif CLONE_BLOB(cert_blob); CLONE_BLOB(ca_info_blob); @@ -272,8 +269,8 @@ void Curl_ssl_cleanup(void) static bool ssl_prefs_check(struct Curl_easy *data) { /* check for CURLOPT_SSLVERSION invalid parameter value */ - const long sslver = data->set.ssl.primary.version; - if((sslver < 0) || (sslver >= CURL_SSLVERSION_LAST)) { + const unsigned char sslver = data->set.ssl.primary.version; + if(sslver >= CURL_SSLVERSION_LAST) { failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION"); return FALSE; } @@ -293,7 +290,8 @@ static bool ssl_prefs_check(struct Curl_easy *data) return TRUE; } -static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data) +static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data, + const struct alpn_spec *alpn) { struct ssl_connect_data *ctx; @@ -302,6 +300,7 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data) if(!ctx) return NULL; + ctx->alpn = alpn; ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data); if(!ctx->backend) { free(ctx); @@ -318,13 +317,6 @@ static void cf_ctx_free(struct ssl_connect_data *ctx) } } -static void cf_ctx_set_data(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - if(cf->ctx) - ((struct ssl_connect_data *)cf->ctx)->call_data = data; -} - static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; @@ -339,7 +331,6 @@ static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data) result = Curl_ssl->connect_blocking(cf, data); if(!result) { - Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */ DEBUGASSERT(connssl->state == ssl_connection_complete); } @@ -615,19 +606,20 @@ int Curl_ssl_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *socks) { struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); - (void)data; - if(connssl->connecting_state == ssl_connect_2_writing) { - /* write mode */ - socks[0] = cf->conn->sock[FIRSTSOCKET]; - return GETSOCK_WRITESOCK(0); - } - if(connssl->connecting_state == ssl_connect_2_reading) { - /* read mode */ - socks[0] = cf->conn->sock[FIRSTSOCKET]; - return GETSOCK_READSOCK(0); + if(sock != CURL_SOCKET_BAD) { + if(connssl->connecting_state == ssl_connect_2_writing) { + /* write mode */ + socks[0] = sock; + return GETSOCK_WRITESOCK(0); + } + if(connssl->connecting_state == ssl_connect_2_reading) { + /* read mode */ + socks[0] = sock; + return GETSOCK_READSOCK(0); + } } - return GETSOCK_BLANK; } @@ -685,20 +677,6 @@ void Curl_ssl_version(char *buffer, size_t size) #endif } -/* - * This function tries to determine connection status. - * - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn) -{ - struct Curl_cfilter *cf = Curl_ssl_cf_get_ssl(conn->cfilter[FIRSTSOCKET]); - return cf? Curl_ssl->check_cxn(cf, data) : -1; -} - void Curl_ssl_free_certinfo(struct Curl_easy *data) { struct curl_certinfo *ci = &data->info.certs; @@ -1430,53 +1408,80 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, #ifdef USE_SSL +static void free_hostname(struct ssl_connect_data *connssl) +{ + if(connssl->dispname != connssl->hostname) + free(connssl->dispname); + free(connssl->hostname); + connssl->hostname = connssl->dispname = NULL; +} + static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - /* TODO: close_one closes BOTH conn->ssl AND conn->proxy_ssl for this - * sockindex (if in use). Gladly, it is safe to call more than once. */ if(connssl) { Curl_ssl->close(cf, data); connssl->state = ssl_connection_none; + free_hostname(connssl); } cf->connected = FALSE; } -static void reinit_hostname(struct Curl_cfilter *cf) +static CURLcode reinit_hostname(struct Curl_cfilter *cf) { struct ssl_connect_data *connssl = cf->ctx; + const char *ehostname, *edispname; + int eport; + /* We need the hostname for SNI negotiation. Once handshaked, this + * remains the SNI hostname for the TLS connection. But when the + * connection is reused, the settings in cf->conn might change. + * So we keep a copy of the hostname we use for SNI. + */ #ifndef CURL_DISABLE_PROXY if(Curl_ssl_cf_is_proxy(cf)) { - /* TODO: there is not definition for a proxy setup on a secondary conn */ - connssl->hostname = cf->conn->http_proxy.host.name; - connssl->dispname = cf->conn->http_proxy.host.dispname; - connssl->port = cf->conn->http_proxy.port; + ehostname = cf->conn->http_proxy.host.name; + edispname = cf->conn->http_proxy.host.dispname; + eport = cf->conn->http_proxy.port; } else #endif { - /* TODO: secondaryhostname is set to the IP address we connect to - * in the FTP handler, it is assumed that host verification uses the - * hostname from FIRSTSOCKET */ - if(cf->sockindex == SECONDARYSOCKET && 0) { - connssl->hostname = cf->conn->secondaryhostname; - connssl->dispname = connssl->hostname; - connssl->port = cf->conn->secondary_port; + ehostname = cf->conn->host.name; + edispname = cf->conn->host.dispname; + eport = cf->conn->remote_port; + } + + /* change if ehostname changed */ + if(ehostname && (!connssl->hostname + || strcmp(ehostname, connssl->hostname))) { + free_hostname(connssl); + connssl->hostname = strdup(ehostname); + if(!connssl->hostname) { + free_hostname(connssl); + return CURLE_OUT_OF_MEMORY; } + if(!edispname || !strcmp(ehostname, edispname)) + connssl->dispname = connssl->hostname; else { - connssl->hostname = cf->conn->host.name; - connssl->dispname = cf->conn->host.dispname; - connssl->port = cf->conn->remote_port; + connssl->dispname = strdup(edispname); + if(!connssl->dispname) { + free_hostname(connssl); + return CURLE_OUT_OF_MEMORY; + } } } - DEBUGASSERT(connssl->hostname); + connssl->port = eport; + return CURLE_OK; } static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - cf_ctx_set_data(cf, data); + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); cf_close(cf, data); + CF_DATA_RESTORE(cf, save); cf_ctx_free(cf->ctx); cf->ctx = NULL; } @@ -1484,10 +1489,12 @@ static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) static void ssl_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - cf_ctx_set_data(cf, data); + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); cf_close(cf, data); cf->next->cft->close(cf->next, data); - cf_ctx_set_data(cf, NULL); + CF_DATA_RESTORE(cf, save); } static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, @@ -1495,6 +1502,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, bool blocking, bool *done) { struct ssl_connect_data *connssl = cf->ctx; + struct cf_call_data save; CURLcode result; if(cf->connected) { @@ -1502,7 +1510,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, return CURLE_OK; } - cf_ctx_set_data(cf, data); + CF_DATA_SAVE(save, cf, data); (void)connssl; DEBUGASSERT(data->conn); DEBUGASSERT(data->conn == cf->conn); @@ -1513,10 +1521,10 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, if(result || !*done) goto out; - /* TODO: right now we do not fully control when hostname is set, - * assign it on each connect call. */ - reinit_hostname(cf); *done = FALSE; + result = reinit_hostname(cf); + if(result) + goto out; if(blocking) { result = ssl_connect(cf, data); @@ -1528,26 +1536,26 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, if(!result && *done) { cf->connected = TRUE; - if(cf->sockindex == FIRSTSOCKET && !Curl_ssl_cf_is_proxy(cf)) - Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */ + connssl->handshake_done = Curl_now(); DEBUGASSERT(connssl->state == ssl_connection_complete); } out: - cf_ctx_set_data(cf, NULL); + CF_DATA_RESTORE(cf, save); return result; } static bool ssl_cf_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { + struct cf_call_data save; bool result; - cf_ctx_set_data(cf, (struct Curl_easy *)data); - if(cf->ctx && Curl_ssl->data_pending(cf, data)) + CF_DATA_SAVE(save, cf, data); + if(Curl_ssl->data_pending(cf, data)) result = TRUE; else result = cf->next->cft->has_data_pending(cf->next, data); - cf_ctx_set_data(cf, NULL); + CF_DATA_RESTORE(cf, save); return result; } @@ -1555,12 +1563,13 @@ static ssize_t ssl_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, const void *buf, size_t len, CURLcode *err) { + struct cf_call_data save; ssize_t nwritten; + CF_DATA_SAVE(save, cf, data); *err = CURLE_OK; - cf_ctx_set_data(cf, data); nwritten = Curl_ssl->send_plain(cf, data, buf, len, err); - cf_ctx_set_data(cf, NULL); + CF_DATA_RESTORE(cf, save); return nwritten; } @@ -1568,12 +1577,13 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { + struct cf_call_data save; ssize_t nread; + CF_DATA_SAVE(save, cf, data); *err = CURLE_OK; - cf_ctx_set_data(cf, data); nread = Curl_ssl->recv_plain(cf, data, buf, len, err); - cf_ctx_set_data(cf, NULL); + CF_DATA_RESTORE(cf, save); return nread; } @@ -1581,39 +1591,72 @@ static int ssl_cf_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *socks) { + struct cf_call_data save; int result; - cf_ctx_set_data(cf, data); + CF_DATA_SAVE(save, cf, data); result = Curl_ssl->get_select_socks(cf, data, socks); - cf_ctx_set_data(cf, NULL); + CF_DATA_RESTORE(cf, save); return result; } -static void ssl_cf_attach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) +static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) { - if(Curl_ssl->attach_data) { - cf_ctx_set_data(cf, data); - Curl_ssl->attach_data(cf, data); - cf_ctx_set_data(cf, NULL); + struct ssl_connect_data *connssl = cf->ctx; + struct cf_call_data save; + + (void)arg1; + (void)arg2; + switch(event) { + case CF_CTRL_CONN_REPORT_STATS: + if(cf->sockindex == FIRSTSOCKET && !Curl_ssl_cf_is_proxy(cf)) + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + break; + case CF_CTRL_DATA_ATTACH: + if(Curl_ssl->attach_data) { + CF_DATA_SAVE(save, cf, data); + Curl_ssl->attach_data(cf, data); + CF_DATA_RESTORE(cf, save); + } + break; + case CF_CTRL_DATA_DETACH: + if(Curl_ssl->detach_data) { + CF_DATA_SAVE(save, cf, data); + Curl_ssl->detach_data(cf, data); + CF_DATA_RESTORE(cf, save); + } + break; + default: + break; } + return CURLE_OK; } -static void ssl_cf_detach_data(struct Curl_cfilter *cf, - struct Curl_easy *data) +static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data) { - if(Curl_ssl->detach_data) { - cf_ctx_set_data(cf, data); - Curl_ssl->detach_data(cf, data); - cf_ctx_set_data(cf, NULL); - } + struct cf_call_data save; + bool result; + /* + * This function tries to determine connection status. + * + * Return codes: + * 1 means the connection is still in place + * 0 means the connection has been closed + * -1 means the connection status is unknown + */ + CF_DATA_SAVE(save, cf, data); + result = Curl_ssl->check_cxn(cf, data) != 0; + CF_DATA_RESTORE(cf, save); + return result; } -static const struct Curl_cftype cft_ssl = { +struct Curl_cftype Curl_cft_ssl = { "SSL", CF_TYPE_SSL, + CURL_LOG_DEFAULT, ssl_cf_destroy, - Curl_cf_def_setup, ssl_cf_connect, ssl_cf_close, Curl_cf_def_get_host, @@ -1621,15 +1664,17 @@ static const struct Curl_cftype cft_ssl = { ssl_cf_data_pending, ssl_cf_send, ssl_cf_recv, - ssl_cf_attach_data, - ssl_cf_detach_data, + ssl_cf_cntrl, + cf_ssl_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, }; -static const struct Curl_cftype cft_ssl_proxy = { +struct Curl_cftype Curl_cft_ssl_proxy = { "SSL-PROXY", CF_TYPE_SSL, + CURL_LOG_DEFAULT, ssl_cf_destroy, - Curl_cf_def_setup, ssl_cf_connect, ssl_cf_close, Curl_cf_def_get_host, @@ -1637,65 +1682,107 @@ static const struct Curl_cftype cft_ssl_proxy = { ssl_cf_data_pending, ssl_cf_send, ssl_cf_recv, - ssl_cf_attach_data, - ssl_cf_detach_data, + ssl_cf_cntrl, + cf_ssl_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, }; -CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) +static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn) { - struct Curl_cfilter *cf; + struct Curl_cfilter *cf = NULL; struct ssl_connect_data *ctx; CURLcode result; DEBUGASSERT(data->conn); - ctx = cf_ctx_new(data); + + ctx = cf_ctx_new(data, Curl_alpn_get_spec(data, conn)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - result = Curl_cf_create(&cf, &cft_ssl, ctx); - if(result) - goto out; - - Curl_conn_cf_add(data, conn, sockindex, cf); - - result = CURLE_OK; + result = Curl_cf_create(&cf, &Curl_cft_ssl, ctx); out: if(result) cf_ctx_free(ctx); + *pcf = result? NULL : cf; return result; } -#ifndef CURL_DISABLE_PROXY -CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) +CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) { struct Curl_cfilter *cf; + CURLcode result; + + result = cf_ssl_create(&cf, data, conn); + if(!result) + Curl_conn_cf_add(data, conn, sockindex, cf); + return result; +} + +CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + result = cf_ssl_create(&cf, data, cf_at->conn); + if(!result) + Curl_conn_cf_insert_after(cf_at, cf); + return result; +} + +#ifndef CURL_DISABLE_PROXY +static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn) +{ + struct Curl_cfilter *cf = NULL; struct ssl_connect_data *ctx; CURLcode result; - ctx = cf_ctx_new(data); + ctx = cf_ctx_new(data, Curl_alpn_get_proxy_spec(data, conn)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } + result = Curl_cf_create(&cf, &Curl_cft_ssl_proxy, ctx); - result = Curl_cf_create(&cf, &cft_ssl_proxy, ctx); +out: if(result) - goto out; + cf_ctx_free(ctx); + *pcf = result? NULL : cf; + return result; +} - Curl_conn_cf_add(data, conn, sockindex, cf); +CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + struct Curl_cfilter *cf; + CURLcode result; - result = CURLE_OK; + result = cf_ssl_proxy_create(&cf, data, conn); + if(!result) + Curl_conn_cf_add(data, conn, sockindex, cf); + return result; +} -out: - if(result) - cf_ctx_free(ctx); +CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data) +{ + struct Curl_cfilter *cf; + CURLcode result; + + result = cf_ssl_proxy_create(&cf, data, cf_at->conn); + if(!result) + Curl_conn_cf_insert_after(cf_at, cf); return result; } @@ -1717,9 +1804,10 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, /* get first filter in chain, if any is present */ cf = Curl_ssl_cf_get_ssl(data->conn->cfilter[sockindex]); if(cf) { - cf_ctx_set_data(cf, data); + struct cf_call_data save; + CF_DATA_SAVE(save, cf, data); result = Curl_ssl->get_internals(cf->ctx, info); - cf_ctx_set_data(cf, NULL); + CF_DATA_RESTORE(cf, save); } } return result; @@ -1733,7 +1821,7 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, (void)data; for(; cf; cf = cf->next) { - if(cf->cft == &cft_ssl) { + if(cf->cft == &Curl_cft_ssl) { if(Curl_ssl->shut_down(cf, data)) result = CURLE_SSL_SHUTDOWN_FAILED; Curl_conn_cf_discard(cf, data); @@ -1749,7 +1837,7 @@ static struct Curl_cfilter *get_ssl_cf_engaged(struct connectdata *conn, struct Curl_cfilter *cf, *lowest_ssl_cf = NULL; for(cf = conn->cfilter[sockindex]; cf; cf = cf->next) { - if(cf->cft == &cft_ssl || cf->cft == &cft_ssl_proxy) { + if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) { lowest_ssl_cf = cf; if(cf->connected || (cf->next && cf->next->connected)) { /* connected or about to start */ @@ -1762,7 +1850,7 @@ static struct Curl_cfilter *get_ssl_cf_engaged(struct connectdata *conn, bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) { - return (cf->cft == &cft_ssl_proxy); + return (cf->cft == &Curl_cft_ssl_proxy); } struct ssl_config_data * @@ -1814,10 +1902,142 @@ Curl_ssl_get_primary_config(struct Curl_easy *data, struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf) { for(; cf; cf = cf->next) { - if(cf->cft == &cft_ssl || cf->cft == &cft_ssl_proxy) + if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) return cf; } return NULL; } +static const struct alpn_spec ALPN_SPEC_H10 = { + { ALPN_HTTP_1_0 }, 1 +}; +static const struct alpn_spec ALPN_SPEC_H11 = { + { ALPN_HTTP_1_1 }, 1 +}; +#ifdef USE_HTTP2 +static const struct alpn_spec ALPN_SPEC_H2_H11 = { + { ALPN_H2, ALPN_HTTP_1_1 }, 2 +}; +#endif + +const struct alpn_spec * +Curl_alpn_get_spec(struct Curl_easy *data, struct connectdata *conn) +{ + if(!conn->bits.tls_enable_alpn) + return NULL; + if(data->state.httpwant == CURL_HTTP_VERSION_1_0) + return &ALPN_SPEC_H10; +#ifdef USE_HTTP2 + if(data->state.httpwant >= CURL_HTTP_VERSION_2) + return &ALPN_SPEC_H2_H11; +#endif + return &ALPN_SPEC_H11; +} + +const struct alpn_spec * +Curl_alpn_get_proxy_spec(struct Curl_easy *data, struct connectdata *conn) +{ + if(!conn->bits.tls_enable_alpn) + return NULL; + if(data->state.httpwant == CURL_HTTP_VERSION_1_0) + return &ALPN_SPEC_H10; + return &ALPN_SPEC_H11; +} + +CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, + const struct alpn_spec *spec) +{ + size_t i, len; + int off = 0; + unsigned char blen; + + memset(buf, 0, sizeof(*buf)); + for(i = 0; spec && i < spec->count; ++i) { + len = strlen(spec->entries[i]); + if(len >= ALPN_NAME_MAX) + return CURLE_FAILED_INIT; + blen = (unsigned char)len; + if(off + blen + 1 >= (int)sizeof(buf->data)) + return CURLE_FAILED_INIT; + buf->data[off++] = blen; + memcpy(buf->data + off, spec->entries[i], blen); + off += blen; + } + buf->len = off; + return CURLE_OK; +} + +CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, + const struct alpn_spec *spec) +{ + size_t i, len; + size_t off = 0; + + memset(buf, 0, sizeof(*buf)); + for(i = 0; spec && i < spec->count; ++i) { + len = strlen(spec->entries[i]); + if(len >= ALPN_NAME_MAX) + return CURLE_FAILED_INIT; + if(off + len + 2 >= (int)sizeof(buf->data)) + return CURLE_FAILED_INIT; + if(off) + buf->data[off++] = ','; + memcpy(buf->data + off, spec->entries[i], len); + off += len; + } + buf->data[off] = '\0'; + buf->len = (int)off; + return CURLE_OK; +} + +CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, + struct Curl_easy *data, + const unsigned char *proto, + size_t proto_len) +{ + int can_multi = 0; + + if(proto && proto_len) { + if(proto_len == ALPN_HTTP_1_1_LENGTH && + !memcmp(ALPN_HTTP_1_1, proto, ALPN_HTTP_1_1_LENGTH)) { + cf->conn->alpn = CURL_HTTP_VERSION_1_1; + } + else if(proto_len == ALPN_HTTP_1_0_LENGTH && + !memcmp(ALPN_HTTP_1_0, proto, ALPN_HTTP_1_0_LENGTH)) { + cf->conn->alpn = CURL_HTTP_VERSION_1_0; + } +#ifdef USE_HTTP2 + else if(proto_len == ALPN_H2_LENGTH && + !memcmp(ALPN_H2, proto, ALPN_H2_LENGTH)) { + cf->conn->alpn = CURL_HTTP_VERSION_2; + can_multi = 1; + } +#endif +#ifdef USE_HTTP3 + else if(proto_len == ALPN_H3_LENGTH && + !memcmp(ALPN_H3, proto, ALPN_H3_LENGTH)) { + cf->conn->alpn = CURL_HTTP_VERSION_3; + can_multi = 1; + } +#endif + else { + cf->conn->alpn = CURL_HTTP_VERSION_NONE; + failf(data, "unsupported ALPN protocol: '%.*s'", (int)proto_len, proto); + /* TODO: do we want to fail this? Previous code just ignored it and + * some vtls backends even ignore the return code of this function. */ + /* return CURLE_NOT_BUILT_IN; */ + goto out; + } + infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, (int)proto_len, proto); + } + else { + cf->conn->alpn = CURL_HTTP_VERSION_NONE; + infof(data, VTLS_INFOF_NO_ALPN); + } + +out: + Curl_multiuse_state(data, can_multi? BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + return CURLE_OK; +} + #endif /* USE_SSL */ diff --git a/Utilities/cmcurl/lib/vtls/vtls.h b/Utilities/cmcurl/lib/vtls/vtls.h index 5ad64fcf60..0d9e74a699 100644 --- a/Utilities/cmcurl/lib/vtls/vtls.h +++ b/Utilities/cmcurl/lib/vtls/vtls.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -27,7 +27,6 @@ struct connectdata; struct ssl_config_data; -struct ssl_connect_data; struct ssl_primary_config; struct Curl_ssl_session; @@ -53,6 +52,7 @@ struct Curl_ssl_session; /* Curl_multi SSL backend-specific data; declared differently by each SSL backend */ struct multi_ssl_backend_data; +struct Curl_cfilter; CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail); @@ -68,8 +68,53 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, /* see https://www.iana.org/assignments/tls-extensiontype-values/ */ #define ALPN_HTTP_1_1_LENGTH 8 #define ALPN_HTTP_1_1 "http/1.1" +#define ALPN_HTTP_1_0_LENGTH 8 +#define ALPN_HTTP_1_0 "http/1.0" #define ALPN_H2_LENGTH 2 #define ALPN_H2 "h2" +#define ALPN_H3_LENGTH 2 +#define ALPN_H3 "h3" + +/* conservative sizes on the ALPN entries and count we are handling, + * we can increase these if we ever feel the need or have to accommodate + * ALPN strings from the "outside". */ +#define ALPN_NAME_MAX 10 +#define ALPN_ENTRIES_MAX 3 +#define ALPN_PROTO_BUF_MAX (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1)) + +struct alpn_spec { + const char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX]; + size_t count; /* number of entries */ +}; + +struct alpn_proto_buf { + unsigned char data[ALPN_PROTO_BUF_MAX]; + int len; +}; + +CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, + const struct alpn_spec *spec); +CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, + const struct alpn_spec *spec); + +CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, + struct Curl_easy *data, + const unsigned char *proto, + size_t proto_len); + +/** + * Get the ALPN specification to use for talking to remote host. + * May return NULL if ALPN is disabled on the connection. + */ +const struct alpn_spec * +Curl_alpn_get_spec(struct Curl_easy *data, struct connectdata *conn); + +/** + * Get the ALPN specification to use for talking to the proxy. + * May return NULL if ALPN is disabled on the connection. + */ +const struct alpn_spec * +Curl_alpn_get_proxy_spec(struct Curl_easy *data, struct connectdata *conn); char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen); @@ -95,7 +140,6 @@ struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data); /* init the SSL session ID cache */ CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t); void Curl_ssl_version(char *buffer, size_t size); -int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn); /* Certificate information list handling. */ @@ -156,6 +200,9 @@ CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, struct connectdata *conn, int sockindex); +CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); + CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, int sockindex); @@ -163,6 +210,8 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data, struct connectdata *conn, int sockindex); +CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, + struct Curl_easy *data); #endif /* !CURL_DISABLE_PROXY */ /** @@ -208,6 +257,9 @@ bool Curl_ssl_supports(struct Curl_easy *data, int ssl_option); void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, CURLINFO info, int n); +extern struct Curl_cftype Curl_cft_ssl; +extern struct Curl_cftype Curl_cft_ssl_proxy; + #else /* if not USE_SSL */ /* When SSL support is not present, just define away these function calls */ @@ -218,7 +270,6 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex, #define Curl_ssl_set_engine_default(x) CURLE_NOT_BUILT_IN #define Curl_ssl_engines_list(x) NULL #define Curl_ssl_initsessions(x,y) CURLE_OK -#define Curl_ssl_check_cxn(d,x) 0 #define Curl_ssl_free_certinfo(x) Curl_nop_stmt #define Curl_ssl_kill_session(x) Curl_nop_stmt #define Curl_ssl_random(x,y,z) ((void)x, CURLE_NOT_BUILT_IN) diff --git a/Utilities/cmcurl/lib/vtls/vtls_int.h b/Utilities/cmcurl/lib/vtls/vtls_int.h index 6710a2b559..a20ca7db7f 100644 --- a/Utilities/cmcurl/lib/vtls/vtls_int.h +++ b/Utilities/cmcurl/lib/vtls/vtls_int.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -33,18 +33,20 @@ struct ssl_connect_data { ssl_connection_state state; ssl_connect_state connecting_state; - const char *hostname; /* hostnaem for verification */ - const char *dispname; /* display version of hostname */ + char *hostname; /* hostname for verification */ + char *dispname; /* display version of hostname */ int port; /* remote port at origin */ + const struct alpn_spec *alpn; /* ALPN to use or NULL for none */ struct ssl_backend_data *backend; /* vtls backend specific props */ - struct Curl_easy *call_data; /* data handle used in current call, - * same as parameter passed, but available - * here for backend internal callbacks - * that need it. NULLed after at the - * end of each vtls filter invcocation. */ + struct cf_call_data call_data; /* data handle used in current call */ + struct curltime handshake_done; /* time when handshake finished */ }; +#define CF_CTX_CALL_DATA(cf) \ + ((struct ssl_connect_data *)(cf)->ctx)->call_data + + /* Definitions for SSL Implementations */ struct Curl_ssl { diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.c b/Utilities/cmcurl/lib/vtls/wolfssl.c index 7cc4774e83..2e57899324 100644 --- a/Utilities/cmcurl/lib/vtls/wolfssl.c +++ b/Utilities/cmcurl/lib/vtls/wolfssl.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -218,29 +218,9 @@ static const struct group_name_map gnm[] = { { WOLFSSL_KYBER_LEVEL1, "KYBER_LEVEL1" }, { WOLFSSL_KYBER_LEVEL3, "KYBER_LEVEL3" }, { WOLFSSL_KYBER_LEVEL5, "KYBER_LEVEL5" }, - { WOLFSSL_NTRU_HPS_LEVEL1, "NTRU_HPS_LEVEL1" }, - { WOLFSSL_NTRU_HPS_LEVEL3, "NTRU_HPS_LEVEL3" }, - { WOLFSSL_NTRU_HPS_LEVEL5, "NTRU_HPS_LEVEL5" }, - { WOLFSSL_NTRU_HRSS_LEVEL3, "NTRU_HRSS_LEVEL3" }, - { WOLFSSL_SABER_LEVEL1, "SABER_LEVEL1" }, - { WOLFSSL_SABER_LEVEL3, "SABER_LEVEL3" }, - { WOLFSSL_SABER_LEVEL5, "SABER_LEVEL5" }, - { WOLFSSL_KYBER_90S_LEVEL1, "KYBER_90S_LEVEL1" }, - { WOLFSSL_KYBER_90S_LEVEL3, "KYBER_90S_LEVEL3" }, - { WOLFSSL_KYBER_90S_LEVEL5, "KYBER_90S_LEVEL5" }, - { WOLFSSL_P256_NTRU_HPS_LEVEL1, "P256_NTRU_HPS_LEVEL1" }, - { WOLFSSL_P384_NTRU_HPS_LEVEL3, "P384_NTRU_HPS_LEVEL3" }, - { WOLFSSL_P521_NTRU_HPS_LEVEL5, "P521_NTRU_HPS_LEVEL5" }, - { WOLFSSL_P384_NTRU_HRSS_LEVEL3, "P384_NTRU_HRSS_LEVEL3" }, - { WOLFSSL_P256_SABER_LEVEL1, "P256_SABER_LEVEL1" }, - { WOLFSSL_P384_SABER_LEVEL3, "P384_SABER_LEVEL3" }, - { WOLFSSL_P521_SABER_LEVEL5, "P521_SABER_LEVEL5" }, { WOLFSSL_P256_KYBER_LEVEL1, "P256_KYBER_LEVEL1" }, { WOLFSSL_P384_KYBER_LEVEL3, "P384_KYBER_LEVEL3" }, { WOLFSSL_P521_KYBER_LEVEL5, "P521_KYBER_LEVEL5" }, - { WOLFSSL_P256_KYBER_90S_LEVEL1, "P256_KYBER_90S_LEVEL1" }, - { WOLFSSL_P384_KYBER_90S_LEVEL3, "P384_KYBER_90S_LEVEL3" }, - { WOLFSSL_P521_KYBER_90S_LEVEL5, "P521_KYBER_90S_LEVEL5" }, { 0, NULL } }; #endif @@ -299,8 +279,7 @@ static long bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) static int bio_cf_out_write(WOLFSSL_BIO *bio, const char *buf, int blen) { struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); - struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nwritten; CURLcode result = CURLE_OK; @@ -315,8 +294,7 @@ static int bio_cf_out_write(WOLFSSL_BIO *bio, const char *buf, int blen) static int bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen) { struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); - struct ssl_connect_data *connssl = cf->ctx; - struct Curl_easy *data = connssl->call_data; + struct Curl_easy *data = CF_DATA_CURRENT(cf); ssize_t nread; CURLcode result = CURLE_OK; @@ -633,29 +611,18 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #endif #ifdef HAVE_ALPN - if(cf->conn->bits.tls_enable_alpn) { - char protocols[128]; - *protocols = '\0'; - - /* wolfSSL's ALPN protocol name list format is a comma separated string of - protocols in descending order of preference, eg: "h2,http/1.1" */ - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2) { - strcpy(protocols + strlen(protocols), ALPN_H2 ","); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - strcpy(protocols + strlen(protocols), ALPN_HTTP_1_1); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); + if(connssl->alpn) { + struct alpn_proto_buf proto; + CURLcode result; - if(wolfSSL_UseALPN(backend->handle, protocols, - (unsigned)strlen(protocols), + result = Curl_alpn_to_proto_str(&proto, connssl->alpn); + if(result || + wolfSSL_UseALPN(backend->handle, (char *)proto.data, proto.len, WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) { failf(data, "SSL: failed setting ALPN protocols"); return CURLE_SSL_CONNECT_ERROR; } + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif /* HAVE_ALPN */ @@ -707,7 +674,7 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #else /* USE_BIO_CHAIN */ /* pass the raw socket into the SSL layer */ - if(!SSL_set_fd(backend->handle, (int)cf->conn->sock[cf->sockindex])) { + if(!SSL_set_fd(backend->handle, (int)Curl_conn_cf_get_socket(cf, data))) { failf(data, "SSL: SSL_set_fd failed"); return CURLE_SSL_CONNECT_ERROR; } @@ -883,25 +850,11 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len); if(rc == SSL_SUCCESS) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, protocol_len, protocol); - - if(protocol_len == ALPN_HTTP_1_1_LENGTH && - !memcmp(protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH)) - cf->conn->alpn = CURL_HTTP_VERSION_1_1; -#ifdef USE_HTTP2 - else if(data->state.httpwant >= CURL_HTTP_VERSION_2 && - protocol_len == ALPN_H2_LENGTH && - !memcmp(protocol, ALPN_H2, ALPN_H2_LENGTH)) - cf->conn->alpn = CURL_HTTP_VERSION_2; -#endif - else - infof(data, "ALPN, unrecognized protocol %.*s", protocol_len, - protocol); - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, (const unsigned char *)protocol, + protocol_len); } else if(rc == SSL_ALPN_NOT_FOUND) - infof(data, VTLS_INFOF_NO_ALPN); + Curl_alpn_set_negotiated(cf, data, NULL, 0); else { failf(data, "ALPN, failure getting protocol, error %d", rc); return CURLE_SSL_CONNECT_ERROR; @@ -1166,7 +1119,7 @@ wolfssl_connect_common(struct Curl_cfilter *cf, { CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); int what; /* check if the connection has already been established */ diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.h b/Utilities/cmcurl/lib/vtls/wolfssl.h index b2e7c3fde2..a5ed848099 100644 --- a/Utilities/cmcurl/lib/vtls/wolfssl.h +++ b/Utilities/cmcurl/lib/vtls/wolfssl.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.c b/Utilities/cmcurl/lib/vtls/x509asn1.c index 4c1c9a8b79..39e4fb33bc 100644 --- a/Utilities/cmcurl/lib/vtls/x509asn1.c +++ b/Utilities/cmcurl/lib/vtls/x509asn1.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -48,6 +48,7 @@ #include "curl_ctype.h" #include "hostcheck.h" #include "vtls/vtls.h" +#include "vtls/vtls_int.h" #include "sendf.h" #include "inet_pton.h" #include "curl_base64.h" @@ -1313,7 +1314,8 @@ CURLcode Curl_verifyhost(struct Curl_cfilter *cf, /* Get the server IP address. */ #ifdef ENABLE_IPV6 - if(conn->bits.ipv6_ip && Curl_inet_pton(AF_INET6, connssl->hostname, &addr)) + if(cf->conn->bits.ipv6_ip && + Curl_inet_pton(AF_INET6, connssl->hostname, &addr)) addrlen = sizeof(struct in6_addr); else #endif @@ -1348,19 +1350,18 @@ CURLcode Curl_verifyhost(struct Curl_cfilter *cf, break; switch(name.tag) { case 2: /* DNS name. */ - matched = 0; len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING, name.beg, name.end); - if(len > 0) { - if(size_t)len == strlen(dnsname) - matched = Curl_cert_hostcheck(dnsname, (size_t)len, - connssl->hostname, hostlen); - free(dnsname); - } + if(len > 0 && (size_t)len == strlen(dnsname)) + matched = Curl_cert_hostcheck(dnsname, (size_t)len, + connssl->hostname, hostlen); + else + matched = 0; + free(dnsname); break; case 7: /* IP address. */ - matched = (name.end - name.beg) == addrlen && + matched = (size_t)(name.end - name.beg) == addrlen && !memcmp(&addr, name.beg, addrlen); break; } @@ -1406,8 +1407,10 @@ CURLcode Curl_verifyhost(struct Curl_cfilter *cf, failf(data, "SSL: unable to obtain common name from peer certificate"); else { len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end); - if(len < 0) + 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, diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.h b/Utilities/cmcurl/lib/vtls/x509asn1.h index eb8e9597cb..5496de40e4 100644 --- a/Utilities/cmcurl/lib/vtls/x509asn1.h +++ b/Utilities/cmcurl/lib/vtls/x509asn1.h @@ -8,7 +8,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/warnless.c b/Utilities/cmcurl/lib/warnless.c index b00d7a5a26..10c91fb28f 100644 --- a/Utilities/cmcurl/lib/warnless.c +++ b/Utilities/cmcurl/lib/warnless.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/warnless.h b/Utilities/cmcurl/lib/warnless.h index 4367099d92..99b243379e 100644 --- a/Utilities/cmcurl/lib/warnless.h +++ b/Utilities/cmcurl/lib/warnless.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/wildcard.c b/Utilities/cmcurl/lib/wildcard.c index a3e24b6784..3b81c7a869 100644 --- a/Utilities/cmcurl/lib/wildcard.c +++ b/Utilities/cmcurl/lib/wildcard.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/wildcard.h b/Utilities/cmcurl/lib/wildcard.h index 21e933b9a4..9dccab01ab 100644 --- a/Utilities/cmcurl/lib/wildcard.h +++ b/Utilities/cmcurl/lib/wildcard.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2010 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 diff --git a/Utilities/cmcurl/lib/ws.c b/Utilities/cmcurl/lib/ws.c index c1b2622a71..0fc5e56aee 100644 --- a/Utilities/cmcurl/lib/ws.c +++ b/Utilities/cmcurl/lib/ws.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -115,12 +115,18 @@ CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req) return result; } -CURLcode Curl_ws_accept(struct Curl_easy *data) +/* + * 'nread' is number of bytes of websocket data already in the buffer at + * 'mem'. + */ +CURLcode Curl_ws_accept(struct Curl_easy *data, + const char *mem, size_t nread) { struct SingleRequest *k = &data->req; struct HTTP *ws = data->req.p.http; struct connectdata *conn = data->conn; struct websocket *wsp = &data->req.p.http->ws; + struct ws_conn *wsc = &conn->proto.ws; CURLcode result; /* Verify the Sec-WebSocket-Accept response. @@ -149,13 +155,21 @@ CURLcode Curl_ws_accept(struct Curl_easy *data) infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x", ws->ws.mask[0], ws->ws.mask[1], ws->ws.mask[2], ws->ws.mask[3]); + Curl_dyn_init(&wsc->early, data->set.buffer_size); + if(nread) { + result = Curl_dyn_addn(&wsc->early, mem, nread); + if(result) + return result; + infof(data, "%zu bytes websocket payload", nread); + wsp->stillb = Curl_dyn_ptr(&wsc->early); + wsp->stillblen = Curl_dyn_len(&wsc->early); + } k->upgr101 = UPGR101_RECEIVED; if(data->set.connect_only) /* switch off non-blocking sockets */ (void)curlx_nonblock(conn->sock[FIRSTSOCKET], FALSE); - wsp->oleft = 0; return result; } @@ -183,12 +197,12 @@ static void ws_decode_shift(struct Curl_easy *data, size_t spent) /* ws_decode() decodes a binary frame into structured WebSocket data, - wpkt - the incoming raw data. If NULL, work on the already buffered data. - ilen - the size of the provided data, perhaps too little, perhaps too much - out - stored pointed to extracted data + data - the transfer + inbuf - incoming raw data. If NULL, work on the already buffered data. + inlen - size of the provided data, perhaps too little, perhaps too much + headlen - stored length of the frame header olen - stored length of the extracted data oleft - number of unread bytes pending to that belongs to this frame - more - if there is more data in there flags - stored bitmask about the frame Returns CURLE_AGAIN if there is only a partial frame in the buffer. Then it @@ -246,6 +260,9 @@ static CURLcode ws_decode(struct Curl_easy *data, infof(data, "WS: received OPCODE PONG"); *flags |= CURLWS_PONG; break; + default: + failf(data, "WS: unknown opcode: %x", opcode); + return CURLE_RECV_ERROR; } if(inbuf[1] & WSBIT_MASK) { @@ -296,7 +313,7 @@ static CURLcode ws_decode(struct Curl_easy *data, *oleft = 0; /* bytes yet to come (for this frame) */ } - infof(data, "WS: received %zu bytes payload (%zu left, buflen was %zu)", + infof(data, "WS: received %Ou bytes payload (%Ou left, buflen was %zu)", payloadsize, *oleft, inlen); return CURLE_OK; } @@ -339,9 +356,6 @@ size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */, result = ws_decode(data, wsbuf, buflen, &headlen, &write_len, &fb_left, &recvflags); - consumed += headlen; - wsbuf += headlen; - buflen -= headlen; if(result == CURLE_AGAIN) /* insufficient amount of data, keep it for later. * we pretend to have written all since we have a copy */ @@ -350,6 +364,10 @@ size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */, infof(data, "WS: decode error %d", (int)result); return nitems - 1; } + consumed += headlen; + wsbuf += headlen; + buflen -= headlen; + /* New frame. store details about the frame to be reachable with curl_ws_meta() from within the write callback */ ws->ws.frame.age = 0; @@ -392,7 +410,6 @@ size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */, return nitems; } - CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, size_t buflen, size_t *nread, struct curl_ws_frame **metap) @@ -409,7 +426,7 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, return result; while(!done) { - size_t write_len; + size_t datalen; unsigned int recvflags; if(!wsp->stillblen) { @@ -419,26 +436,32 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, data->set.buffer_size, &n); if(result) return result; - if(!n) + if(!n) { /* connection closed */ + infof(data, "connection expectedly closed?"); return CURLE_GOT_NOTHING; + } wsp->stillb = data->state.buffer; wsp->stillblen = n; } - infof(data, "WS: got %u websocket bytes to decode", - (int)wsp->stillblen); + infof(data, "WS: %u bytes left to decode", (int)wsp->stillblen); if(!wsp->frame.bytesleft) { size_t headlen; curl_off_t oleft; /* detect new frame */ result = ws_decode(data, (unsigned char *)wsp->stillb, wsp->stillblen, - &headlen, &write_len, &oleft, &recvflags); + &headlen, &datalen, &oleft, &recvflags); if(result == CURLE_AGAIN) /* a packet fragment only */ break; else if(result) return result; + if(datalen > buflen) { + size_t diff = datalen - buflen; + datalen = buflen; + oleft += diff; + } wsp->stillb += headlen; wsp->stillblen -= headlen; wsp->frame.offset = 0; @@ -447,40 +470,45 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, } else { /* existing frame, remaining payload handling */ - write_len = wsp->frame.bytesleft; - if(write_len > wsp->stillblen) - write_len = wsp->stillblen; + datalen = wsp->frame.bytesleft; + if(datalen > wsp->stillblen) + datalen = wsp->stillblen; + if(datalen > buflen) + datalen = buflen; + + wsp->frame.offset += wsp->frame.len; + wsp->frame.bytesleft -= datalen; } + wsp->frame.len = datalen; /* auto-respond to PINGs */ if((wsp->frame.flags & CURLWS_PING) && !wsp->frame.bytesleft) { - infof(data, "WS: auto-respond to PING with a PONG"); + size_t nsent = 0; + infof(data, "WS: auto-respond to PING with a PONG, %zu bytes payload", + datalen); /* send back the exact same content as a PONG */ - result = curl_ws_send(data, wsp->stillb, write_len, - &write_len, 0, CURLWS_PONG); + result = curl_ws_send(data, wsp->stillb, datalen, &nsent, 0, + CURLWS_PONG); if(result) return result; + infof(data, "WS: bytesleft %zu datalen %zu", + wsp->frame.bytesleft, datalen); + /* we handled the data part of the PING, advance over that */ + wsp->stillb += nsent; + wsp->stillblen -= nsent; } - else if(write_len || !wsp->frame.bytesleft) { - if(write_len > buflen) - write_len = buflen; + else if(datalen) { /* copy the payload to the user buffer */ - memcpy(buffer, wsp->stillb, write_len); - *nread = write_len; + memcpy(buffer, wsp->stillb, datalen); + *nread = datalen; done = TRUE; - } - if(write_len) { - /* update buffer and frame info */ - wsp->frame.offset += write_len; - DEBUGASSERT(wsp->frame.bytesleft >= (curl_off_t)write_len); - if(wsp->frame.bytesleft) - wsp->frame.bytesleft -= write_len; - DEBUGASSERT(write_len <= wsp->stillblen); - wsp->stillblen -= write_len; + + wsp->stillblen -= datalen; if(wsp->stillblen) - wsp->stillb += write_len; - else + wsp->stillb += datalen; + else { wsp->stillb = NULL; + } } } *metap = &wsp->frame; @@ -555,17 +583,27 @@ static size_t ws_packethead(struct Curl_easy *data, } if(!(flags & CURLWS_CONT)) { - /* if not marked as continuing, assume this is the final fragment */ - firstbyte |= WSBIT_FIN | opcode; + if(!ws->ws.contfragment) + /* not marked as continuing, this is the final fragment */ + firstbyte |= WSBIT_FIN | opcode; + else + /* marked as continuing, this is the final fragment; set CONT + opcode and FIN bit */ + firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT; + ws->ws.contfragment = FALSE; + infof(data, "WS: set FIN"); } else if(ws->ws.contfragment) { /* the previous fragment was not a final one and this isn't either, keep a CONT opcode and no FIN bit */ firstbyte |= WSBIT_OPCODE_CONT; + infof(data, "WS: keep CONT, no FIN"); } else { + firstbyte = opcode; ws->ws.contfragment = TRUE; + infof(data, "WS: set CONT, no FIN"); } out[0] = firstbyte; if(len > 65535) { @@ -686,8 +724,14 @@ CURL_EXTERN CURLcode curl_ws_send(struct Curl_easy *data, const void *buffer, infof(data, "WS: wanted to send %zu bytes, sent %zu bytes", headlen + buflen, written); - *sent = written; + if(!result) { + /* the *sent number only counts "payload", excluding the header */ + if((size_t)written > headlen) + *sent = written - headlen; + else + *sent = 0; + } return result; } @@ -698,6 +742,20 @@ void Curl_ws_done(struct Curl_easy *data) Curl_dyn_free(&wsp->buf); } +CURLcode Curl_ws_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + struct ws_conn *wsc = &conn->proto.ws; + (void)data; + (void)dead_connection; + Curl_dyn_free(&wsc->early); + + /* make sure this is non-blocking to avoid getting stuck in shutdown */ + (void)curlx_nonblock(conn->sock[FIRSTSOCKET], TRUE); + return CURLE_OK; +} + CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data) { /* we only return something for websocket, called from within the callback diff --git a/Utilities/cmcurl/lib/ws.h b/Utilities/cmcurl/lib/ws.h index 2f3ed2d1e6..176dda470b 100644 --- a/Utilities/cmcurl/lib/ws.h +++ b/Utilities/cmcurl/lib/ws.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. + * 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 @@ -46,24 +46,28 @@ struct websocket { size_t usedbuf; /* number of leading bytes in 'buf' the most recent complete websocket frame uses */ struct curl_ws_frame frame; /* the struct used for frame state */ - curl_off_t oleft; /* outstanding number of payload bytes left from the - server */ size_t stillblen; /* number of bytes left in the buffer to deliver in the next curl_ws_recv() call */ - char *stillb; /* the stillblen pending bytes are here */ + const char *stillb; /* the stillblen pending bytes are here */ curl_off_t sleft; /* outstanding number of payload bytes left to send */ unsigned int xori; /* xor index */ }; -CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req); -CURLcode Curl_ws_accept(struct Curl_easy *data); +struct ws_conn { + struct dynbuf early; /* data already read when switching to ws */ +}; +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); #else #define Curl_ws_request(x,y) CURLE_OK #define Curl_ws_done(x) Curl_nop_stmt +#define Curl_ws_free(x) Curl_nop_stmt #endif #endif /* HEADER_CURL_WS_H */ |