Merge branch 'upstream-curl' into update-curl

* upstream-curl:
  curl 2023-05-30 (7ab9d437)
This commit is contained in:
Brad King
2023-05-30 09:39:43 -04:00
130 changed files with 12065 additions and 6980 deletions
+197
View File
@@ -0,0 +1,197 @@
#***************************************************************************
# _ _ ____ _
# 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(CheckCCompilerFlag)
if(PICKY_COMPILER)
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang")
# https://clang.llvm.org/docs/DiagnosticsReference.html
# https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
# WPICKY_ENABLE = Options we want to enable as-is.
# WPICKY_DETECT = Options we want to test first and enable if available.
# Prefer the -Wextra alias with clang.
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
set(WPICKY_ENABLE "-Wextra")
else()
set(WPICKY_ENABLE "-W")
endif()
list(APPEND WPICKY_ENABLE
-Wall -pedantic
)
# ----------------------------------
# Add new options here, if in doubt:
# ----------------------------------
set(WPICKY_DETECT
)
# Assume these options always exist with both clang and gcc.
# Require clang 3.0 / gcc 2.95 or later.
list(APPEND WPICKY_ENABLE
-Wbad-function-cast # clang 3.0 gcc 2.95
-Wconversion # clang 3.0 gcc 2.95
-Winline # clang 1.0 gcc 1.0
-Wmissing-declarations # clang 1.0 gcc 2.7
-Wmissing-prototypes # clang 1.0 gcc 1.0
-Wnested-externs # clang 1.0 gcc 2.7
-Wno-long-long # clang 1.0 gcc 2.95
-Wno-multichar # clang 1.0 gcc 2.95
-Wpointer-arith # clang 1.0 gcc 1.4
-Wshadow # clang 1.0 gcc 2.95
-Wsign-compare # clang 1.0 gcc 2.95
-Wundef # clang 1.0 gcc 2.95
-Wunused # clang 1.1 gcc 2.95
-Wwrite-strings # clang 1.0 gcc 1.4
)
# Always enable with clang, version dependent with gcc
set(WPICKY_COMMON_OLD
-Wcast-align # clang 1.0 gcc 4.2
-Wdeclaration-after-statement # clang 1.0 gcc 3.4
-Wempty-body # clang 3.0 gcc 4.3
-Wendif-labels # clang 1.0 gcc 3.3
-Wfloat-equal # clang 1.0 gcc 2.96 (3.0)
-Wignored-qualifiers # clang 3.0 gcc 4.3
-Wno-format-nonliteral # clang 1.0 gcc 2.96 (3.0)
-Wno-sign-conversion # clang 3.0 gcc 4.3
-Wno-system-headers # clang 1.0 gcc 3.0
-Wstrict-prototypes # clang 1.0 gcc 3.3
-Wtype-limits # clang 3.0 gcc 4.3
-Wvla # clang 2.8 gcc 4.3
)
set(WPICKY_COMMON
-Wdouble-promotion # clang 3.6 gcc 4.6 appleclang 6.3
-Wenum-conversion # clang 3.2 gcc 10.0 appleclang 4.6 g++ 11.0
-Wunused-const-variable # clang 3.4 gcc 6.0 appleclang 5.1
)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
list(APPEND WPICKY_ENABLE
${WPICKY_COMMON_OLD}
-Wshift-sign-overflow # clang 2.9
-Wshorten-64-to-32 # clang 1.0
)
# Enable based on compiler version
if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.6) OR
(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.3))
list(APPEND WPICKY_ENABLE
${WPICKY_COMMON}
)
endif()
if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 3.9) OR
(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 8.3))
list(APPEND WPICKY_ENABLE
-Wcomma # clang 3.9 appleclang 8.3
-Wmissing-variable-declarations # clang 3.2 appleclang 4.6
)
endif()
if((CMAKE_C_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0) OR
(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.3))
list(APPEND WPICKY_ENABLE
-Wassign-enum # clang 7.0 appleclang 10.3
-Wextra-semi-stmt # clang 7.0 appleclang 10.3
)
endif()
else() # gcc
list(APPEND WPICKY_DETECT
${WPICKY_COMMON}
)
# Enable based on compiler version
if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.3)
list(APPEND WPICKY_ENABLE
${WPICKY_COMMON_OLD}
-Wmissing-parameter-type # gcc 4.3
-Wold-style-declaration # gcc 4.3
-Wstrict-aliasing=3 # gcc 4.0
)
endif()
if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.5 AND MINGW)
list(APPEND WPICKY_ENABLE
-Wno-pedantic-ms-format # gcc 4.5 (mingw-only)
)
endif()
if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8)
list(APPEND WPICKY_ENABLE
-Wformat=2 # clang 3.0 gcc 4.8 (clang part-default, enabling it fully causes -Wformat-nonliteral warnings)
)
endif()
if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 5.0)
list(APPEND WPICKY_ENABLE
-Warray-bounds=2 -ftree-vrp # clang 3.0 gcc 5.0 (clang default: -Warray-bounds)
)
endif()
if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 6.0)
list(APPEND WPICKY_ENABLE
-Wduplicated-cond # gcc 6.0
-Wnull-dereference # clang 3.0 gcc 6.0 (clang default)
-fdelete-null-pointer-checks
-Wshift-negative-value # clang 3.7 gcc 6.0 (clang default)
-Wshift-overflow=2 # clang 3.0 gcc 6.0 (clang default: -Wshift-overflow)
)
endif()
if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 7.0)
list(APPEND WPICKY_ENABLE
-Walloc-zero # gcc 7.0
-Wduplicated-branches # gcc 7.0
-Wformat-overflow=2 # gcc 7.0
-Wformat-truncation=1 # gcc 7.0
-Wrestrict # gcc 7.0
)
endif()
if(NOT CMAKE_C_COMPILER_VERSION VERSION_LESS 10.0)
list(APPEND WPICKY_ENABLE
-Warith-conversion # gcc 10.0
)
endif()
endif()
#
unset(WPICKY)
foreach(_CCOPT ${WPICKY_ENABLE})
set(WPICKY "${WPICKY} ${_CCOPT}")
endforeach()
foreach(_CCOPT ${WPICKY_DETECT})
# surprisingly, CHECK_C_COMPILER_FLAG needs a new variable to store each new
# test result in.
string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname)
# GCC only warns about unknown -Wno- options if there are also other diagnostic messages,
# so test for the positive form instead
string(REPLACE "-Wno-" "-W" _CCOPT_ON "${_CCOPT}")
check_c_compiler_flag(${_CCOPT_ON} ${_optvarname})
if(${_optvarname})
set(WPICKY "${WPICKY} ${_CCOPT}")
endif()
endforeach()
message(STATUS "Picky compiler options:${WPICKY}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WPICKY}")
endif()
endif()
+124 -109
View File
@@ -195,6 +195,7 @@ endif()
#
# The following variables are available:
# HAVE_RAND_EGD: `RAND_egd` present in OpenSSL
# HAVE_AWSLC: OpenSSL is AWS-LC
# HAVE_BORINGSSL: OpenSSL is BoringSSL
# HAVE_PK11_CREATEMANAGEDGENERICOBJECTL: `PK11_CreateManagedGenericObject` present in NSS
# HAVE_SSL_CTX_SET_QUIC_METHOD: `SSL_CTX_set_quic_method` present in OpenSSL/wolfSSL
@@ -275,28 +276,7 @@ endif()
option(ENABLE_DEBUG "Set to ON to enable curl debug features" OFF)
option(ENABLE_CURLDEBUG "Set to ON to build with TrackMemory feature enabled" OFF)
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang")
if(PICKY_COMPILER)
foreach(_CCOPT -pedantic -Wall -W -Wpointer-arith -Wwrite-strings -Wunused -Wshadow -Winline -Wnested-externs -Wmissing-declarations -Wmissing-prototypes -Wfloat-equal -Wsign-compare -Wundef -Wendif-labels -Wstrict-prototypes -Wdeclaration-after-statement -Wstrict-aliasing=3 -Wcast-align -Wtype-limits -Wold-style-declaration -Wmissing-parameter-type -Wempty-body -Wclobbered -Wignored-qualifiers -Wconversion -Wvla -Wdouble-promotion -Wenum-conversion -Warith-conversion)
# surprisingly, CHECK_C_COMPILER_FLAG needs a new variable to store each new
# test result in.
string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname)
check_c_compiler_flag(${_CCOPT} ${_optvarname})
if(${_optvarname})
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_CCOPT}")
endif()
endforeach()
foreach(_CCOPT long-long multichar format-nonliteral sign-conversion system-headers pedantic-ms-format)
# GCC only warns about unknown -Wno- options if there are also other diagnostic messages,
# so test for the positive form instead
string(MAKE_C_IDENTIFIER "OPT${_CCOPT}" _optvarname)
check_c_compiler_flag("-W${_CCOPT}" ${_optvarname})
if(${_optvarname})
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-${_CCOPT}")
endif()
endforeach()
endif()
endif()
include(PickyWarnings)
if(ENABLE_DEBUG)
# DEBUGBUILD will be defined only for Debug builds
@@ -476,6 +456,11 @@ if(${CMAKE_SYSTEM_NAME} MATCHES AIX)
set(_ALL_SOURCE 1)
endif()
# If we are on Haiku, make sure that the network library is brought in.
if(${CMAKE_SYSTEM_NAME} MATCHES Haiku)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -lnetwork")
endif()
# Include all the necessary files for macros
include(CMakePushCheckState)
include(CheckFunctionExists)
@@ -511,6 +496,19 @@ check_function_exists(gethostname HAVE_GETHOSTNAME)
if(WIN32)
check_library_exists_concat("ws2_32" getch HAVE_LIBWS2_32)
check_library_exists_concat("winmm" getch HAVE_LIBWINMM)
# Matching logic used for Curl_win32_random()
if(MINGW)
check_c_source_compiles("
#include <_mingw.h>
#if defined(__MINGW64_VERSION_MAJOR)
#error
#endif
int main(void) {
return 0;
}"
HAVE_MINGW_ORIGINAL)
endif()
endif()
if(0) # This code not needed for building within CMake.
@@ -583,8 +581,87 @@ if(use_core_foundation)
list(APPEND CURL_LIBS "-framework CoreFoundation")
endif()
# Keep compression lib detection before TLS detection, which
# might depend on it.
if(CURL_USE_OPENSSL)
find_package(OpenSSL)
if(NOT OpenSSL_FOUND)
message(FATAL_ERROR
"Could not find OpenSSL. Install an OpenSSL development package or "
"configure CMake with -DCMAKE_USE_OPENSSL=OFF to build without OpenSSL.")
endif()
set(SSL_ENABLED ON)
set(USE_OPENSSL ON)
list(APPEND CURL_LIBS ${OPENSSL_LIBRARIES})
include_directories(${OPENSSL_INCLUDE_DIR})
if(WIN32)
list(APPEND CURL_LIBS "ws2_32")
if(NOT HAVE_MINGW_ORIGINAL)
list(APPEND CURL_LIBS "bcrypt") # for OpenSSL/LibreSSL
endif()
endif()
set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR})
if(NOT DEFINED HAVE_RAND_EGD)
check_symbol_exists(RAND_egd "${CURL_INCLUDES}" HAVE_RAND_EGD)
endif()
if(NOT DEFINED HAVE_BORINGSSL)
check_symbol_exists(OPENSSL_IS_BORINGSSL "openssl/base.h" HAVE_BORINGSSL)
endif()
if(NOT DEFINED HAVE_AWSLC)
check_symbol_exists(OPENSSL_IS_AWSLC "openssl/base.h" HAVE_AWSLC)
endif()
# Optionally build with a specific CA cert bundle.
if(CURL_CA_BUNDLE)
add_definitions(-DCURL_CA_BUNDLE="${CURL_CA_BUNDLE}")
endif()
# Optionally build with a specific CA cert dir.
if(CURL_CA_PATH)
add_definitions(-DCURL_CA_PATH="${CURL_CA_PATH}")
endif()
endif()
if(CURL_USE_MBEDTLS)
find_package(MbedTLS REQUIRED)
set(SSL_ENABLED ON)
set(USE_MBEDTLS ON)
list(APPEND CURL_LIBS ${MBEDTLS_LIBRARIES})
include_directories(${MBEDTLS_INCLUDE_DIRS})
endif()
if(CURL_USE_BEARSSL)
find_package(BearSSL REQUIRED)
set(SSL_ENABLED ON)
set(USE_BEARSSL ON)
list(APPEND CURL_LIBS ${BEARSSL_LIBRARY})
include_directories(${BEARSSL_INCLUDE_DIRS})
endif()
if(CURL_USE_WOLFSSL)
find_package(WolfSSL REQUIRED)
set(SSL_ENABLED ON)
set(USE_WOLFSSL ON)
list(APPEND CURL_LIBS ${WolfSSL_LIBRARIES})
include_directories(${WolfSSL_INCLUDE_DIRS})
endif()
if(CURL_USE_NSS)
find_package(NSS REQUIRED)
include_directories(${NSS_INCLUDE_DIRS})
list(APPEND CURL_LIBS ${NSS_LIBRARIES})
set(SSL_ENABLED ON)
set(USE_NSS ON)
if(NOT DEFINED HAVE_PK11_CREATEMANAGEDGENERICOBJECT)
cmake_push_check_state()
set(CMAKE_REQUIRED_INCLUDES ${NSS_INCLUDE_DIRS})
set(CMAKE_REQUIRED_LIBRARIES ${NSS_LIBRARIES})
check_symbol_exists(PK11_CreateManagedGenericObject "pk11pub.h" HAVE_PK11_CREATEMANAGEDGENERICOBJECT)
cmake_pop_check_state()
endif()
endif()
# Keep ZLIB detection after TLS detection,
# and before calling CheckQuicSupportInOpenSSL.
set(HAVE_LIBZ OFF)
set(USE_ZLIB OFF)
@@ -639,75 +716,6 @@ if(CURL_ZSTD)
endif()
endif()
if(CURL_USE_OPENSSL)
find_package(OpenSSL)
if(NOT OpenSSL_FOUND)
message(FATAL_ERROR
"Could not find OpenSSL. Install an OpenSSL development package or "
"configure CMake with -DCMAKE_USE_OPENSSL=OFF to build without OpenSSL.")
endif()
set(SSL_ENABLED ON)
set(USE_OPENSSL ON)
list(APPEND CURL_LIBS ${OPENSSL_LIBRARIES})
include_directories(${OPENSSL_INCLUDE_DIR})
set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR})
if(NOT DEFINED HAVE_RAND_EGD)
check_symbol_exists(RAND_egd "${CURL_INCLUDES}" HAVE_RAND_EGD)
endif()
if(NOT DEFINED HAVE_BORINGSSL)
check_symbol_exists(OPENSSL_IS_BORINGSSL "openssl/base.h" HAVE_BORINGSSL)
endif()
# Optionally build with a specific CA cert bundle.
if(CURL_CA_BUNDLE)
add_definitions(-DCURL_CA_BUNDLE="${CURL_CA_BUNDLE}")
endif()
# Optionally build with a specific CA cert dir.
if(CURL_CA_PATH)
add_definitions(-DCURL_CA_PATH="${CURL_CA_PATH}")
endif()
endif()
if(CURL_USE_MBEDTLS)
find_package(MbedTLS REQUIRED)
set(SSL_ENABLED ON)
set(USE_MBEDTLS ON)
list(APPEND CURL_LIBS ${MBEDTLS_LIBRARIES})
include_directories(${MBEDTLS_INCLUDE_DIRS})
endif()
if(CURL_USE_BEARSSL)
find_package(BearSSL REQUIRED)
set(SSL_ENABLED ON)
set(USE_BEARSSL ON)
list(APPEND CURL_LIBS ${BEARSSL_LIBRARY})
include_directories(${BEARSSL_INCLUDE_DIRS})
endif()
if(CURL_USE_WOLFSSL)
find_package(WolfSSL REQUIRED)
set(SSL_ENABLED ON)
set(USE_WOLFSSL ON)
list(APPEND CURL_LIBS ${WolfSSL_LIBRARIES})
include_directories(${WolfSSL_INCLUDE_DIRS})
endif()
if(CURL_USE_NSS)
find_package(NSS REQUIRED)
include_directories(${NSS_INCLUDE_DIRS})
list(APPEND CURL_LIBS ${NSS_LIBRARIES})
set(SSL_ENABLED ON)
set(USE_NSS ON)
if(NOT DEFINED HAVE_PK11_CREATEMANAGEDGENERICOBJECT)
cmake_push_check_state()
set(CMAKE_REQUIRED_INCLUDES ${NSS_INCLUDE_DIRS})
set(CMAKE_REQUIRED_LIBRARIES ${NSS_LIBRARIES})
check_symbol_exists(PK11_CreateManagedGenericObject "pk11pub.h" HAVE_PK11_CREATEMANAGEDGENERICOBJECT)
cmake_pop_check_state()
endif()
endif()
option(USE_NGHTTP2 "Use Nghttp2 library" OFF)
if(USE_NGHTTP2)
find_package(NGHTTP2 REQUIRED)
@@ -723,23 +731,32 @@ function(CheckQuicSupportInOpenSSL)
set(CMAKE_REQUIRED_INCLUDES "${WolfSSL_INCLUDE_DIRS}")
set(CMAKE_REQUIRED_LIBRARIES "${WolfSSL_LIBRARIES}")
if(HAVE_LIBZ)
list(APPEND CMAKE_REQUIRED_INCLUDES "${ZLIB_INCLUDE_DIRS}")
list(APPEND CMAKE_REQUIRED_INCLUDES "${ZLIB_INCLUDE_DIRS}") # Public wolfSSL headers require zlib headers
list(APPEND CMAKE_REQUIRED_LIBRARIES "${ZLIB_LIBRARIES}")
endif()
if(WIN32)
list(APPEND CMAKE_REQUIRED_LIBRARIES "crypt32")
list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32" "crypt32")
endif()
list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_UINTPTR_T) # to pull in stdint.h (as of wolfSSL v5.5.4)
check_symbol_exists(wolfSSL_set_quic_method "wolfssl/options.h;wolfssl/openssl/ssl.h" HAVE_SSL_CTX_SET_QUIC_METHOD)
else()
set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}")
set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}")
if(HAVE_LIBZ)
list(APPEND CMAKE_REQUIRED_LIBRARIES "${ZLIB_LIBRARIES}")
endif()
if(WIN32)
list(APPEND CMAKE_REQUIRED_LIBRARIES "ws2_32")
if(NOT HAVE_MINGW_ORIGINAL)
list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt") # for OpenSSL/LibreSSL
endif()
endif()
check_symbol_exists(SSL_CTX_set_quic_method "openssl/ssl.h" HAVE_SSL_CTX_SET_QUIC_METHOD)
endif()
cmake_pop_check_state()
endif()
if(NOT HAVE_SSL_CTX_SET_QUIC_METHOD)
message(FATAL_ERROR "QUIC support is missing in OpenSSL/BoringSSL/wolfSSL. Try setting -DOPENSSL_ROOT_DIR")
message(FATAL_ERROR "QUIC support is missing in OpenSSL/LibreSSL/BoringSSL/wolfSSL. Try setting -DOPENSSL_ROOT_DIR")
endif()
endfunction()
@@ -1337,6 +1354,8 @@ check_type_size("off_t" SIZEOF_OFF_T)
set(CMAKE_REQUIRED_INCLUDES "${CURL_SOURCE_DIR}/include")
set(CMAKE_EXTRA_INCLUDE_FILES "curl/system.h")
check_type_size("curl_off_t" SIZEOF_CURL_OFF_T)
set(CMAKE_EXTRA_INCLUDE_FILES "curl/curl.h")
check_type_size("curl_socket_t" SIZEOF_CURL_SOCKET_T)
set(CMAKE_EXTRA_INCLUDE_FILES "")
if(WIN32)
@@ -1464,19 +1483,6 @@ if(WIN32)
list(APPEND CURL_LIBS "advapi32" "crypt32")
endif()
# Matching logic used for Curl_win32_random()
if(MINGW)
check_c_source_compiles("
#include <_mingw.h>
#if defined(__MINGW64_VERSION_MAJOR)
#error
#endif
int main(void) {
return 0;
}"
HAVE_MINGW_ORIGINAL)
endif()
if(NOT HAVE_MINGW_ORIGINAL)
list(APPEND CURL_LIBS "bcrypt")
else()
@@ -1785,6 +1791,15 @@ write_basic_package_version_file(
VERSION ${CURL_VERSION}
COMPATIBILITY SameMajorVersion
)
file(READ "${version_config}" generated_version_config)
file(WRITE "${version_config}"
"if(NOT PACKAGE_FIND_VERSION_RANGE AND PACKAGE_FIND_VERSION_MAJOR STREQUAL \"7\")
# Version 8 satisfies version 7... requirements
set(PACKAGE_FIND_VERSION_MAJOR 8)
set(PACKAGE_FIND_VERSION_COUNT 1)
endif()
${generated_version_config}"
)
# Use:
# * TARGETS_EXPORT_NAME
+6 -3
View File
@@ -174,8 +174,9 @@ typedef enum {
} curl_sslbackend;
/* aliases for library clones and renames */
#define CURLSSLBACKEND_LIBRESSL CURLSSLBACKEND_OPENSSL
#define CURLSSLBACKEND_AWSLC CURLSSLBACKEND_OPENSSL
#define CURLSSLBACKEND_BORINGSSL CURLSSLBACKEND_OPENSSL
#define CURLSSLBACKEND_LIBRESSL CURLSSLBACKEND_OPENSSL
/* deprecated names: */
#define CURLSSLBACKEND_CYASSL CURLSSLBACKEND_WOLFSSL
@@ -331,7 +332,8 @@ struct curl_fileinfo {
unsigned int flags;
/* used internally */
/* These are libcurl private struct fields. Previously used by libcurl, so
they must never be interfered with. */
char *b_data;
size_t b_size;
size_t b_used;
@@ -778,7 +780,8 @@ typedef enum {
CONNECT HTTP/1.1 */
CURLPROXY_HTTP_1_0 = 1, /* added in 7.19.4, force to use CONNECT
HTTP/1.0 */
CURLPROXY_HTTPS = 2, /* added in 7.52.0 */
CURLPROXY_HTTPS = 2, /* HTTPS but stick to HTTP/1 added in 7.52.0 */
CURLPROXY_HTTPS2 = 3, /* HTTPS and attempt HTTP/2 added in 8.1.0 */
CURLPROXY_SOCKS4 = 4, /* support added in 7.15.2, enum existed already
in 7.10 */
CURLPROXY_SOCKS5 = 5, /* added in 7.10 */
+4 -4
View File
@@ -32,13 +32,13 @@
/* This is the version number of the libcurl package from which this header
file origins: */
#define LIBCURL_VERSION "8.0.1"
#define LIBCURL_VERSION "8.1.2"
/* The numeric version number is also available "in parts" by using these
defines: */
#define LIBCURL_VERSION_MAJOR 8
#define LIBCURL_VERSION_MINOR 0
#define LIBCURL_VERSION_PATCH 1
#define LIBCURL_VERSION_MINOR 1
#define LIBCURL_VERSION_PATCH 2
/* 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 0x080001
#define LIBCURL_VERSION_NUM 0x080102
/*
* This is the date and time when the full source package was created. The
+7 -7
View File
@@ -48,13 +48,13 @@ CURL_EXTERN void curl_easy_cleanup(CURL *curl);
*
* DESCRIPTION
*
* Request internal information from the curl session with this function. The
* third argument MUST be a pointer to a long, a pointer to a char * or a
* pointer to a double (as the documentation describes elsewhere). The data
* pointed to will be filled in accordingly and can be relied upon only if the
* function returns CURLE_OK. This function is intended to get used *AFTER* a
* performed transfer, all results from this function are undefined until the
* transfer is completed.
* Request internal information from the curl session with this function.
* The third argument MUST be pointing to the specific type of the used option
* which is documented in each man page of the option. The data pointed to
* will be filled in accordingly and can be relied upon only if the function
* returns CURLE_OK. This function is intended to get used *AFTER* a performed
* transfer, all results from this function are undefined until the transfer
* is completed.
*/
CURL_EXTERN CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...);
+12
View File
@@ -114,6 +114,7 @@ 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 "SunOS" OR
CMAKE_SYSTEM_NAME STREQUAL "GNU/kFreeBSD" OR
# FreeBSD comes with the a.out and elf flavours
@@ -158,6 +159,17 @@ if(WIN32)
set_target_properties(${LIB_NAME} PROPERTIES IMPORT_SUFFIX "_imp.lib")
endif()
endif()
elseif(NOT CMAKE_CROSSCOMPILING)
# on not-Windows and not-crosscompiling, check for writable argv[]
include(CheckCSourceRuns)
check_c_source_runs("
int main(int argc, char **argv)
{
(void)argc;
argv[0][0] = ' ';
return (argv[0][0] == ' ')?0:1;
}"
HAVE_WRITABLE_ARGV)
endif()
target_include_directories(${LIB_NAME} INTERFACE
+12 -2
View File
@@ -105,8 +105,12 @@ LIB_CFILES = \
asyn-ares.c \
asyn-thread.c \
base64.c \
bufq.c \
bufref.c \
c-hyper.c \
cf-h1-proxy.c \
cf-h2-proxy.c \
cf-haproxy.c \
cf-https-connect.c \
cf-socket.c \
cfilters.c \
@@ -135,6 +139,7 @@ LIB_CFILES = \
dict.c \
doh.c \
dynbuf.c \
dynhds.c \
easy.c \
easygetopt.c \
easyoptions.c \
@@ -148,7 +153,6 @@ LIB_CFILES = \
getenv.c \
getinfo.c \
gopher.c \
h2h3.c \
hash.c \
headers.c \
hmac.c \
@@ -159,6 +163,7 @@ LIB_CFILES = \
hostsyn.c \
hsts.c \
http.c \
http1.c \
http2.c \
http_chunks.c \
http_digest.c \
@@ -230,8 +235,12 @@ LIB_HFILES = \
amigaos.h \
arpa_telnet.h \
asyn.h \
bufq.h \
bufref.h \
c-hyper.h \
cf-h1-proxy.h \
cf-h2-proxy.h \
cf-haproxy.h \
cf-https-connect.h \
cf-socket.h \
cfilters.h \
@@ -273,6 +282,7 @@ LIB_HFILES = \
dict.h \
doh.h \
dynbuf.h \
dynhds.h \
easy_lock.h \
easyif.h \
easyoptions.h \
@@ -286,12 +296,12 @@ LIB_HFILES = \
ftplistparser.h \
getinfo.h \
gopher.h \
h2h3.h \
hash.h \
headers.h \
hostip.h \
hsts.h \
http.h \
http1.h \
http2.h \
http_chunks.h \
http_digest.h \
+2 -2
View File
@@ -117,7 +117,7 @@ static struct altsvc *altsvc_createid(const char *srchost,
as->dst.port = curlx_ultous(dstport);
return as;
error:
error:
altsvc_free(as);
return NULL;
}
@@ -217,7 +217,7 @@ static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file)
}
return result;
fail:
fail:
Curl_safefree(asi->filename);
free(line);
fclose(fp);
+3 -3
View File
@@ -251,7 +251,7 @@ int init_thread_sync_data(struct thread_data *td,
return 1;
err_exit:
err_exit:
#ifndef CURL_DISABLE_SOCKETPAIR
if(tsd->sock_pair[0] != CURL_SOCKET_BAD) {
sclose(tsd->sock_pair[0]);
@@ -469,10 +469,10 @@ static bool init_resolve_thread(struct Curl_easy *data,
return TRUE;
err_exit:
err_exit:
destroy_async_data(asp);
errno_exit:
errno_exit:
errno = err;
return FALSE;
}
+1 -1
View File
@@ -178,7 +178,7 @@ CURLcode Curl_base64_decode(const char *src,
*outlen = rawlen;
return CURLE_OK;
bad:
bad:
free(newstr);
return CURLE_BAD_CONTENT_ENCODING;
}
+659
View File
@@ -0,0 +1,659 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "curl_setup.h"
#include "bufq.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
static bool chunk_is_empty(const struct buf_chunk *chunk)
{
return chunk->r_offset >= chunk->w_offset;
}
static bool chunk_is_full(const struct buf_chunk *chunk)
{
return chunk->w_offset >= chunk->dlen;
}
static size_t chunk_len(const struct buf_chunk *chunk)
{
return chunk->w_offset - chunk->r_offset;
}
static size_t chunk_space(const struct buf_chunk *chunk)
{
return chunk->dlen - chunk->w_offset;
}
static void chunk_reset(struct buf_chunk *chunk)
{
chunk->next = NULL;
chunk->r_offset = chunk->w_offset = 0;
}
static size_t chunk_append(struct buf_chunk *chunk,
const unsigned char *buf, size_t len)
{
unsigned char *p = &chunk->x.data[chunk->w_offset];
size_t n = chunk->dlen - chunk->w_offset;
DEBUGASSERT(chunk->dlen >= chunk->w_offset);
if(n) {
n = CURLMIN(n, len);
memcpy(p, buf, n);
chunk->w_offset += n;
}
return n;
}
static size_t chunk_read(struct buf_chunk *chunk,
unsigned char *buf, size_t len)
{
unsigned char *p = &chunk->x.data[chunk->r_offset];
size_t n = chunk->w_offset - chunk->r_offset;
DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
if(!n) {
return 0;
}
else if(n <= len) {
memcpy(buf, p, n);
chunk->r_offset = chunk->w_offset = 0;
return n;
}
else {
memcpy(buf, p, len);
chunk->r_offset += len;
return len;
}
}
static ssize_t chunk_slurpn(struct buf_chunk *chunk, size_t max_len,
Curl_bufq_reader *reader,
void *reader_ctx, CURLcode *err)
{
unsigned char *p = &chunk->x.data[chunk->w_offset];
size_t n = chunk->dlen - chunk->w_offset; /* free amount */
ssize_t nread;
DEBUGASSERT(chunk->dlen >= chunk->w_offset);
if(!n) {
*err = CURLE_AGAIN;
return -1;
}
if(max_len && n > max_len)
n = max_len;
nread = reader(reader_ctx, p, n, err);
if(nread > 0) {
DEBUGASSERT((size_t)nread <= n);
chunk->w_offset += nread;
}
return nread;
}
static void chunk_peek(const struct buf_chunk *chunk,
const unsigned char **pbuf, size_t *plen)
{
DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
*pbuf = &chunk->x.data[chunk->r_offset];
*plen = chunk->w_offset - chunk->r_offset;
}
static void chunk_peek_at(const struct buf_chunk *chunk, size_t offset,
const unsigned char **pbuf, size_t *plen)
{
offset += chunk->r_offset;
DEBUGASSERT(chunk->w_offset >= offset);
*pbuf = &chunk->x.data[offset];
*plen = chunk->w_offset - offset;
}
static size_t chunk_skip(struct buf_chunk *chunk, size_t amount)
{
size_t n = chunk->w_offset - chunk->r_offset;
DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
if(n) {
n = CURLMIN(n, amount);
chunk->r_offset += n;
if(chunk->r_offset == chunk->w_offset)
chunk->r_offset = chunk->w_offset = 0;
}
return n;
}
static void chunk_shift(struct buf_chunk *chunk)
{
if(chunk->r_offset) {
if(!chunk_is_empty(chunk)) {
size_t n = chunk->w_offset - chunk->r_offset;
memmove(chunk->x.data, chunk->x.data + chunk->r_offset, n);
chunk->w_offset -= chunk->r_offset;
chunk->r_offset = 0;
}
else {
chunk->r_offset = chunk->w_offset = 0;
}
}
}
static void chunk_list_free(struct buf_chunk **anchor)
{
struct buf_chunk *chunk;
while(*anchor) {
chunk = *anchor;
*anchor = chunk->next;
free(chunk);
}
}
void Curl_bufcp_init(struct bufc_pool *pool,
size_t chunk_size, size_t spare_max)
{
DEBUGASSERT(chunk_size > 0);
DEBUGASSERT(spare_max > 0);
memset(pool, 0, sizeof(*pool));
pool->chunk_size = chunk_size;
pool->spare_max = spare_max;
}
static CURLcode bufcp_take(struct bufc_pool *pool,
struct buf_chunk **pchunk)
{
struct buf_chunk *chunk = NULL;
if(pool->spare) {
chunk = pool->spare;
pool->spare = chunk->next;
--pool->spare_count;
chunk_reset(chunk);
*pchunk = chunk;
return CURLE_OK;
}
chunk = calloc(1, sizeof(*chunk) + pool->chunk_size);
if(!chunk) {
*pchunk = NULL;
return CURLE_OUT_OF_MEMORY;
}
chunk->dlen = pool->chunk_size;
*pchunk = chunk;
return CURLE_OK;
}
static void bufcp_put(struct bufc_pool *pool,
struct buf_chunk *chunk)
{
if(pool->spare_count >= pool->spare_max) {
free(chunk);
}
else {
chunk_reset(chunk);
chunk->next = pool->spare;
pool->spare = chunk;
++pool->spare_count;
}
}
void Curl_bufcp_free(struct bufc_pool *pool)
{
chunk_list_free(&pool->spare);
pool->spare_count = 0;
}
static void bufq_init(struct bufq *q, struct bufc_pool *pool,
size_t chunk_size, size_t max_chunks, int opts)
{
DEBUGASSERT(chunk_size > 0);
DEBUGASSERT(max_chunks > 0);
memset(q, 0, sizeof(*q));
q->chunk_size = chunk_size;
q->max_chunks = max_chunks;
q->pool = pool;
q->opts = opts;
}
void Curl_bufq_init2(struct bufq *q, size_t chunk_size, size_t max_chunks,
int opts)
{
bufq_init(q, NULL, chunk_size, max_chunks, opts);
}
void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks)
{
bufq_init(q, NULL, chunk_size, max_chunks, BUFQ_OPT_NONE);
}
void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool,
size_t max_chunks, int opts)
{
bufq_init(q, pool, pool->chunk_size, max_chunks, opts);
}
void Curl_bufq_free(struct bufq *q)
{
chunk_list_free(&q->head);
chunk_list_free(&q->spare);
q->tail = NULL;
q->chunk_count = 0;
}
void Curl_bufq_reset(struct bufq *q)
{
struct buf_chunk *chunk;
while(q->head) {
chunk = q->head;
q->head = chunk->next;
chunk->next = q->spare;
q->spare = chunk;
}
q->tail = NULL;
}
size_t Curl_bufq_len(const struct bufq *q)
{
const struct buf_chunk *chunk = q->head;
size_t len = 0;
while(chunk) {
len += chunk_len(chunk);
chunk = chunk->next;
}
return len;
}
size_t Curl_bufq_space(const struct bufq *q)
{
size_t space = 0;
if(q->tail)
space += chunk_space(q->tail);
if(q->spare) {
struct buf_chunk *chunk = q->spare;
while(chunk) {
space += chunk->dlen;
chunk = chunk->next;
}
}
if(q->chunk_count < q->max_chunks) {
space += (q->max_chunks - q->chunk_count) * q->chunk_size;
}
return space;
}
bool Curl_bufq_is_empty(const struct bufq *q)
{
return !q->head || chunk_is_empty(q->head);
}
bool Curl_bufq_is_full(const struct bufq *q)
{
if(!q->tail || q->spare)
return FALSE;
if(q->chunk_count < q->max_chunks)
return FALSE;
if(q->chunk_count > q->max_chunks)
return TRUE;
/* we have no spares and cannot make more, is the tail full? */
return chunk_is_full(q->tail);
}
static struct buf_chunk *get_spare(struct bufq *q)
{
struct buf_chunk *chunk = NULL;
if(q->spare) {
chunk = q->spare;
q->spare = chunk->next;
chunk_reset(chunk);
return chunk;
}
if(q->chunk_count >= q->max_chunks && (!(q->opts & BUFQ_OPT_SOFT_LIMIT)))
return NULL;
if(q->pool) {
if(bufcp_take(q->pool, &chunk))
return NULL;
++q->chunk_count;
return chunk;
}
else {
chunk = calloc(1, sizeof(*chunk) + q->chunk_size);
if(!chunk)
return NULL;
chunk->dlen = q->chunk_size;
++q->chunk_count;
return chunk;
}
}
static void prune_head(struct bufq *q)
{
struct buf_chunk *chunk;
while(q->head && chunk_is_empty(q->head)) {
chunk = q->head;
q->head = chunk->next;
if(q->tail == chunk)
q->tail = q->head;
if(q->pool) {
bufcp_put(q->pool, chunk);
--q->chunk_count;
}
else if((q->chunk_count > q->max_chunks) ||
(q->opts & BUFQ_OPT_NO_SPARES)) {
/* SOFT_LIMIT allowed us more than max. free spares until
* we are at max again. Or free them if we are configured
* to not use spares. */
free(chunk);
--q->chunk_count;
}
else {
chunk->next = q->spare;
q->spare = chunk;
}
}
}
static struct buf_chunk *get_non_full_tail(struct bufq *q)
{
struct buf_chunk *chunk;
if(q->tail && !chunk_is_full(q->tail))
return q->tail;
chunk = get_spare(q);
if(chunk) {
/* new tail, and possibly new head */
if(q->tail) {
q->tail->next = chunk;
q->tail = chunk;
}
else {
DEBUGASSERT(!q->head);
q->head = q->tail = chunk;
}
}
return chunk;
}
ssize_t Curl_bufq_write(struct bufq *q,
const unsigned char *buf, size_t len,
CURLcode *err)
{
struct buf_chunk *tail;
ssize_t nwritten = 0;
size_t n;
DEBUGASSERT(q->max_chunks > 0);
while(len) {
tail = get_non_full_tail(q);
if(!tail) {
if(q->chunk_count < q->max_chunks) {
*err = CURLE_OUT_OF_MEMORY;
return -1;
}
break;
}
n = chunk_append(tail, buf, len);
DEBUGASSERT(n);
nwritten += n;
buf += n;
len -= n;
}
if(nwritten == 0 && len) {
*err = CURLE_AGAIN;
return -1;
}
*err = CURLE_OK;
return nwritten;
}
ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len,
CURLcode *err)
{
ssize_t nread = 0;
size_t n;
*err = CURLE_OK;
while(len && q->head) {
n = chunk_read(q->head, buf, len);
if(n) {
nread += n;
buf += n;
len -= n;
}
prune_head(q);
}
if(nread == 0) {
*err = CURLE_AGAIN;
return -1;
}
return nread;
}
bool Curl_bufq_peek(struct bufq *q,
const unsigned char **pbuf, size_t *plen)
{
if(q->head && chunk_is_empty(q->head)) {
prune_head(q);
}
if(q->head && !chunk_is_empty(q->head)) {
chunk_peek(q->head, pbuf, plen);
return TRUE;
}
*pbuf = NULL;
*plen = 0;
return FALSE;
}
bool Curl_bufq_peek_at(struct bufq *q, size_t offset,
const unsigned char **pbuf, size_t *plen)
{
struct buf_chunk *c = q->head;
size_t clen;
while(c) {
clen = chunk_len(c);
if(!clen)
break;
if(offset >= clen) {
offset -= clen;
c = c->next;
continue;
}
chunk_peek_at(c, offset, pbuf, plen);
return TRUE;
}
*pbuf = NULL;
*plen = 0;
return FALSE;
}
void Curl_bufq_skip(struct bufq *q, size_t amount)
{
size_t n;
while(amount && q->head) {
n = chunk_skip(q->head, amount);
amount -= n;
prune_head(q);
}
}
void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount)
{
Curl_bufq_skip(q, amount);
if(q->tail)
chunk_shift(q->tail);
}
ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer,
void *writer_ctx, CURLcode *err)
{
const unsigned char *buf;
size_t blen;
ssize_t nwritten = 0;
while(Curl_bufq_peek(q, &buf, &blen)) {
ssize_t chunk_written;
chunk_written = writer(writer_ctx, buf, blen, err);
if(chunk_written < 0) {
if(!nwritten || *err != CURLE_AGAIN) {
/* blocked on first write or real error, fail */
nwritten = -1;
}
break;
}
Curl_bufq_skip(q, (size_t)chunk_written);
nwritten += chunk_written;
}
return nwritten;
}
ssize_t Curl_bufq_write_pass(struct bufq *q,
const unsigned char *buf, size_t len,
Curl_bufq_writer *writer, void *writer_ctx,
CURLcode *err)
{
ssize_t nwritten = 0, n;
*err = CURLE_OK;
while(len) {
if(Curl_bufq_is_full(q)) {
/* try to make room in case we are full */
n = Curl_bufq_pass(q, writer, writer_ctx, err);
if(n < 0) {
if(*err != CURLE_AGAIN) {
/* real error, fail */
return -1;
}
/* would block */
}
}
/* Add whatever is remaining now to bufq */
n = Curl_bufq_write(q, buf, len, err);
if(n < 0) {
if(*err != CURLE_AGAIN) {
/* real error, fail */
return -1;
}
/* no room in bufq, bail out */
goto out;
}
/* Maybe only part of `data` has been added, continue to loop */
buf += (size_t)n;
len -= (size_t)n;
nwritten += (size_t)n;
}
out:
return nwritten;
}
ssize_t Curl_bufq_sipn(struct bufq *q, size_t max_len,
Curl_bufq_reader *reader, void *reader_ctx,
CURLcode *err)
{
struct buf_chunk *tail = NULL;
ssize_t nread;
*err = CURLE_AGAIN;
tail = get_non_full_tail(q);
if(!tail) {
if(q->chunk_count < q->max_chunks) {
*err = CURLE_OUT_OF_MEMORY;
return -1;
}
/* full, blocked */
*err = CURLE_AGAIN;
return -1;
}
nread = chunk_slurpn(tail, max_len, reader, reader_ctx, err);
if(nread < 0) {
return -1;
}
else if(nread == 0) {
/* eof */
*err = CURLE_OK;
}
return nread;
}
/**
* Read up to `max_len` bytes and append it to the end of the buffer queue.
* if `max_len` is 0, no limit is imposed and the call behaves exactly
* the same as `Curl_bufq_slurp()`.
* Returns the total amount of buf read (may be 0) or -1 on other
* reader errors.
* Note that even in case of a -1 chunks may have been read and
* the buffer queue will have different length than before.
*/
static ssize_t bufq_slurpn(struct bufq *q, size_t max_len,
Curl_bufq_reader *reader, void *reader_ctx,
CURLcode *err)
{
ssize_t nread = 0, n;
*err = CURLE_AGAIN;
while(1) {
n = Curl_bufq_sipn(q, max_len, reader, reader_ctx, err);
if(n < 0) {
if(!nread || *err != CURLE_AGAIN) {
/* blocked on first read or real error, fail */
nread = -1;
}
else
*err = CURLE_OK;
break;
}
else if(n == 0) {
/* eof */
*err = CURLE_OK;
break;
}
nread += (size_t)n;
if(max_len) {
DEBUGASSERT((size_t)n <= max_len);
max_len -= (size_t)n;
if(!max_len)
break;
}
/* give up slurping when we get less bytes than we asked for */
if(q->tail && !chunk_is_full(q->tail))
break;
}
return nread;
}
ssize_t Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader,
void *reader_ctx, CURLcode *err)
{
return bufq_slurpn(q, 0, reader, reader_ctx, err);
}
+271
View File
@@ -0,0 +1,271 @@
#ifndef HEADER_CURL_BUFQ_H
#define HEADER_CURL_BUFQ_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 <curl/curl.h>
/**
* A chunk of bytes for reading and writing.
* The size is fixed a creation with read and write offset
* for where unread content is.
*/
struct buf_chunk {
struct buf_chunk *next; /* to keep it in a list */
size_t dlen; /* the amount of allocated x.data[] */
size_t r_offset; /* first unread bytes */
size_t w_offset; /* one after last written byte */
union {
unsigned char data[1]; /* the buffer for `dlen` bytes */
void *dummy; /* alignment */
} x;
};
/**
* A pool for providing/keeping a number of chunks of the same size
*
* The same pool can be shared by many `bufq` instances. However, a pool
* is not thread safe. All bufqs using it are supposed to operate in the
* same thread.
*/
struct bufc_pool {
struct buf_chunk *spare; /* list of available spare chunks */
size_t chunk_size; /* the size of chunks in this pool */
size_t spare_count; /* current number of spare chunks in list */
size_t spare_max; /* max number of spares to keep */
};
void Curl_bufcp_init(struct bufc_pool *pool,
size_t chunk_size, size_t spare_max);
void Curl_bufcp_free(struct bufc_pool *pool);
/**
* A queue of byte chunks for reading and writing.
* Reading is done from `head`, writing is done to `tail`.
*
* `bufq`s can be empty or full or neither. Its `len` is the number
* of bytes that can be read. For an empty bufq, `len` will be 0.
*
* By default, a bufq can hold up to `max_chunks * chunk_size` number
* of bytes. When `max_chunks` are used (in the `head` list) and the
* `tail` chunk is full, the bufq will report that it is full.
*
* On a full bufq, `len` may be less than the maximum number of bytes,
* e.g. when the head chunk is partially read. `len` may also become
* larger than the max when option `BUFQ_OPT_SOFT_LIMIT` is used.
*
* By default, writing to a full bufq will return (-1, CURLE_AGAIN). Same
* as reading from an empty bufq.
* With `BUFQ_OPT_SOFT_LIMIT` set, a bufq will allow writing becond this
* limit and use more than `max_chunks`. However it will report that it
* is full nevertheless. This is provided for situation where writes
* preferably never fail (except for memory exhaustion).
*
* By default and without a pool, a bufq will keep chunks that read
* read empty in its `spare` list. Option `BUFQ_OPT_NO_SPARES` will
* disable that and free chunks once they become empty.
*
* When providing a pool to a bufq, all chunk creation and spare handling
* will be delegated to that pool.
*/
struct bufq {
struct buf_chunk *head; /* chunk with bytes to read from */
struct buf_chunk *tail; /* chunk to write to */
struct buf_chunk *spare; /* list of free chunks, unless `pool` */
struct bufc_pool *pool; /* optional pool for free chunks */
size_t chunk_count; /* current number of chunks in `head+spare` */
size_t max_chunks; /* max `head` chunks to use */
size_t chunk_size; /* size of chunks to manage */
int opts; /* options for handling queue, see below */
};
/**
* Default behaviour: chunk limit is "hard", meaning attempts to write
* more bytes than can be hold in `max_chunks` is refused and will return
* -1, CURLE_AGAIN. */
#define BUFQ_OPT_NONE (0)
/**
* Make `max_chunks` a "soft" limit. A bufq will report that it is "full"
* when `max_chunks` are used, but allows writing beyond this limit.
*/
#define BUFQ_OPT_SOFT_LIMIT (1 << 0)
/**
* Do not keep spare chunks.
*/
#define BUFQ_OPT_NO_SPARES (1 << 1)
/**
* Initialize a buffer queue that can hold up to `max_chunks` buffers
* each of size `chunk_size`. The bufq will not allow writing of
* more bytes than can be held in `max_chunks`.
*/
void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks);
/**
* Initialize a buffer queue that can hold up to `max_chunks` buffers
* each of size `chunk_size` with the given options. See `BUFQ_OPT_*`.
*/
void Curl_bufq_init2(struct bufq *q, size_t chunk_size,
size_t max_chunks, int opts);
void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool,
size_t max_chunks, int opts);
/**
* Reset the buffer queue to be empty. Will keep any allocated buffer
* chunks around.
*/
void Curl_bufq_reset(struct bufq *q);
/**
* Free all resources held by the buffer queue.
*/
void Curl_bufq_free(struct bufq *q);
/**
* Return the total amount of data in the queue.
*/
size_t Curl_bufq_len(const struct bufq *q);
/**
* Return the total amount of free space in the queue.
* The returned length is the number of bytes that can
* be expected to be written successfully to the bufq,
* providing no memory allocations fail.
*/
size_t Curl_bufq_space(const struct bufq *q);
/**
* Returns TRUE iff there is no data in the buffer queue.
*/
bool Curl_bufq_is_empty(const struct bufq *q);
/**
* Returns TRUE iff there is no space left in the buffer queue.
*/
bool Curl_bufq_is_full(const struct bufq *q);
/**
* Write buf to the end of the buffer queue. The buf is copied
* and the amount of copied bytes is returned.
* A return code of -1 indicates an error, setting `err` to the
* cause. An err of CURLE_AGAIN is returned if the buffer queue is full.
*/
ssize_t Curl_bufq_write(struct bufq *q,
const unsigned char *buf, size_t len,
CURLcode *err);
/**
* Read buf from the start of the buffer queue. The buf is copied
* and the amount of copied bytes is returned.
* A return code of -1 indicates an error, setting `err` to the
* cause. An err of CURLE_AGAIN is returned if the buffer queue is empty.
*/
ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len,
CURLcode *err);
/**
* Peek at the head chunk in the buffer queue. Returns a pointer to
* the chunk buf (at the current offset) and its length. Does not
* modify the buffer queue.
* Returns TRUE iff bytes are available. Sets `pbuf` to NULL and `plen`
* to 0 when no bytes are available.
* Repeated calls return the same information until the buffer queue
* is modified, see `Curl_bufq_skip()``
*/
bool Curl_bufq_peek(struct bufq *q,
const unsigned char **pbuf, size_t *plen);
bool Curl_bufq_peek_at(struct bufq *q, size_t offset,
const unsigned char **pbuf, size_t *plen);
/**
* Tell the buffer queue to discard `amount` buf bytes at the head
* of the queue. Skipping more buf than is currently buffered will
* just empty the queue.
*/
void Curl_bufq_skip(struct bufq *q, size_t amount);
/**
* Same as `skip` but shift tail data to the start afterwards,
* so that further writes will find room in tail.
*/
void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount);
typedef ssize_t Curl_bufq_writer(void *writer_ctx,
const unsigned char *buf, size_t len,
CURLcode *err);
/**
* Passes the chunks in the buffer queue to the writer and returns
* the amount of buf written. A writer may return -1 and CURLE_AGAIN
* to indicate blocking at which point the queue will stop and return
* the amount of buf passed so far.
* -1 is returned on any other errors reported by the writer.
* Note that in case of a -1 chunks may have been written and
* the buffer queue will have different length than before.
*/
ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer,
void *writer_ctx, CURLcode *err);
typedef ssize_t Curl_bufq_reader(void *reader_ctx,
unsigned char *buf, size_t len,
CURLcode *err);
/**
* Read date and append it to the end of the buffer queue until the
* reader returns blocking or the queue is full. A reader returns
* -1 and CURLE_AGAIN to indicate blocking.
* Returns the total amount of buf read (may be 0) or -1 on other
* reader errors.
* Note that in case of a -1 chunks may have been read and
* the buffer queue will have different length than before.
*/
ssize_t Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader,
void *reader_ctx, CURLcode *err);
/**
* Read *once* up to `max_len` bytes and append it to the buffer.
* if `max_len` is 0, no limit is imposed besides the chunk space.
* Returns the total amount of buf read (may be 0) or -1 on other
* reader errors.
*/
ssize_t Curl_bufq_sipn(struct bufq *q, size_t max_len,
Curl_bufq_reader *reader, void *reader_ctx,
CURLcode *err);
/**
* Write buf to the end of the buffer queue.
* Will write bufq content or passed `buf` directly using the `writer`
* callback when it sees fit. 'buf' might get passed directly
* on or is placed into the buffer, depending on `len` and current
* amount buffered, chunk size, etc.
*/
ssize_t Curl_bufq_write_pass(struct bufq *q,
const unsigned char *buf, size_t len,
Curl_bufq_writer *writer, void *writer_ctx,
CURLcode *err);
#endif /* HEADER_CURL_BUFQ_H */
+1 -1
View File
@@ -1212,7 +1212,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
Curl_safefree(data->state.aptr.userpwd);
Curl_safefree(data->state.aptr.proxyuserpwd);
return CURLE_OK;
error:
error:
DEBUGASSERT(result);
if(io)
hyper_io_free(io);
+1184
View File
@@ -0,0 +1,1184 @@
/***************************************************************************
* _ _ ____ _
* 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_PROXY) && !defined(CURL_DISABLE_HTTP)
#include <curl/curl.h>
#ifdef USE_HYPER
#include <hyper.h>
#endif
#include "urldata.h"
#include "dynbuf.h"
#include "sendf.h"
#include "http.h"
#include "http_proxy.h"
#include "url.h"
#include "select.h"
#include "progress.h"
#include "cfilters.h"
#include "cf-h1-proxy.h"
#include "connect.h"
#include "curl_log.h"
#include "curlx.h"
#include "vtls/vtls.h"
#include "transfer.h"
#include "multiif.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
typedef enum {
TUNNEL_INIT, /* init/default/no tunnel state */
TUNNEL_CONNECT, /* CONNECT request is being send */
TUNNEL_RECEIVE, /* CONNECT answer is being received */
TUNNEL_RESPONSE, /* CONNECT response received completely */
TUNNEL_ESTABLISHED,
TUNNEL_FAILED
} tunnel_state;
/* struct for HTTP CONNECT tunneling */
struct tunnel_state {
int sockindex;
const char *hostname;
int remote_port;
struct HTTP CONNECT;
struct dynbuf rcvbuf;
struct dynbuf req;
size_t nsend;
size_t headerlines;
enum keeponval {
KEEPON_DONE,
KEEPON_CONNECT,
KEEPON_IGNORE
} keepon;
curl_off_t cl; /* size of content to read and ignore */
tunnel_state tunnel_state;
BIT(chunked_encoding);
BIT(close_connection);
};
static bool tunnel_is_established(struct tunnel_state *ts)
{
return ts && (ts->tunnel_state == TUNNEL_ESTABLISHED);
}
static bool tunnel_is_failed(struct tunnel_state *ts)
{
return ts && (ts->tunnel_state == TUNNEL_FAILED);
}
static CURLcode tunnel_reinit(struct tunnel_state *ts,
struct connectdata *conn,
struct Curl_easy *data)
{
(void)data;
DEBUGASSERT(ts);
Curl_dyn_reset(&ts->rcvbuf);
Curl_dyn_reset(&ts->req);
ts->tunnel_state = TUNNEL_INIT;
ts->keepon = KEEPON_CONNECT;
ts->cl = 0;
ts->close_connection = FALSE;
if(conn->bits.conn_to_host)
ts->hostname = conn->conn_to_host.name;
else if(ts->sockindex == SECONDARYSOCKET)
ts->hostname = conn->secondaryhostname;
else
ts->hostname = conn->host.name;
if(ts->sockindex == SECONDARYSOCKET)
ts->remote_port = conn->secondary_port;
else if(conn->bits.conn_to_port)
ts->remote_port = conn->conn_to_port;
else
ts->remote_port = conn->remote_port;
return CURLE_OK;
}
static CURLcode tunnel_init(struct tunnel_state **pts,
struct Curl_easy *data,
struct connectdata *conn,
int sockindex)
{
struct tunnel_state *ts;
CURLcode result;
if(conn->handler->flags & PROTOPT_NOTCPPROXY) {
failf(data, "%s cannot be done over CONNECT", conn->handler->scheme);
return CURLE_UNSUPPORTED_PROTOCOL;
}
/* we might need the upload buffer for streaming a partial request */
result = Curl_get_upload_buffer(data);
if(result)
return result;
ts = calloc(1, sizeof(*ts));
if(!ts)
return CURLE_OUT_OF_MEMORY;
ts->sockindex = sockindex;
infof(data, "allocate connect buffer");
Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST);
*pts = ts;
connkeep(conn, "HTTP proxy CONNECT");
return tunnel_reinit(ts, conn, data);
}
static void tunnel_go_state(struct Curl_cfilter *cf,
struct tunnel_state *ts,
tunnel_state new_state,
struct Curl_easy *data)
{
if(ts->tunnel_state == new_state)
return;
/* leaving this one */
switch(ts->tunnel_state) {
case TUNNEL_CONNECT:
data->req.ignorebody = FALSE;
break;
default:
break;
}
/* 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:
if(new_state == 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->info.httpcode = 0; /* clear it as it might've been used for the
proxy */
/* If a proxy-authorization header was used for the proxy, then we should
make sure that it isn't accidentally used for the document request
after we've connected. So let's free and clear it here. */
Curl_safefree(data->state.aptr.proxyuserpwd);
#ifdef USE_HYPER
data->state.hconnect = FALSE;
#endif
break;
}
}
static void tunnel_free(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct tunnel_state *ts = cf->ctx;
if(ts) {
tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
Curl_dyn_free(&ts->rcvbuf);
Curl_dyn_free(&ts->req);
free(ts);
cf->ctx = NULL;
}
}
static CURLcode CONNECT_host(struct Curl_easy *data,
struct connectdata *conn,
const char *hostname,
int remote_port,
char **connecthostp,
char **hostp)
{
char *hostheader; /* for CONNECT */
char *host = NULL; /* Host: */
bool ipv6_ip = conn->bits.ipv6_ip;
/* the hostname may be different */
if(hostname != conn->host.name)
ipv6_ip = (strchr(hostname, ':') != NULL);
hostheader = /* host:port with IPv6 support */
aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
remote_port);
if(!hostheader)
return CURLE_OUT_OF_MEMORY;
if(!Curl_checkProxyheaders(data, conn, STRCONST("Host"))) {
host = aprintf("Host: %s\r\n", hostheader);
if(!host) {
free(hostheader);
return CURLE_OUT_OF_MEMORY;
}
}
*connecthostp = hostheader;
*hostp = host;
return CURLE_OK;
}
#ifndef USE_HYPER
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;
CURLcode result;
infof(data, "Establish HTTP proxy tunnel to %s:%d",
ts->hostname, ts->remote_port);
/* This only happens if we've looped here due to authentication
reasons, and we don't really use the newly cloned URL here
then. Just free() it. */
Curl_safefree(data->req.newurl);
result = CONNECT_host(data, conn,
ts->hostname, ts->remote_port,
&hostheader, &host);
if(result)
goto out;
/* Setup the proxy-authorization header, if any */
result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
hostheader, TRUE);
if(result)
goto out;
httpv = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? "1.0" : "1.1";
result =
Curl_dyn_addf(&ts->req,
"CONNECT %s HTTP/%s\r\n"
"%s" /* Host: */
"%s", /* Proxy-Authorization */
hostheader,
httpv,
host?host:"",
data->state.aptr.proxyuserpwd?
data->state.aptr.proxyuserpwd:"");
if(result)
goto out;
if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent"))
&& data->set.str[STRING_USERAGENT])
result = Curl_dyn_addf(&ts->req, "User-Agent: %s\r\n",
data->set.str[STRING_USERAGENT]);
if(result)
goto out;
if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection")))
result = Curl_dyn_addn(&ts->req,
STRCONST("Proxy-Connection: Keep-Alive\r\n"));
if(result)
goto out;
result = Curl_add_custom_headers(data, TRUE, &ts->req);
if(result)
goto out;
/* CRLF terminate the request */
result = Curl_dyn_addn(&ts->req, STRCONST("\r\n"));
if(result)
goto out;
/* Send the connect request to the proxy */
result = Curl_buffer_send(&ts->req, data, &ts->CONNECT,
&data->info.request_size, 0,
ts->sockindex);
ts->headerlines = 0;
out:
if(result)
failf(data, "Failed sending CONNECT to proxy");
free(host);
free(hostheader);
return result;
}
static CURLcode send_CONNECT(struct Curl_easy *data,
struct connectdata *conn,
struct tunnel_state *ts,
bool *done)
{
struct SingleRequest *k = &data->req;
struct HTTP *http = &ts->CONNECT;
CURLcode result = CURLE_OK;
if(http->sending != HTTPSEND_REQUEST)
goto out;
if(!ts->nsend) {
size_t fillcount;
k->upload_fromhere = data->state.ulbuf;
result = Curl_fillreadbuffer(data, data->set.upload_buffer_size,
&fillcount);
if(result)
goto out;
ts->nsend = fillcount;
}
if(ts->nsend) {
ssize_t bytes_written;
/* write to socket (send away data) */
result = Curl_write(data,
conn->writesockfd, /* socket to send to */
k->upload_fromhere, /* buffer pointer */
ts->nsend, /* buffer size */
&bytes_written); /* actually sent */
if(result)
goto out;
/* send to debug callback! */
Curl_debug(data, CURLINFO_HEADER_OUT,
k->upload_fromhere, bytes_written);
ts->nsend -= bytes_written;
k->upload_fromhere += bytes_written;
}
if(!ts->nsend)
http->sending = HTTPSEND_NADA;
out:
if(result)
failf(data, "Failed sending CONNECT to proxy");
*done = (http->sending != HTTPSEND_REQUEST);
return result;
}
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;
(void)cf;
if((checkprefix("WWW-Authenticate:", header) &&
(401 == k->httpcode)) ||
(checkprefix("Proxy-authenticate:", header) &&
(407 == k->httpcode))) {
bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
char *auth = Curl_copy_header_value(header);
if(!auth)
return CURLE_OUT_OF_MEMORY;
DEBUGF(LOG_CF(data, cf, "CONNECT: fwd auth header '%s'", header));
result = Curl_http_input_auth(data, proxy, auth);
free(auth);
if(result)
return result;
}
else if(checkprefix("Content-Length:", header)) {
if(k->httpcode/100 == 2) {
/* A client MUST ignore any Content-Length or Transfer-Encoding
header fields received in a successful response to CONNECT.
"Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
infof(data, "Ignoring Content-Length in CONNECT %03d response",
k->httpcode);
}
else {
(void)curlx_strtoofft(header + strlen("Content-Length:"),
NULL, 10, &ts->cl);
}
}
else if(Curl_compareheader(header,
STRCONST("Connection:"), STRCONST("close")))
ts->close_connection = TRUE;
else if(checkprefix("Transfer-Encoding:", header)) {
if(k->httpcode/100 == 2) {
/* A client MUST ignore any Content-Length or Transfer-Encoding
header fields received in a successful response to CONNECT.
"Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
infof(data, "Ignoring Transfer-Encoding in "
"CONNECT %03d response", k->httpcode);
}
else if(Curl_compareheader(header,
STRCONST("Transfer-Encoding:"),
STRCONST("chunked"))) {
infof(data, "CONNECT responded chunked");
ts->chunked_encoding = TRUE;
/* init our chunky engine */
Curl_httpchunk_init(data);
}
}
else if(Curl_compareheader(header,
STRCONST("Proxy-Connection:"),
STRCONST("close")))
ts->close_connection = TRUE;
else if(!strncmp(header, "HTTP/1.", 7) &&
((header[7] == '0') || (header[7] == '1')) &&
(header[8] == ' ') &&
ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
!ISDIGIT(header[12])) {
/* store the HTTP code from the proxy */
data->info.httpproxycode = k->httpcode = (header[9] - '0') * 100 +
(header[10] - '0') * 10 + (header[11] - '0');
}
return result;
}
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 = Curl_conn_cf_get_socket(cf, data);
char *linep;
size_t perline;
int error;
#define SELECT_OK 0
#define SELECT_ERROR 1
error = SELECT_OK;
*done = FALSE;
if(!Curl_conn_data_pending(data, ts->sockindex))
return CURLE_OK;
while(ts->keepon) {
ssize_t gotbytes;
char byte;
/* Read one byte at a time to avoid a race condition. Wait at most one
second before looping to ensure continuous pgrsUpdates. */
result = Curl_read(data, tunnelsocket, &byte, 1, &gotbytes);
if(result == CURLE_AGAIN)
/* socket buffer drained, return */
return CURLE_OK;
if(Curl_pgrsUpdate(data))
return CURLE_ABORTED_BY_CALLBACK;
if(result) {
ts->keepon = KEEPON_DONE;
break;
}
if(gotbytes <= 0) {
if(data->set.proxyauth && data->state.authproxy.avail &&
data->state.aptr.proxyuserpwd) {
/* proxy auth was requested and there was proxy auth available,
then deem this as "mere" proxy disconnect */
ts->close_connection = TRUE;
infof(data, "Proxy CONNECT connection closed");
}
else {
error = SELECT_ERROR;
failf(data, "Proxy CONNECT aborted");
}
ts->keepon = KEEPON_DONE;
break;
}
if(ts->keepon == KEEPON_IGNORE) {
/* This means we are currently ignoring a response-body */
if(ts->cl) {
/* A Content-Length based body: simply count down the counter
and make sure to break out of the loop when we're done! */
ts->cl--;
if(ts->cl <= 0) {
ts->keepon = KEEPON_DONE;
break;
}
}
else {
/* chunked-encoded body, so we need to do the chunked dance
properly to know when the end of the body is reached */
CHUNKcode r;
CURLcode extra;
ssize_t tookcareof = 0;
/* now parse the chunked piece of data so that we can
properly tell when the stream ends */
r = Curl_httpchunk_read(data, &byte, 1, &tookcareof, &extra);
if(r == CHUNKE_STOP) {
/* we're done reading chunks! */
infof(data, "chunk reading DONE");
ts->keepon = KEEPON_DONE;
}
}
continue;
}
if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) {
failf(data, "CONNECT response too large");
return CURLE_RECV_ERROR;
}
/* if this is not the end of a header line then continue */
if(byte != 0x0a)
continue;
ts->headerlines++;
linep = Curl_dyn_ptr(&ts->rcvbuf);
perline = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */
/* output debug if that is requested */
Curl_debug(data, CURLINFO_HEADER_IN, linep, perline);
if(!data->set.suppress_connect_headers) {
/* send the header to the callback */
int writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
(data->set.include_header ? CLIENTWRITE_BODY : 0) |
(ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
result = Curl_client_write(data, writetype, linep, perline);
if(result)
return result;
}
data->info.header_size += (long)perline;
/* Newlines are CRLF, so the CR is ignored as the line isn't
really terminated until the LF comes. Treat a following CR
as end-of-headers as well.*/
if(('\r' == linep[0]) ||
('\n' == linep[0])) {
/* end of response-headers from the proxy */
if((407 == k->httpcode) && !data->state.authproblem) {
/* If we get a 407 response code with content length
when we have no auth problem, we must ignore the
whole response-body */
ts->keepon = KEEPON_IGNORE;
if(ts->cl) {
infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
" bytes of response-body", ts->cl);
}
else if(ts->chunked_encoding) {
CHUNKcode r;
CURLcode extra;
infof(data, "Ignore chunked response-body");
/* We set ignorebody true here since the chunked decoder
function will acknowledge that. Pay attention so that this is
cleared again when this function returns! */
k->ignorebody = TRUE;
if(linep[1] == '\n')
/* this can only be a LF if the letter at index 0 was a CR */
linep++;
/* now parse the chunked piece of data so that we can properly
tell when the stream ends */
r = Curl_httpchunk_read(data, linep + 1, 1, &gotbytes,
&extra);
if(r == CHUNKE_STOP) {
/* we're done reading chunks! */
infof(data, "chunk reading DONE");
ts->keepon = KEEPON_DONE;
}
}
else {
/* without content-length or chunked encoding, we
can't keep the connection alive since the close is
the end signal so we bail out at once instead */
DEBUGF(LOG_CF(data, cf, "CONNECT: no content-length or chunked"));
ts->keepon = KEEPON_DONE;
}
}
else {
ts->keepon = KEEPON_DONE;
}
DEBUGASSERT(ts->keepon == KEEPON_IGNORE
|| ts->keepon == KEEPON_DONE);
continue;
}
result = on_resp_header(cf, data, ts, linep);
if(result)
return result;
Curl_dyn_reset(&ts->rcvbuf);
} /* while there's buffer left and loop is requested */
if(error)
result = CURLE_RECV_ERROR;
*done = (ts->keepon == KEEPON_DONE);
if(!result && *done && data->info.httpproxycode/100 != 2) {
/* Deal with the possibly already received authenticate
headers. 'newurl' is set to a new URL if we must loop. */
result = Curl_http_auth_act(data);
}
return result;
}
#else /* USE_HYPER */
/* The Hyper version of CONNECT */
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 = Curl_conn_cf_get_socket(cf, data);
hyper_io *io = NULL;
hyper_request *req = NULL;
hyper_headers *headers = NULL;
hyper_clientconn_options *options = NULL;
hyper_task *handshake = NULL;
hyper_task *task = NULL; /* for the handshake */
hyper_clientconn *client = NULL;
hyper_task *sendtask = NULL; /* for the send */
char *hostheader = NULL; /* for CONNECT */
char *host = NULL; /* Host: */
CURLcode result = CURLE_OUT_OF_MEMORY;
io = hyper_io_new();
if(!io) {
failf(data, "Couldn't create hyper IO");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
/* tell Hyper how to read/write network data */
hyper_io_set_userdata(io, data);
hyper_io_set_read(io, Curl_hyper_recv);
hyper_io_set_write(io, Curl_hyper_send);
conn->sockfd = tunnelsocket;
data->state.hconnect = TRUE;
/* create an executor to poll futures */
if(!h->exec) {
h->exec = hyper_executor_new();
if(!h->exec) {
failf(data, "Couldn't create hyper executor");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
}
options = hyper_clientconn_options_new();
hyper_clientconn_options_set_preserve_header_case(options, 1);
hyper_clientconn_options_set_preserve_header_order(options, 1);
if(!options) {
failf(data, "Couldn't create hyper client options");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
hyper_clientconn_options_exec(options, h->exec);
/* "Both the `io` and the `options` are consumed in this function
call" */
handshake = hyper_clientconn_handshake(io, options);
if(!handshake) {
failf(data, "Couldn't create hyper client handshake");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
io = NULL;
options = NULL;
if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) {
failf(data, "Couldn't hyper_executor_push the handshake");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
handshake = NULL; /* ownership passed on */
task = hyper_executor_poll(h->exec);
if(!task) {
failf(data, "Couldn't hyper_executor_poll the handshake");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
client = hyper_task_value(task);
hyper_task_free(task);
req = hyper_request_new();
if(!req) {
failf(data, "Couldn't hyper_request_new");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(hyper_request_set_method(req, (uint8_t *)"CONNECT",
strlen("CONNECT"))) {
failf(data, "error setting method");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
infof(data, "Establish HTTP proxy tunnel to %s:%d",
ts->hostname, ts->remote_port);
/* This only happens if we've looped here due to authentication
reasons, and we don't really use the newly cloned URL here
then. Just free() it. */
Curl_safefree(data->req.newurl);
result = CONNECT_host(data, conn, ts->hostname, ts->remote_port,
&hostheader, &host);
if(result)
goto error;
if(hyper_request_set_uri(req, (uint8_t *)hostheader,
strlen(hostheader))) {
failf(data, "error setting path");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(data->set.verbose) {
char *se = aprintf("CONNECT %s HTTP/1.1\r\n", hostheader);
if(!se) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
Curl_debug(data, CURLINFO_HEADER_OUT, se, strlen(se));
free(se);
}
/* Setup the proxy-authorization header, if any */
result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
hostheader, TRUE);
if(result)
goto error;
Curl_safefree(hostheader);
/* default is 1.1 */
if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) &&
(HYPERE_OK != hyper_request_set_version(req,
HYPER_HTTP_VERSION_1_0))) {
failf(data, "error setting HTTP version");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
headers = hyper_request_headers(req);
if(!headers) {
failf(data, "hyper_request_headers");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(host) {
result = Curl_hyper_header(data, headers, host);
if(result)
goto error;
Curl_safefree(host);
}
if(data->state.aptr.proxyuserpwd) {
result = Curl_hyper_header(data, headers,
data->state.aptr.proxyuserpwd);
if(result)
goto error;
}
if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) &&
data->set.str[STRING_USERAGENT]) {
struct dynbuf ua;
Curl_dyn_init(&ua, DYN_HTTP_REQUEST);
result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n",
data->set.str[STRING_USERAGENT]);
if(result)
goto error;
result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&ua));
if(result)
goto error;
Curl_dyn_free(&ua);
}
if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) {
result = Curl_hyper_header(data, headers,
"Proxy-Connection: Keep-Alive");
if(result)
goto error;
}
result = Curl_add_custom_headers(data, TRUE, headers);
if(result)
goto error;
sendtask = hyper_clientconn_send(client, req);
if(!sendtask) {
failf(data, "hyper_clientconn_send");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) {
failf(data, "Couldn't hyper_executor_push the send");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
error:
free(host);
free(hostheader);
if(io)
hyper_io_free(io);
if(options)
hyper_clientconn_options_free(options);
if(handshake)
hyper_task_free(handshake);
if(client)
hyper_clientconn_free(client);
return result;
}
static CURLcode send_CONNECT(struct Curl_easy *data,
struct connectdata *conn,
struct tunnel_state *ts,
bool *done)
{
struct hyptransfer *h = &data->hyp;
hyper_task *task = NULL;
hyper_error *hypererr = NULL;
CURLcode result = CURLE_OK;
(void)ts;
(void)conn;
do {
task = hyper_executor_poll(h->exec);
if(task) {
bool error = hyper_task_type(task) == HYPER_TASK_ERROR;
if(error)
hypererr = hyper_task_value(task);
hyper_task_free(task);
if(error) {
/* this could probably use a better error code? */
result = CURLE_OUT_OF_MEMORY;
goto error;
}
}
} while(task);
error:
*done = (result == CURLE_OK);
if(hypererr) {
uint8_t errbuf[256];
size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf));
failf(data, "Hyper: %.*s", (int)errlen, errbuf);
hyper_error_free(hypererr);
}
return result;
}
static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct tunnel_state *ts,
bool *done)
{
struct hyptransfer *h = &data->hyp;
CURLcode result;
int didwhat;
(void)ts;
*done = FALSE;
result = Curl_hyper_stream(data, cf->conn, &didwhat, done,
CURL_CSELECT_IN | CURL_CSELECT_OUT);
if(result || !*done)
return result;
if(h->exec) {
hyper_executor_free(h->exec);
h->exec = NULL;
}
if(h->read_waker) {
hyper_waker_free(h->read_waker);
h->read_waker = NULL;
}
if(h->write_waker) {
hyper_waker_free(h->write_waker);
h->write_waker = NULL;
}
return result;
}
#endif /* USE_HYPER */
static CURLcode CONNECT(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct tunnel_state *ts)
{
struct connectdata *conn = cf->conn;
CURLcode result;
bool done;
if(tunnel_is_established(ts))
return CURLE_OK;
if(tunnel_is_failed(ts))
return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
do {
timediff_t check;
check = Curl_timeleft(data, NULL, TRUE);
if(check <= 0) {
failf(data, "Proxy CONNECT aborted due to timeout");
result = CURLE_OPERATION_TIMEDOUT;
goto out;
}
switch(ts->tunnel_state) {
case TUNNEL_INIT:
/* Prepare the CONNECT request and make a first attempt to send. */
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);
/* FALLTHROUGH */
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;
tunnel_go_state(cf, ts, TUNNEL_RECEIVE, data);
/* FALLTHROUGH */
case TUNNEL_RECEIVE:
/* read what is there */
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;
}
/* error or not complete yet. return for more multi-multi */
if(result || !done)
goto out;
/* got it */
tunnel_go_state(cf, ts, TUNNEL_RESPONSE, data);
/* 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.
*/
if(ts->close_connection || conn->bits.close) {
/* Close this filter and the sub-chain, re-connect the
* sub-chain and continue. Closing this filter will
* 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_cf_close(cf, data);
connkeep(conn, "HTTP proxy CONNECT");
result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
goto out;
}
else {
/* staying on this connection, reset state */
tunnel_go_state(cf, ts, TUNNEL_INIT, data);
}
}
break;
default:
break;
}
} while(data->req.newurl);
DEBUGASSERT(ts->tunnel_state == TUNNEL_RESPONSE);
if(data->info.httpproxycode/100 != 2) {
/* a non-2xx response and we have no next url to try. */
Curl_safefree(data->req.newurl);
/* failure, close this connection to avoid re-use */
streamclose(conn, "proxy CONNECT failure");
tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
return CURLE_RECV_ERROR;
}
/* 2xx response, SUCCESS! */
tunnel_go_state(cf, ts, TUNNEL_ESTABLISHED, data);
infof(data, "CONNECT tunnel established, response %d",
data->info.httpproxycode);
result = CURLE_OK;
out:
if(result)
tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
return result;
}
static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
{
CURLcode result;
struct tunnel_state *ts = cf->ctx;
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
DEBUGF(LOG_CF(data, cf, "connect"));
result = cf->next->cft->connect(cf->next, data, blocking, done);
if(result || !*done)
return result;
*done = FALSE;
if(!ts) {
result = tunnel_init(&ts, data, cf->conn, cf->sockindex);
if(result)
return result;
cf->ctx = ts;
}
/* TODO: can we do blocking? */
/* We want "seamless" operations through HTTP proxy tunnel */
result = CONNECT(cf, data, ts);
if(result)
goto out;
Curl_safefree(data->state.aptr.proxyuserpwd);
out:
*done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
if(*done) {
cf->connected = TRUE;
tunnel_free(cf, data);
}
return result;
}
static int cf_h1_proxy_get_select_socks(struct Curl_cfilter *cf,
struct Curl_easy *data,
curl_socket_t *socks)
{
struct tunnel_state *ts = cf->ctx;
int fds;
fds = cf->next->cft->get_select_socks(cf->next, data, socks);
if(!fds && cf->next->connected && !cf->connected) {
/* If we are not connected, but the filter "below" is
* and not waiting on something, we are tunneling. */
socks[0] = Curl_conn_cf_get_socket(cf, data);
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->CONNECT.sending == HTTPSEND_REQUEST) {
return GETSOCK_WRITESOCK(0);
}
return GETSOCK_READSOCK(0);
}
return GETSOCK_WRITESOCK(0);
}
return fds;
}
static void cf_h1_proxy_destroy(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
DEBUGF(LOG_CF(data, cf, "destroy"));
tunnel_free(cf, data);
}
static void cf_h1_proxy_close(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
DEBUGF(LOG_CF(data, cf, "close"));
cf->connected = FALSE;
if(cf->ctx) {
tunnel_go_state(cf, cf->ctx, TUNNEL_INIT, data);
}
if(cf->next)
cf->next->cft->close(cf->next, data);
}
struct Curl_cftype Curl_cft_h1_proxy = {
"H1-PROXY",
CF_TYPE_IP_CONNECT,
0,
cf_h1_proxy_destroy,
cf_h1_proxy_connect,
cf_h1_proxy_close,
Curl_cf_http_proxy_get_host,
cf_h1_proxy_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_cf_h1_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_h1_proxy, NULL);
if(!result)
Curl_conn_cf_insert_after(cf_at, cf);
return result;
}
#endif /* !CURL_DISABLE_PROXY && ! CURL_DISABLE_HTTP */
+39
View File
@@ -0,0 +1,39 @@
#ifndef HEADER_CURL_H1_PROXY_H
#define HEADER_CURL_H1_PROXY_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_PROXY) && !defined(CURL_DISABLE_HTTP)
CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf,
struct Curl_easy *data);
extern struct Curl_cftype Curl_cft_h1_proxy;
#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
#endif /* HEADER_CURL_H1_PROXY_H */
+1356
View File
@@ -0,0 +1,1356 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "curl_setup.h"
#if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY)
#include <nghttp2/nghttp2.h>
#include "urldata.h"
#include "cfilters.h"
#include "connect.h"
#include "curl_log.h"
#include "bufq.h"
#include "dynbuf.h"
#include "dynhds.h"
#include "http1.h"
#include "http_proxy.h"
#include "multiif.h"
#include "cf-h2-proxy.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
#define H2_NW_CHUNK_SIZE (128*1024)
#define H2_NW_RECV_CHUNKS 1
#define H2_NW_SEND_CHUNKS 1
#define HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */
#define H2_TUNNEL_WINDOW_SIZE (1024 * 1024)
#define H2_TUNNEL_CHUNK_SIZE (32 * 1024)
#define H2_TUNNEL_RECV_CHUNKS \
(H2_TUNNEL_WINDOW_SIZE / H2_TUNNEL_CHUNK_SIZE)
#define H2_TUNNEL_SEND_CHUNKS \
(H2_TUNNEL_WINDOW_SIZE / H2_TUNNEL_CHUNK_SIZE)
typedef enum {
TUNNEL_INIT, /* init/default/no tunnel state */
TUNNEL_CONNECT, /* CONNECT request is being send */
TUNNEL_RESPONSE, /* CONNECT response received completely */
TUNNEL_ESTABLISHED,
TUNNEL_FAILED
} tunnel_state;
struct tunnel_stream {
struct http_resp *resp;
struct bufq recvbuf;
struct bufq sendbuf;
char *authority;
int32_t stream_id;
uint32_t error;
tunnel_state state;
bool has_final_response;
bool closed;
bool reset;
};
static CURLcode tunnel_stream_init(struct Curl_cfilter *cf,
struct tunnel_stream *ts)
{
const char *hostname;
int port;
bool ipv6_ip = cf->conn->bits.ipv6_ip;
ts->state = TUNNEL_INIT;
ts->stream_id = -1;
Curl_bufq_init2(&ts->recvbuf, H2_TUNNEL_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS,
BUFQ_OPT_SOFT_LIMIT);
Curl_bufq_init(&ts->sendbuf, H2_TUNNEL_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS);
if(cf->conn->bits.conn_to_host)
hostname = cf->conn->conn_to_host.name;
else if(cf->sockindex == SECONDARYSOCKET)
hostname = cf->conn->secondaryhostname;
else
hostname = cf->conn->host.name;
if(cf->sockindex == SECONDARYSOCKET)
port = cf->conn->secondary_port;
else if(cf->conn->bits.conn_to_port)
port = cf->conn->conn_to_port;
else
port = cf->conn->remote_port;
if(hostname != cf->conn->host.name)
ipv6_ip = (strchr(hostname, ':') != NULL);
ts->authority = /* host:port with IPv6 support */
aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", port);
if(!ts->authority)
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
}
static void tunnel_stream_clear(struct tunnel_stream *ts)
{
Curl_http_resp_free(ts->resp);
Curl_bufq_free(&ts->recvbuf);
Curl_bufq_free(&ts->sendbuf);
Curl_safefree(ts->authority);
memset(ts, 0, sizeof(*ts));
ts->state = TUNNEL_INIT;
}
static void tunnel_go_state(struct Curl_cfilter *cf,
struct tunnel_stream *ts,
tunnel_state new_state,
struct Curl_easy *data)
{
(void)cf;
if(ts->state == new_state)
return;
/* leaving this one */
switch(ts->state) {
case TUNNEL_CONNECT:
data->req.ignorebody = FALSE;
break;
default:
break;
}
/* entering this one */
switch(new_state) {
case TUNNEL_INIT:
DEBUGF(LOG_CF(data, cf, "new tunnel state 'init'"));
tunnel_stream_clear(ts);
break;
case TUNNEL_CONNECT:
DEBUGF(LOG_CF(data, cf, "new tunnel state 'connect'"));
ts->state = TUNNEL_CONNECT;
break;
case TUNNEL_RESPONSE:
DEBUGF(LOG_CF(data, cf, "new tunnel state 'response'"));
ts->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:
if(new_state == TUNNEL_FAILED)
DEBUGF(LOG_CF(data, cf, "new tunnel state 'failed'"));
ts->state = new_state;
/* If a proxy-authorization header was used for the proxy, then we should
make sure that it isn't accidentally used for the document request
after we've connected. So let's free and clear it here. */
Curl_safefree(data->state.aptr.proxyuserpwd);
break;
}
}
struct cf_h2_proxy_ctx {
nghttp2_session *h2;
/* The easy handle used in the current filter call, cleared at return */
struct cf_call_data call_data;
struct bufq inbufq; /* network receive buffer */
struct bufq outbufq; /* network send buffer */
struct tunnel_stream tunnel; /* our tunnel CONNECT stream */
int32_t goaway_error;
int32_t last_stream_id;
BIT(conn_closed);
BIT(goaway);
};
/* How to access `call_data` from a cf_h2 filter */
#define CF_CTX_CALL_DATA(cf) \
((struct cf_h2_proxy_ctx *)(cf)->ctx)->call_data
static void cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx *ctx)
{
struct cf_call_data save = ctx->call_data;
if(ctx->h2) {
nghttp2_session_del(ctx->h2);
}
Curl_bufq_free(&ctx->inbufq);
Curl_bufq_free(&ctx->outbufq);
tunnel_stream_clear(&ctx->tunnel);
memset(ctx, 0, sizeof(*ctx));
ctx->call_data = save;
}
static void cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx *ctx)
{
if(ctx) {
cf_h2_proxy_ctx_clear(ctx);
free(ctx);
}
}
static ssize_t nw_in_reader(void *reader_ctx,
unsigned char *buf, size_t buflen,
CURLcode *err)
{
struct Curl_cfilter *cf = reader_ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
ssize_t nread;
nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err);
DEBUGF(LOG_CF(data, cf, "nw_in recv(len=%zu) -> %zd, %d",
buflen, nread, *err));
return nread;
}
static ssize_t nw_out_writer(void *writer_ctx,
const unsigned char *buf, size_t buflen,
CURLcode *err)
{
struct Curl_cfilter *cf = writer_ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
ssize_t nwritten;
nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, err);
DEBUGF(LOG_CF(data, cf, "nw_out send(len=%zu) -> %zd", buflen, nwritten));
return nwritten;
}
static int h2_client_new(struct Curl_cfilter *cf,
nghttp2_session_callbacks *cbs)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
nghttp2_option *o;
int rc = nghttp2_option_new(&o);
if(rc)
return rc;
/* We handle window updates ourself to enforce buffer limits */
nghttp2_option_set_no_auto_window_update(o, 1);
#if NGHTTP2_VERSION_NUM >= 0x013200
/* with 1.50.0 */
/* 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);
#endif
rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o);
nghttp2_option_del(o);
return rc;
}
static ssize_t on_session_send(nghttp2_session *h2,
const uint8_t *buf, size_t blen,
int flags, void *userp);
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
void *userp);
static int on_stream_close(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, 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 tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
int32_t stream_id,
const uint8_t *mem, size_t len, void *userp);
/*
* Initialize the cfilter context
*/
static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OUT_OF_MEMORY;
nghttp2_session_callbacks *cbs = NULL;
int rc;
DEBUGASSERT(!ctx->h2);
memset(&ctx->tunnel, 0, sizeof(ctx->tunnel));
Curl_bufq_init(&ctx->inbufq, H2_NW_CHUNK_SIZE, H2_NW_RECV_CHUNKS);
Curl_bufq_init(&ctx->outbufq, H2_NW_CHUNK_SIZE, H2_NW_SEND_CHUNKS);
if(tunnel_stream_init(cf, &ctx->tunnel))
goto out;
rc = nghttp2_session_callbacks_new(&cbs);
if(rc) {
failf(data, "Couldn't initialize nghttp2 callbacks");
goto out;
}
nghttp2_session_callbacks_set_send_callback(cbs, on_session_send);
nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
cbs, tunnel_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close);
nghttp2_session_callbacks_set_on_header_callback(cbs, on_header);
/* 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;
}
{
nghttp2_settings_entry iv[3];
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
iv[1].value = H2_TUNNEL_WINDOW_SIZE;
iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
iv[2].value = 0;
rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, iv, 3);
if(rc) {
failf(data, "nghttp2_submit_settings() failed: %s(%d)",
nghttp2_strerror(rc), rc);
result = CURLE_HTTP2;
goto out;
}
}
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;
}
/* all set, traffic will be send on connect */
result = CURLE_OK;
out:
if(cbs)
nghttp2_session_callbacks_del(cbs);
DEBUGF(LOG_CF(data, cf, "init proxy ctx -> %d", result));
return result;
}
static CURLcode nw_out_flush(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
size_t buflen = Curl_bufq_len(&ctx->outbufq);
ssize_t nwritten;
CURLcode result;
(void)data;
if(!buflen)
return CURLE_OK;
DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes", buflen));
nwritten = Curl_bufq_pass(&ctx->outbufq, nw_out_writer, cf, &result);
if(nwritten < 0) {
return result;
}
if((size_t)nwritten < buflen) {
return CURLE_AGAIN;
}
return CURLE_OK;
}
/*
* Processes pending input left in network input buffer.
* 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_cfilter *cf,
struct Curl_easy *data,
CURLcode *err)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
const unsigned char *buf;
size_t blen;
ssize_t rv;
while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) {
rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen);
DEBUGF(LOG_CF(data, cf,
"fed %zu bytes from nw to nghttp2 -> %zd", blen, rv));
if(rv < 0) {
failf(data,
"process_pending_input: nghttp2_session_mem_recv() returned "
"%zd:%s", rv, nghttp2_strerror((int)rv));
*err = CURLE_RECV_ERROR;
return -1;
}
Curl_bufq_skip(&ctx->inbufq, (size_t)rv);
if(Curl_bufq_is_empty(&ctx->inbufq)) {
DEBUGF(LOG_CF(data, cf, "all data in connection buffer processed"));
break;
}
else {
DEBUGF(LOG_CF(data, cf, "process_pending_input: %zu bytes left "
"in connection buffer", Curl_bufq_len(&ctx->inbufq)));
}
}
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(cf->conn, "http/2: No new requests allowed");
}
return 0;
}
static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
ssize_t nread;
/* Process network input buffer fist */
if(!Curl_bufq_is_empty(&ctx->inbufq)) {
DEBUGF(LOG_CF(data, cf, "Process %zd bytes in connection buffer",
Curl_bufq_len(&ctx->inbufq)));
if(h2_process_pending_input(cf, data, &result) < 0)
return result;
}
/* Receive data from the "lower" filters, e.g. network until
* it is time to stop or we have enough data for this stream */
while(!ctx->conn_closed && /* not closed the connection */
!ctx->tunnel.closed && /* nor the tunnel */
Curl_bufq_is_empty(&ctx->inbufq) && /* and we consumed our input */
!Curl_bufq_is_full(&ctx->tunnel.recvbuf)) {
nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result);
DEBUGF(LOG_CF(data, cf, "read %zd bytes nw data -> %zd, %d",
Curl_bufq_len(&ctx->inbufq), nread, result));
if(nread < 0) {
if(result != CURLE_AGAIN) {
failf(data, "Failed receiving HTTP2 data");
return result;
}
break;
}
else if(nread == 0) {
ctx->conn_closed = TRUE;
break;
}
if(h2_process_pending_input(cf, data, &result))
return result;
}
if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
connclose(cf->conn, "GOAWAY received");
}
return CURLE_OK;
}
/*
* Check if there's been an update in the priority /
* dependency settings and if so it submits a PRIORITY frame with the updated
* info.
* Flush any out data pending in the network buffer.
*/
static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
int rv = 0;
rv = nghttp2_session_send(ctx->h2);
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 nw_out_flush(cf, data);
}
static ssize_t on_session_send(nghttp2_session *h2,
const uint8_t *buf, size_t blen, int flags,
void *userp)
{
struct Curl_cfilter *cf = userp;
struct cf_h2_proxy_ctx *ctx = cf->ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
ssize_t nwritten;
CURLcode result = CURLE_OK;
(void)h2;
(void)flags;
DEBUGASSERT(data);
nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen,
nw_out_writer, cf, &result);
if(nwritten < 0) {
if(result == CURLE_AGAIN) {
return NGHTTP2_ERR_WOULDBLOCK;
}
failf(data, "Failed sending HTTP2 data");
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
if(!nwritten)
return NGHTTP2_ERR_WOULDBLOCK;
return nwritten;
}
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
void *userp)
{
struct Curl_cfilter *cf = userp;
struct cf_h2_proxy_ctx *ctx = cf->ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
int32_t stream_id = frame->hd.stream_id;
(void)session;
DEBUGASSERT(data);
if(!stream_id) {
/* stream ID zero is for connection-oriented stuff */
DEBUGASSERT(data);
switch(frame->hd.type) {
case NGHTTP2_SETTINGS:
/* we do not do anything with this for now */
break;
case NGHTTP2_GOAWAY:
infof(data, "recveived GOAWAY, error=%d, last_stream=%u",
frame->goaway.error_code, frame->goaway.last_stream_id);
ctx->goaway = TRUE;
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;
}
if(stream_id != ctx->tunnel.stream_id) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] rcvd FRAME not for tunnel",
stream_id));
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
switch(frame->hd.type) {
case NGHTTP2_DATA:
/* If body started on this stream, then receiving DATA is illegal. */
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv frame DATA", stream_id));
break;
case NGHTTP2_HEADERS:
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv frame HEADERS", stream_id));
/* nghttp2 guarantees that :status is received, and we store it to
stream->status_code. Fuzzing has proven this can still be reached
without status code having been set. */
if(!ctx->tunnel.resp)
return NGHTTP2_ERR_CALLBACK_FAILURE;
/* Only final status code signals the end of header */
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] got http status: %d",
stream_id, ctx->tunnel.resp->status));
if(!ctx->tunnel.has_final_response) {
if(ctx->tunnel.resp->status / 100 != 1) {
ctx->tunnel.has_final_response = TRUE;
}
}
break;
case NGHTTP2_PUSH_PROMISE:
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv PUSH_PROMISE", stream_id));
return NGHTTP2_ERR_CALLBACK_FAILURE;
case NGHTTP2_RST_STREAM:
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv RST", stream_id));
ctx->tunnel.reset = TRUE;
break;
case NGHTTP2_WINDOW_UPDATE:
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv WINDOW_UPDATE", stream_id));
if((data->req.keepon & KEEP_SEND_HOLD) &&
(data->req.keepon & KEEP_SEND)) {
data->req.keepon &= ~KEEP_SEND_HOLD;
Curl_expire(data, 0, EXPIRE_RUN_NOW);
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] unpausing after win update",
stream_id));
}
break;
default:
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv frame %x",
stream_id, frame->hd.type));
break;
}
return 0;
}
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)
{
struct Curl_cfilter *cf = userp;
struct cf_h2_proxy_ctx *ctx = cf->ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
int32_t stream_id = frame->hd.stream_id;
CURLcode result;
(void)flags;
(void)data;
(void)session;
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
if(stream_id != ctx->tunnel.stream_id) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] header for non-tunnel stream: "
"%.*s: %.*s", stream_id,
(int)namelen, name,
(int)valuelen, value));
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
if(frame->hd.type == NGHTTP2_PUSH_PROMISE)
return NGHTTP2_ERR_CALLBACK_FAILURE;
if(ctx->tunnel.has_final_response) {
/* we do not do anything with trailers for tunnel streams */
return 0;
}
if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 &&
memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) {
int http_status;
struct http_resp *resp;
/* status: always comes first, we might get more than one response,
* link the previous ones for keepers */
result = Curl_http_decode_status(&http_status,
(const char *)value, valuelen);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
result = Curl_http_resp_make(&resp, http_status, NULL);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
resp->prev = ctx->tunnel.resp;
ctx->tunnel.resp = resp;
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] status: HTTP/2 %03d",
stream_id, ctx->tunnel.resp->status));
return 0;
}
if(!ctx->tunnel.resp)
return NGHTTP2_ERR_CALLBACK_FAILURE;
result = Curl_dynhds_add(&ctx->tunnel.resp->headers,
(const char *)name, namelen,
(const char *)value, valuelen);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] header: %.*s: %.*s",
stream_id,
(int)namelen, name,
(int)valuelen, value));
return 0; /* 0 is successful */
}
static ssize_t tunnel_send_callback(nghttp2_session *session,
int32_t stream_id,
uint8_t *buf, size_t length,
uint32_t *data_flags,
nghttp2_data_source *source,
void *userp)
{
struct Curl_cfilter *cf = userp;
struct cf_h2_proxy_ctx *ctx = cf->ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
struct tunnel_stream *ts;
CURLcode result;
ssize_t nread;
(void)source;
(void)data;
(void)ctx;
if(!stream_id)
return NGHTTP2_ERR_INVALID_ARGUMENT;
ts = nghttp2_session_get_stream_user_data(session, stream_id);
if(!ts)
return NGHTTP2_ERR_CALLBACK_FAILURE;
DEBUGASSERT(ts == &ctx->tunnel);
nread = Curl_bufq_read(&ts->sendbuf, buf, length, &result);
if(nread < 0) {
if(result != CURLE_AGAIN)
return NGHTTP2_ERR_CALLBACK_FAILURE;
return NGHTTP2_ERR_DEFERRED;
}
if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf))
*data_flags = NGHTTP2_DATA_FLAG_EOF;
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] tunnel_send_callback -> %zd",
ts->stream_id, nread));
return nread;
}
static int tunnel_recv_callback(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_proxy_ctx *ctx = cf->ctx;
ssize_t nwritten;
CURLcode result;
(void)flags;
(void)session;
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
if(stream_id != ctx->tunnel.stream_id)
return NGHTTP2_ERR_CALLBACK_FAILURE;
nwritten = Curl_bufq_write(&ctx->tunnel.recvbuf, mem, len, &result);
if(nwritten < 0) {
if(result != CURLE_AGAIN)
return NGHTTP2_ERR_CALLBACK_FAILURE;
nwritten = 0;
}
DEBUGASSERT((size_t)nwritten == len);
return 0;
}
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_proxy_ctx *ctx = cf->ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
(void)session;
(void)data;
if(stream_id != ctx->tunnel.stream_id)
return 0;
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] on_stream_close, %s (err %d)",
stream_id, nghttp2_http2_strerror(error_code), error_code));
ctx->tunnel.closed = TRUE;
ctx->tunnel.error = error_code;
return 0;
}
static CURLcode h2_submit(int32_t *pstream_id,
struct Curl_cfilter *cf,
struct Curl_easy *data,
nghttp2_session *h2,
struct httpreq *req,
const nghttp2_priority_spec *pri_spec,
void *stream_user_data,
nghttp2_data_source_read_callback read_callback,
void *read_ctx)
{
struct dynhds h2_headers;
nghttp2_nv *nva = NULL;
unsigned int i;
int32_t stream_id = -1;
size_t nheader;
CURLcode result;
(void)cf;
Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
result = Curl_http_req_to_h2(&h2_headers, req, data);
if(result)
goto out;
nheader = Curl_dynhds_count(&h2_headers);
nva = malloc(sizeof(nghttp2_nv) * nheader);
if(!nva) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
for(i = 0; i < nheader; ++i) {
struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
nva[i].name = (unsigned char *)e->name;
nva[i].namelen = e->namelen;
nva[i].value = (unsigned char *)e->value;
nva[i].valuelen = e->valuelen;
nva[i].flags = NGHTTP2_NV_FLAG_NONE;
}
if(read_callback) {
nghttp2_data_provider data_prd;
data_prd.read_callback = read_callback;
data_prd.source.ptr = read_ctx;
stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
&data_prd, stream_user_data);
}
else {
stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
NULL, stream_user_data);
}
if(stream_id < 0) {
failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
nghttp2_strerror(stream_id), stream_id);
result = CURLE_SEND_ERROR;
goto out;
}
result = CURLE_OK;
out:
free(nva);
Curl_dynhds_free(&h2_headers);
*pstream_id = stream_id;
return result;
}
static CURLcode submit_CONNECT(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct tunnel_stream *ts)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
CURLcode result;
struct httpreq *req = NULL;
infof(data, "Establish HTTP/2 proxy tunnel to %s", ts->authority);
result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1,
NULL, 0, ts->authority, strlen(ts->authority),
NULL, 0);
if(result)
goto out;
/* Setup the proxy-authorization header, if any */
result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
req->authority, TRUE);
if(result)
goto out;
if(data->state.aptr.proxyuserpwd) {
result = Curl_dynhds_h1_cadd_line(&req->headers,
data->state.aptr.proxyuserpwd);
if(result)
goto out;
}
if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent"))
&& data->set.str[STRING_USERAGENT]) {
result = Curl_dynhds_cadd(&req->headers, "User-Agent",
data->set.str[STRING_USERAGENT]);
if(result)
goto out;
}
result = Curl_dynhds_add_custom(data, TRUE, &req->headers);
if(result)
goto out;
result = h2_submit(&ts->stream_id, cf, data, ctx->h2, req,
NULL, ts, tunnel_send_callback, cf);
if(result) {
DEBUGF(LOG_CF(data, cf, "send: nghttp2_submit_request error (%s)%u",
nghttp2_strerror(ts->stream_id), ts->stream_id));
}
out:
if(req)
Curl_http_req_free(req);
if(result)
failf(data, "Failed sending CONNECT to proxy");
return result;
}
static CURLcode inspect_response(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct tunnel_stream *ts)
{
CURLcode result = CURLE_OK;
struct dynhds_entry *auth_reply = NULL;
(void)cf;
DEBUGASSERT(ts->resp);
if(ts->resp->status/100 == 2) {
infof(data, "CONNECT tunnel established, response %d", ts->resp->status);
tunnel_go_state(cf, ts, TUNNEL_ESTABLISHED, data);
return CURLE_OK;
}
if(ts->resp->status == 401) {
auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate");
}
else if(ts->resp->status == 407) {
auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate");
}
if(auth_reply) {
DEBUGF(LOG_CF(data, cf, "CONNECT: fwd auth header '%s'",
auth_reply->value));
result = Curl_http_input_auth(data, ts->resp->status == 407,
auth_reply->value);
if(result)
return result;
if(data->req.newurl) {
/* Inidicator that we should try again */
Curl_safefree(data->req.newurl);
tunnel_go_state(cf, ts, TUNNEL_INIT, data);
return CURLE_OK;
}
}
/* Seems to have failed */
return CURLE_RECV_ERROR;
}
static CURLcode CONNECT(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct tunnel_stream *ts)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
DEBUGASSERT(ts);
DEBUGASSERT(ts->authority);
do {
switch(ts->state) {
case TUNNEL_INIT:
/* Prepare the CONNECT request and make a first attempt to send. */
DEBUGF(LOG_CF(data, cf, "CONNECT start for %s", ts->authority));
result = submit_CONNECT(cf, data, ts);
if(result)
goto out;
tunnel_go_state(cf, ts, TUNNEL_CONNECT, data);
/* FALLTHROUGH */
case TUNNEL_CONNECT:
/* see that the request is completely sent */
result = h2_progress_ingress(cf, data);
if(!result)
result = h2_progress_egress(cf, data);
if(result) {
tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
break;
}
if(ts->has_final_response) {
tunnel_go_state(cf, ts, TUNNEL_RESPONSE, data);
}
else {
result = CURLE_OK;
goto out;
}
/* FALLTHROUGH */
case TUNNEL_RESPONSE:
DEBUGASSERT(ts->has_final_response);
result = inspect_response(cf, data, ts);
if(result)
goto out;
break;
case TUNNEL_ESTABLISHED:
return CURLE_OK;
case TUNNEL_FAILED:
return CURLE_RECV_ERROR;
default:
break;
}
} while(ts->state == TUNNEL_INIT);
out:
if(result || ctx->tunnel.closed)
tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
return result;
}
static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
struct cf_call_data save;
timediff_t check;
struct tunnel_stream *ts = &ctx->tunnel;
if(cf->connected) {
*done = TRUE;
return CURLE_OK;
}
/* Connect the lower filters first */
if(!cf->next->connected) {
result = Curl_conn_cf_connect(cf->next, data, blocking, done);
if(result || !*done)
return result;
}
*done = FALSE;
CF_DATA_SAVE(save, cf, data);
if(!ctx->h2) {
result = cf_h2_proxy_ctx_init(cf, data);
if(result)
goto out;
}
DEBUGASSERT(ts->authority);
check = Curl_timeleft(data, NULL, TRUE);
if(check <= 0) {
failf(data, "Proxy CONNECT aborted due to timeout");
result = CURLE_OPERATION_TIMEDOUT;
goto out;
}
/* for the secondary socket (FTP), use the "connect to host"
* but ignore the "connect to port" (use the secondary port)
*/
result = CONNECT(cf, data, ts);
out:
*done = (result == CURLE_OK) && (ts->state == TUNNEL_ESTABLISHED);
cf->connected = *done;
CF_DATA_RESTORE(cf, save);
return result;
}
static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
if(ctx) {
struct cf_call_data save;
CF_DATA_SAVE(save, cf, data);
cf_h2_proxy_ctx_clear(ctx);
CF_DATA_RESTORE(cf, save);
}
}
static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
(void)data;
if(ctx) {
cf_h2_proxy_ctx_free(ctx);
cf->ctx = NULL;
}
}
static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) ||
(ctx && ctx->tunnel.state == TUNNEL_ESTABLISHED &&
!Curl_bufq_is_empty(&ctx->tunnel.recvbuf)))
return TRUE;
return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
}
static int cf_h2_proxy_get_select_socks(struct Curl_cfilter *cf,
struct Curl_easy *data,
curl_socket_t *sock)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
int bitmap = GETSOCK_BLANK;
struct cf_call_data save;
CF_DATA_SAVE(save, cf, data);
sock[0] = Curl_conn_cf_get_socket(cf, data);
bitmap |= GETSOCK_READSOCK(0);
/* HTTP/2 layer wants to send data) AND there's a window to send data in */
if(nghttp2_session_want_write(ctx->h2) &&
nghttp2_session_get_remote_window_size(ctx->h2))
bitmap |= GETSOCK_WRITESOCK(0);
CF_DATA_RESTORE(cf, save);
return bitmap;
}
static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf,
struct Curl_easy *data,
CURLcode *err)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
ssize_t rv = 0;
if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] REFUSED_STREAM, try again on a new "
"connection", ctx->tunnel.stream_id));
connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */
*err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
return -1;
}
else if(ctx->tunnel.error != NGHTTP2_NO_ERROR) {
failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
ctx->tunnel.stream_id, nghttp2_http2_strerror(ctx->tunnel.error),
ctx->tunnel.error);
*err = CURLE_HTTP2_STREAM;
return -1;
}
else if(ctx->tunnel.reset) {
failf(data, "HTTP/2 stream %u was reset", ctx->tunnel.stream_id);
*err = CURLE_RECV_ERROR;
return -1;
}
*err = CURLE_OK;
rv = 0;
DEBUGF(LOG_CF(data, cf, "handle_tunnel_close -> %zd, %d", rv, *err));
return rv;
}
static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
ssize_t nread = -1;
*err = CURLE_AGAIN;
if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
nread = Curl_bufq_read(&ctx->tunnel.recvbuf,
(unsigned char *)buf, len, err);
if(nread < 0)
goto out;
DEBUGASSERT(nread > 0);
}
if(nread < 0) {
if(ctx->tunnel.closed) {
nread = h2_handle_tunnel_close(cf, data, err);
}
else if(ctx->tunnel.reset ||
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
(ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) {
*err = CURLE_RECV_ERROR;
nread = -1;
}
}
else if(nread == 0) {
*err = CURLE_AGAIN;
nread = -1;
}
out:
DEBUGF(LOG_CF(data, cf, "tunnel_recv(len=%zu) -> %zd, %d",
len, nread, *err));
return nread;
}
static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf,
struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
ssize_t nread = -1;
struct cf_call_data save;
CURLcode result;
if(ctx->tunnel.state != TUNNEL_ESTABLISHED) {
*err = CURLE_RECV_ERROR;
return -1;
}
CF_DATA_SAVE(save, cf, data);
if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
*err = h2_progress_ingress(cf, data);
if(*err)
goto out;
}
nread = tunnel_recv(cf, data, buf, len, err);
if(nread > 0) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] increase window by %zd",
ctx->tunnel.stream_id, nread));
nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, (size_t)nread);
}
result = h2_progress_egress(cf, data);
if(result) {
*err = result;
nread = -1;
}
out:
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv(len=%zu) -> %zd %d",
ctx->tunnel.stream_id, len, nread, *err));
CF_DATA_RESTORE(cf, save);
return nread;
}
static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf,
struct Curl_easy *data,
const void *mem, size_t len, CURLcode *err)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
struct cf_call_data save;
ssize_t nwritten = -1;
const unsigned char *buf = mem;
size_t start_len = len;
int rv;
if(ctx->tunnel.state != TUNNEL_ESTABLISHED) {
*err = CURLE_SEND_ERROR;
return -1;
}
CF_DATA_SAVE(save, cf, data);
while(len) {
nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err);
if(nwritten <= 0) {
if(*err && *err != CURLE_AGAIN) {
DEBUGF(LOG_CF(data, cf, "error adding data to tunnel sendbuf: %d",
*err));
nwritten = -1;
goto out;
}
/* blocked */
nwritten = 0;
}
else {
DEBUGASSERT((size_t)nwritten <= len);
buf += (size_t)nwritten;
len -= (size_t)nwritten;
}
/* resume the tunnel stream and let the h2 session send, which
* triggers reading from tunnel.sendbuf */
rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
if(nghttp2_is_fatal(rv)) {
*err = CURLE_SEND_ERROR;
nwritten = -1;
goto out;
}
*err = h2_progress_egress(cf, data);
if(*err) {
nwritten = -1;
goto out;
}
if(!nwritten && Curl_bufq_is_full(&ctx->tunnel.sendbuf)) {
size_t rwin;
/* we could not add to the buffer and after session processing,
* it is still full. */
rwin = nghttp2_session_get_stream_remote_window_size(
ctx->h2, ctx->tunnel.stream_id);
DEBUGF(LOG_CF(data, cf, "cf_send: tunnel win %u/%zu",
nghttp2_session_get_remote_window_size(ctx->h2), rwin));
if(rwin == 0) {
/* We cannot upload more as the stream's remote window size
* is 0. We need to receive WIN_UPDATEs before we can continue.
*/
data->req.keepon |= KEEP_SEND_HOLD;
DEBUGF(LOG_CF(data, cf, "pausing send as remote flow "
"window is exhausted"));
}
break;
}
}
nwritten = start_len - len;
if(nwritten > 0) {
*err = CURLE_OK;
}
else if(ctx->tunnel.closed) {
nwritten = -1;
*err = CURLE_SEND_ERROR;
}
else {
nwritten = -1;
*err = CURLE_AGAIN;
}
out:
DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) -> %zd, %d ",
start_len, nwritten, *err));
CF_DATA_RESTORE(cf, save);
return nwritten;
}
struct Curl_cftype Curl_cft_h2_proxy = {
"H2-PROXY",
CF_TYPE_IP_CONNECT,
CURL_LOG_DEFAULT,
cf_h2_proxy_destroy,
cf_h2_proxy_connect,
cf_h2_proxy_close,
Curl_cf_http_proxy_get_host,
cf_h2_proxy_get_select_socks,
cf_h2_proxy_data_pending,
cf_h2_proxy_send,
cf_h2_proxy_recv,
Curl_cf_def_cntrl,
Curl_cf_def_conn_is_alive,
Curl_cf_def_conn_keep_alive,
Curl_cf_def_query,
};
CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct Curl_cfilter *cf_h2_proxy = NULL;
struct cf_h2_proxy_ctx *ctx;
CURLcode result = CURLE_OUT_OF_MEMORY;
(void)data;
ctx = calloc(sizeof(*ctx), 1);
if(!ctx)
goto out;
result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx);
if(result)
goto out;
Curl_conn_cf_insert_after(cf, cf_h2_proxy);
result = CURLE_OK;
out:
if(result)
cf_h2_proxy_ctx_free(ctx);
return result;
}
#endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */
+39
View File
@@ -0,0 +1,39 @@
#ifndef HEADER_CURL_H2_PROXY_H
#define HEADER_CURL_H2_PROXY_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "curl_setup.h"
#if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY)
CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
struct Curl_easy *data);
extern struct Curl_cftype Curl_cft_h2_proxy;
#endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */
#endif /* HEADER_CURL_H2_PROXY_H */
+246
View File
@@ -0,0 +1,246 @@
/***************************************************************************
* _ _ ____ _
* 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_PROXY)
#include <curl/curl.h>
#include "urldata.h"
#include "cfilters.h"
#include "cf-haproxy.h"
#include "curl_log.h"
#include "multiif.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
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)
{
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;
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(&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(&ctx->data_out, "PROXY %s %s %s %i %i\r\n",
tcp_version,
data->info.conn_local_ip,
data->info.conn_primary_ip,
data->info.conn_local_port,
data->info.conn_primary_port);
#ifdef USE_UNIX_SOCKETS
}
#endif /* USE_UNIX_SOCKETS */
return result;
}
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;
}
result = cf->next->cft->connect(cf->next, data, blocking, done);
if(result || !*done)
return 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 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,
0,
cf_haproxy_destroy,
cf_haproxy_connect,
cf_haproxy_close,
Curl_cf_def_get_host,
cf_haproxy_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,
};
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_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 */
+39
View File
@@ -0,0 +1,39 @@
#ifndef HEADER_CURL_CF_HAPROXY_H
#define HEADER_CURL_CF_HAPROXY_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 "urldata.h"
#if !defined(CURL_DISABLE_PROXY)
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_CF_HAPROXY_H */
+7 -25
View File
@@ -496,11 +496,11 @@ out:
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)
static CURLcode 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;
@@ -514,24 +514,6 @@ 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,
@@ -560,8 +542,8 @@ CURLcode Curl_cf_https_setup(struct Curl_easy *data,
try_h21 = TRUE;
}
result = Curl_cf_http_connect_add(data, conn, sockindex, remotehost,
try_h3, try_h21);
result = cf_http_connect_add(data, conn, sockindex, remotehost,
try_h3, try_h21);
out:
return result;
}
+165 -170
View File
@@ -54,6 +54,7 @@
#endif
#include "urldata.h"
#include "bufq.h"
#include "sendf.h"
#include "if2ip.h"
#include "strerror.h"
@@ -79,6 +80,22 @@
#include "memdebug.h"
#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY) && defined(WIN32)
/* It makes support for IPv4-mapped IPv6 addresses.
* Linux kernel, NetBSD, FreeBSD and Darwin: default is off;
* Windows Vista and later: default is on;
* DragonFly BSD: acts like off, and dummy setting;
* OpenBSD and earlier Windows: unsupported.
* Linux: controlled by /proc/sys/net/ipv6/bindv6only.
*/
static void set_ipv6_v6only(curl_socket_t sockfd, int on)
{
(void)setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on));
}
#else
#define set_ipv6_v6only(x,y)
#endif
static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd)
{
#if defined(TCP_NODELAY)
@@ -195,6 +212,10 @@ tcpkeepalive(struct Curl_easy *data,
}
}
/**
* 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)
@@ -224,7 +245,7 @@ void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest,
dest->addrlen = ai->ai_addrlen;
if(dest->addrlen > sizeof(struct Curl_sockaddr_storage))
dest->addrlen = sizeof(struct Curl_sockaddr_storage);
dest->addrlen = sizeof(struct Curl_sockaddr_storage);
memcpy(&dest->sa_addr, ai->ai_addr, dest->addrlen);
}
@@ -700,8 +721,11 @@ static bool verifyconnect(curl_socket_t sockfd, int *error)
return rc;
}
CURLcode Curl_socket_connect_result(struct Curl_easy *data,
const char *ipaddress, int error)
/**
* Determine the curl code for a socket connect() == -1 with errno.
*/
static CURLcode socket_connect_result(struct Curl_easy *data,
const char *ipaddress, int error)
{
char buffer[STRERROR_LEN];
@@ -729,29 +753,20 @@ CURLcode Curl_socket_connect_result(struct Curl_easy *data,
}
}
#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 */
/* We have a recv buffer to enhance reads with len < NW_SMALL_READS.
* This happens often on TLS connections where the TLS implementation
* tries to read the head of a TLS record, determine the length of the
* full record and then make a subsequent read for that.
* On large reads, we will not fill the buffer to avoid the double copy. */
#define NW_RECV_CHUNK_SIZE (64 * 1024)
#define NW_RECV_CHUNKS 1
#define NW_SMALL_READS (1024)
struct cf_socket_ctx {
int transport;
struct Curl_sockaddr_ex addr; /* address to connect to */
curl_socket_t sock; /* current attempt socket */
#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
struct io_buffer recv_buffer;
#endif
struct bufq recvbuf; /* used when `buffer_recv` is set */
char r_ip[MAX_IPADR_LEN]; /* remote IP as string */
int r_port; /* remote port number */
char l_ip[MAX_IPADR_LEN]; /* local IP as string */
@@ -763,6 +778,7 @@ struct cf_socket_ctx {
BIT(got_first_byte); /* if first byte was received */
BIT(accepted); /* socket was accepted, not connected */
BIT(active);
BIT(buffer_recv);
};
static void cf_socket_ctx_init(struct cf_socket_ctx *ctx,
@@ -773,6 +789,56 @@ static void cf_socket_ctx_init(struct cf_socket_ctx *ctx,
ctx->sock = CURL_SOCKET_BAD;
ctx->transport = transport;
Curl_sock_assign_addr(&ctx->addr, ai, transport);
Curl_bufq_init(&ctx->recvbuf, NW_RECV_CHUNK_SIZE, NW_RECV_CHUNKS);
}
struct reader_ctx {
struct Curl_cfilter *cf;
struct Curl_easy *data;
};
static ssize_t nw_in_read(void *reader_ctx,
unsigned char *buf, size_t len,
CURLcode *err)
{
struct reader_ctx *rctx = reader_ctx;
struct cf_socket_ctx *ctx = rctx->cf->ctx;
ssize_t nread;
*err = CURLE_OK;
nread = sread(ctx->sock, buf, len);
if(-1 == nread) {
int sockerr = SOCKERRNO;
if(
#ifdef WSAEWOULDBLOCK
/* This is how Windows does it */
(WSAEWOULDBLOCK == sockerr)
#else
/* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned
due to its inability to send off data without blocking. We therefore
treat both error codes the same here */
(EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr)
#endif
) {
/* this is just a case of EWOULDBLOCK */
*err = CURLE_AGAIN;
nread = -1;
}
else {
char buffer[STRERROR_LEN];
failf(rctx->data, "Recv failure: %s",
Curl_strerror(sockerr, buffer, sizeof(buffer)));
rctx->data->state.os_errno = sockerr;
*err = CURLE_RECV_ERROR;
nread = -1;
}
}
DEBUGF(LOG_CF(rctx->data, rctx->cf, "nw_in_read(len=%zu) -> %d, err=%d",
len, (int)nread, *err));
return nread;
}
static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
@@ -786,14 +852,14 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
* 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));
DEBUGF(LOG_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T
", active)", ctx->sock));
socket_close(data, cf->conn, !ctx->accepted, ctx->sock);
cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
}
else {
DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d) no longer at "
"conn->sock[], discarding", (int)ctx->sock));
DEBUGF(LOG_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T
") no longer at conn->sock[], discarding", ctx->sock));
/* TODO: we do not want this to happen. Need to check which
* code is messing with conn->sock[cf->sockindex] */
}
@@ -803,15 +869,14 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
}
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));
DEBUGF(LOG_CF(data, cf, "cf_socket_close(%" CURL_FORMAT_SOCKET_T
", not active)", ctx->sock));
sclose(ctx->sock);
ctx->sock = CURL_SOCKET_BAD;
}
#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
io_buffer_reset(&ctx->recv_buffer);
#endif
Curl_bufq_reset(&ctx->recvbuf);
ctx->active = FALSE;
ctx->buffer_recv = FALSE;
memset(&ctx->started_at, 0, sizeof(ctx->started_at));
memset(&ctx->connected_at, 0, sizeof(ctx->connected_at));
}
@@ -825,6 +890,7 @@ static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
cf_socket_close(cf, data);
DEBUGF(LOG_CF(data, cf, "destroy"));
Curl_bufq_free(&ctx->recvbuf);
free(ctx);
cf->ctx = NULL;
}
@@ -901,8 +967,10 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf,
goto out;
#ifdef ENABLE_IPV6
if(ctx->addr.family == AF_INET6)
if(ctx->addr.family == AF_INET6) {
set_ipv6_v6only(ctx->sock, 0);
ipmsg = " Trying [%s]:%d...";
}
else
#endif
ipmsg = " Trying %s:%d...";
@@ -975,7 +1043,8 @@ out:
ctx->connected_at = Curl_now();
cf->connected = TRUE;
}
DEBUGF(LOG_CF(data, cf, "cf_socket_open() -> %d, fd=%d", result, ctx->sock));
DEBUGF(LOG_CF(data, cf, "cf_socket_open() -> %d, fd=%" CURL_FORMAT_SOCKET_T,
result, ctx->sock));
return result;
}
@@ -1016,7 +1085,8 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data,
#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);
infof(data, "Failed to enable TCP Fast Open on fd %"
CURL_FORMAT_SOCKET_T, ctx->sock);
rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
#elif defined(MSG_FASTOPEN) /* old Linux */
@@ -1065,7 +1135,7 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf,
/* 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);
result = socket_connect_result(data, ctx->r_ip, SOCKERRNO);
goto out;
}
}
@@ -1151,89 +1221,16 @@ static int cf_socket_get_select_socks(struct Curl_cfilter *cf,
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;
if(!Curl_bufq_is_empty(&ctx->recvbuf))
return TRUE;
readable = SOCKET_READABLE(ctx->sock, 0);
return (readable > 0 && (readable & CURL_CSELECT_IN));
}
@@ -1246,19 +1243,6 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data,
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;
@@ -1315,47 +1299,50 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
*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(ctx->buffer_recv && !Curl_bufq_is_empty(&ctx->recvbuf)) {
DEBUGF(LOG_CF(data, cf, "recv from buffer"));
nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err);
}
else {
struct reader_ctx rctx;
if(-1 == nread) {
int sockerr = SOCKERRNO;
rctx.cf = cf;
rctx.data = data;
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;
/* "small" reads may trigger filling our buffer, "large" reads
* are probably not worth the additional copy */
if(ctx->buffer_recv && len < NW_SMALL_READS) {
ssize_t nwritten;
nwritten = Curl_bufq_slurp(&ctx->recvbuf, nw_in_read, &rctx, err);
if(nwritten < 0 && !Curl_bufq_is_empty(&ctx->recvbuf)) {
/* we have a partial read with an error. need to deliver
* what we got, return the error later. */
DEBUGF(LOG_CF(data, cf, "partial read: empty buffer first"));
nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err);
}
else if(nwritten < 0) {
nread = -1;
goto out;
}
else if(nwritten == 0) {
/* eof */
*err = CURLE_OK;
nread = 0;
}
else {
DEBUGF(LOG_CF(data, cf, "buffered %zd additional bytes", nwritten));
nread = Curl_bufq_read(&ctx->recvbuf, (unsigned char *)buf, len, err);
}
}
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;
nread = nw_in_read(&rctx, (unsigned char *)buf, len, err);
}
}
out:
DEBUGF(LOG_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread,
*err));
if(nread > 0 && !ctx->got_first_byte) {
@@ -1411,6 +1398,11 @@ static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data)
conn_set_primary_ip(cf, data);
set_local_ip(cf, data);
Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port);
/* buffering is currently disabled by default because we have stalls
* in parallel transfers where not all buffered data is consumed and no
* socket events happen.
*/
ctx->buffer_recv = FALSE;
}
ctx->active = TRUE;
}
@@ -1577,12 +1569,13 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf,
rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
if(-1 == rc) {
return Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO);
return 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));
DEBUGF(LOG_CF(data, cf, "%s socket %" CURL_FORMAT_SOCKET_T
" connected: [%s:%d] -> [%s:%d]",
(ctx->transport == TRNSPRT_QUIC)? "QUIC" : "UDP",
ctx->sock, ctx->l_ip, ctx->l_port, ctx->r_ip, ctx->r_port));
(void)curlx_nonblock(ctx->sock, TRUE);
switch(ctx->addr.family) {
@@ -1623,10 +1616,6 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf,
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;
}
@@ -1634,12 +1623,13 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf,
result = cf_udp_setup_quic(cf, data);
if(result)
goto out;
DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d (%s:%d)",
DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%"
CURL_FORMAT_SOCKET_T " (%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));
DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%"
CURL_FORMAT_SOCKET_T " (unconnected)", ctx->sock));
}
*done = TRUE;
cf->connected = TRUE;
@@ -1811,7 +1801,8 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *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));
DEBUGF(LOG_CF(data, cf, "Curl_conn_tcp_listen_set(%"
CURL_FORMAT_SOCKET_T ")", ctx->sock));
out:
if(result) {
@@ -1875,13 +1866,17 @@ CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data,
ctx->accepted = TRUE;
ctx->connected_at = Curl_now();
cf->connected = TRUE;
DEBUGF(LOG_CF(data, cf, "accepted_set(sock=%d, remote=%s port=%d)",
(int)ctx->sock, ctx->r_ip, ctx->r_port));
DEBUGF(LOG_CF(data, cf, "accepted_set(sock=%" CURL_FORMAT_SOCKET_T
", remote=%s port=%d)",
ctx->sock, ctx->r_ip, ctx->r_port));
return CURLE_OK;
}
bool Curl_cf_is_socket(struct Curl_cfilter *cf)
/**
* Return TRUE iff `cf` is a socket filter.
*/
static bool cf_is_socket(struct Curl_cfilter *cf)
{
return cf && (cf->cft == &Curl_cft_tcp ||
cf->cft == &Curl_cft_udp ||
@@ -1896,7 +1891,7 @@ CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf,
const char **pr_ip_str, int *pr_port,
const char **pl_ip_str, int *pl_port)
{
if(Curl_cf_is_socket(cf) && cf->ctx) {
if(cf_is_socket(cf) && cf->ctx) {
struct cf_socket_ctx *ctx = cf->ctx;
if(psock)
+17 -11
View File
@@ -34,6 +34,23 @@ struct Curl_easy;
struct connectdata;
struct Curl_sockaddr_ex;
#ifndef SIZEOF_CURL_SOCKET_T
/* configure and cmake check and set the define */
# ifdef _WIN64
# define SIZEOF_CURL_SOCKET_T 8
# else
/* default guess */
# define SIZEOF_CURL_SOCKET_T 4
# endif
#endif
#if SIZEOF_CURL_SOCKET_T < 8
# define CURL_FORMAT_SOCKET_T "d"
#else
# define CURL_FORMAT_SOCKET_T "qd"
#endif
/*
* The Curl_sockaddr_ex structure is basically libcurl's external API
* curl_sockaddr structure with enough space available to directly hold any
@@ -70,12 +87,6 @@ CURLcode Curl_socket_open(struct Curl_easy *data,
int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
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.
@@ -154,11 +165,6 @@ CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data,
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.
+36 -50
View File
@@ -44,40 +44,18 @@
#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
#endif
void Curl_cf_def_destroy_this(struct Curl_cfilter *cf, struct Curl_easy *data)
{
(void)cf;
(void)data;
}
#ifdef DEBUGBUILD
/* used by unit2600.c */
void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
cf->connected = FALSE;
if(cf->next)
cf->next->cft->close(cf->next, data);
}
#endif
CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *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;
}
static void conn_report_connect_stats(struct Curl_easy *data,
struct connectdata *conn);
void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data,
const char **phost, const char **pdisplay_host,
@@ -283,21 +261,31 @@ void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at,
*pnext = tail;
}
void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data)
bool Curl_conn_cf_discard_sub(struct Curl_cfilter *cf,
struct Curl_cfilter *discard,
struct Curl_easy *data,
bool destroy_always)
{
struct Curl_cfilter **pprev = &cf->conn->cfilter[cf->sockindex];
struct Curl_cfilter **pprev = &cf->next;
bool found = FALSE;
/* remove from chain if still in there */
/* remove from sub-chain and destroy */
DEBUGASSERT(cf);
while (*pprev) {
if (*pprev == cf) {
*pprev = cf->next;
while(*pprev) {
if(*pprev == cf) {
*pprev = discard->next;
discard->next = NULL;
found = TRUE;
break;
}
pprev = &((*pprev)->next);
}
cf->cft->destroy(cf, data);
free(cf);
if(found || destroy_always) {
discard->next = NULL;
discard->cft->destroy(discard, data);
free(discard);
}
return found;
}
CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf,
@@ -324,14 +312,6 @@ int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf,
return 0;
}
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;
}
ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, CURLcode *err)
{
@@ -371,11 +351,11 @@ CURLcode Curl_conn_connect(struct Curl_easy *data,
result = cf->cft->connect(cf, data, blocking, done);
if(!result && *done) {
Curl_conn_ev_update_info(data, data->conn);
Curl_conn_report_connect_stats(data, data->conn);
conn_report_connect_stats(data, data->conn);
data->conn->keepalive = Curl_now();
}
else if(result) {
Curl_conn_report_connect_stats(data, data->conn);
conn_report_connect_stats(data, data->conn);
}
}
@@ -405,10 +385,8 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex)
return FALSE;
}
bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex)
bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf)
{
struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
for(; cf; cf = cf->next) {
if(cf->cft->flags & CF_TYPE_SSL)
return TRUE;
@@ -418,6 +396,11 @@ bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex)
return FALSE;
}
bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex)
{
return conn? Curl_conn_cf_is_ssl(conn->cfilter[sockindex]) : FALSE;
}
bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex)
{
struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
@@ -612,8 +595,11 @@ void Curl_conn_ev_update_info(struct Curl_easy *data,
cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
}
void Curl_conn_report_connect_stats(struct Curl_easy *data,
struct connectdata *conn)
/**
* Update connection statistics
*/
static void conn_report_connect_stats(struct Curl_easy *data,
struct connectdata *conn)
{
struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET];
if(cf) {
+16 -16
View File
@@ -197,10 +197,6 @@ void Curl_cf_def_destroy_this(struct Curl_cfilter *cf,
/* Default implementations for the type functions, implementing pass-through
* the filter chain. */
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,
bool blocking, bool *done);
void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data,
const char **phost, const char **pdisplay_host,
int *pport);
@@ -254,11 +250,16 @@ 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.
* If the filter is attached to a connection, it will be removed before
* it is destroyed.
* Discard, e.g. remove and destroy `discard` iff
* it still is in the filter chain below `cf`. If `discard`
* is no longer found beneath `cf` return FALSE.
* if `destroy_always` is TRUE, will call `discard`s destroy
* function and free it even if not found in the subchain.
*/
void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data);
bool Curl_conn_cf_discard_sub(struct Curl_cfilter *cf,
struct Curl_cfilter *discard,
struct Curl_easy *data,
bool destroy_always);
/**
* Discard all cfilters starting with `*pcf` and clearing it afterwards.
@@ -281,8 +282,6 @@ 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,
@@ -292,6 +291,12 @@ CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf,
bool ignore_result,
int event, int arg1, void *arg2);
/**
* Determine if the connection filter chain is using SSL to the remote host
* (or will be once connected).
*/
bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf);
/**
* Get the socket used by the filter chain starting at `cf`.
* Returns CURL_SOCKET_BAD if not available.
@@ -436,12 +441,6 @@ CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause);
void Curl_conn_ev_update_info(struct Curl_easy *data,
struct connectdata *conn);
/**
* Update connection statistics
*/
void Curl_conn_report_connect_stats(struct Curl_easy *data,
struct connectdata *conn);
/**
* Check if FIRSTSOCKET's cfilter chain deems connection alive.
*/
@@ -455,6 +454,7 @@ CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data);
void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
const char **phost, const char **pdisplay_host,
int *pport);
+1 -1
View File
@@ -246,7 +246,7 @@ CURLcode Curl_conncache_add_conn(struct Curl_easy *data)
"The cache now contains %zu members",
conn->connection_id, connc->num_conn));
unlock:
unlock:
CONNCACHE_UNLOCK(data);
return result;
+54 -39
View File
@@ -59,6 +59,7 @@
#include "strerror.h"
#include "cfilters.h"
#include "connect.h"
#include "cf-haproxy.h"
#include "cf-https-connect.h"
#include "cf-socket.h"
#include "select.h"
@@ -547,7 +548,7 @@ static CURLcode baller_connect(struct Curl_cfilter *cf,
baller->result = Curl_conn_cf_connect(baller->cf, data, 0, connected);
if(!baller->result) {
if (*connected) {
if(*connected) {
baller->connected = TRUE;
baller->is_done = TRUE;
}
@@ -663,7 +664,8 @@ evaluate:
DEBUGF(LOG_CF(data, cf, "%s done", baller->name));
}
else {
DEBUGF(LOG_CF(data, cf, "%s starting (timeout=%ldms)",
DEBUGF(LOG_CF(data, cf, "%s starting (timeout=%"
CURL_FORMAT_TIMEDIFF_T "ms)",
baller->name, baller->timeoutms));
++ongoing;
++added;
@@ -801,7 +803,8 @@ static CURLcode start_connect(struct Curl_cfilter *cf,
timeout_ms, EXPIRE_DNS_PER_NAME);
if(result)
return result;
DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)",
DEBUGF(LOG_CF(data, cf, "created %s (timeout %"
CURL_FORMAT_TIMEDIFF_T "ms)",
ctx->baller[0]->name, ctx->baller[0]->timeoutms));
if(addr1) {
/* second one gets a delayed start */
@@ -812,7 +815,8 @@ static CURLcode start_connect(struct Curl_cfilter *cf,
timeout_ms, EXPIRE_DNS_PER_NAME2);
if(result)
return result;
DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)",
DEBUGF(LOG_CF(data, cf, "created %s (timeout %"
CURL_FORMAT_TIMEDIFF_T "ms)",
ctx->baller[1]->name, ctx->baller[1]->timeoutms));
}
@@ -1056,12 +1060,23 @@ struct Curl_cftype Curl_cft_happy_eyeballs = {
cf_he_query,
};
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)
/**
* 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.
*/
static CURLcode
cf_happy_eyeballs_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
cf_ip_connect_create *cf_create,
const struct Curl_dns_entry *remotehost,
int transport)
{
struct cf_he_ctx *ctx = NULL;
CURLcode result;
@@ -1120,20 +1135,6 @@ static cf_ip_connect_create *get_cf_create(int transport)
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 */
static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at,
struct Curl_easy *data,
const struct Curl_dns_entry *remotehost,
@@ -1150,9 +1151,9 @@ static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at,
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);
result = cf_happy_eyeballs_create(&cf, data, cf_at->conn,
cf_create, remotehost,
transport);
if(result)
return result;
@@ -1219,7 +1220,7 @@ connect_sub_chain:
if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) {
#ifdef USE_SSL
if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS
if(IS_HTTPS_PROXY(cf->conn->http_proxy.proxytype)
&& !Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
result = Curl_cf_ssl_proxy_insert_after(cf, data);
if(result)
@@ -1355,12 +1356,12 @@ out:
return result;
}
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)
static CURLcode cf_setup_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex,
const struct Curl_dns_entry *remotehost,
int transport,
int ssl_mode)
{
struct Curl_cfilter *cf;
CURLcode result = CURLE_OK;
@@ -1374,6 +1375,21 @@ out:
return result;
}
#ifdef DEBUGBUILD
/* used by unit2600.c */
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 */
CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at,
struct Curl_easy *data,
const struct Curl_dns_entry *remotehost,
@@ -1405,9 +1421,8 @@ CURLcode Curl_conn_setup(struct Curl_easy *data,
#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)) {
conn->handler->protocol == CURLPROTO_HTTPS) {
DEBUGASSERT(ssl_mode != CURL_CF_SSL_DISABLE);
result = Curl_cf_https_setup(data, conn, sockindex, remotehost);
if(result)
goto out;
@@ -1416,8 +1431,8 @@ CURLcode Curl_conn_setup(struct Curl_easy *data,
/* Still no cfilter set, apply default. */
if(!conn->cfilter[sockindex]) {
result = Curl_cf_setup_add(data, conn, sockindex, remotehost,
conn->transport, ssl_mode);
result = cf_setup_add(data, conn, sockindex, remotehost,
conn->transport, ssl_mode);
if(result)
goto out;
}
-25
View File
@@ -104,31 +104,6 @@ typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf,
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,
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,
+8 -1
View File
@@ -53,6 +53,9 @@
#include "content_encoding.h"
#include "strdup.h"
#include "strcase.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
@@ -1077,8 +1080,12 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
Curl_httpchunk_init(data); /* init our chunky engine. */
}
else if(namelen) {
const struct content_encoding *encoding = find_encoding(name, namelen);
const struct content_encoding *encoding;
struct contenc_writer *writer;
if(is_transfer && !data->set.http_transfer_encoding)
/* not requested, ignore */
return CURLE_OK;
encoding = find_encoding(name, namelen);
if(!k->writer_stack) {
k->writer_stack = new_unencoding_writer(data, &client_encoding,
+59 -76
View File
@@ -483,11 +483,6 @@ static int invalid_octets(const char *p)
*/
struct Cookie *
Curl_cookie_add(struct Curl_easy *data,
/*
* The 'data' pointer here may be NULL at times, and thus
* must only be used very carefully for things that can deal
* with data being NULL. Such as infof() and similar
*/
struct CookieInfo *c,
bool httpheader, /* TRUE if HTTP header-style line */
bool noexpire, /* if TRUE, skip remove_expired() */
@@ -508,10 +503,7 @@ Curl_cookie_add(struct Curl_easy *data,
bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */
size_t myhash;
#ifdef CURL_DISABLE_VERBOSE_STRINGS
(void)data;
#endif
DEBUGASSERT(data);
DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */
if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)
return NULL;
@@ -523,8 +515,6 @@ Curl_cookie_add(struct Curl_easy *data,
if(httpheader) {
/* This line was read off an HTTP-header */
const char *namep;
const char *valuep;
const char *ptr;
size_t linelength = strlen(lineptr);
@@ -547,8 +537,9 @@ Curl_cookie_add(struct Curl_easy *data,
if(nlen) {
bool done = FALSE;
bool sep = FALSE;
const char *namep = ptr;
const char *valuep;
namep = ptr;
ptr += nlen;
/* trim trailing spaces and tabs after name */
@@ -1128,17 +1119,11 @@ Curl_cookie_add(struct Curl_easy *data,
if(replace_old) {
/* the domains were identical */
if(clist->spath && co->spath) {
if(strcasecompare(clist->spath, co->spath))
replace_old = TRUE;
else
replace_old = FALSE;
}
else if(!clist->spath && !co->spath)
replace_old = TRUE;
else
if(clist->spath && co->spath &&
!strcasecompare(clist->spath, co->spath))
replace_old = FALSE;
else if(!clist->spath != !co->spath)
replace_old = FALSE;
}
if(replace_old && !co->livecookie && clist->livecookie) {
@@ -1219,7 +1204,8 @@ Curl_cookie_add(struct Curl_easy *data,
*
* If 'newsession' is TRUE, discard all "session cookies" on read from file.
*
* Note that 'data' might be called as NULL pointer.
* Note that 'data' might be called as NULL pointer. If data is NULL, 'file'
* will be ignored.
*
* Returns NULL on out of memory. Invalid cookies are ignored.
*/
@@ -1229,9 +1215,8 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
bool newsession)
{
struct CookieInfo *c;
FILE *fp = NULL;
bool fromfile = TRUE;
char *line = NULL;
FILE *handle = NULL;
if(!inc) {
/* we didn't get a struct, create one */
@@ -1251,61 +1236,59 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
/* we got an already existing one, use that */
c = inc;
}
c->running = FALSE; /* this is not running, this is init */
if(file && !strcmp(file, "-")) {
fp = stdin;
fromfile = FALSE;
}
else if(!file || !*file) {
/* points to an empty string or NULL */
fp = NULL;
}
else {
fp = fopen(file, "rb");
if(!fp)
infof(data, "WARNING: failed to open cookie file \"%s\"", file);
}
c->newsession = newsession; /* new session? */
if(fp) {
char *lineptr;
bool headerline;
line = malloc(MAX_COOKIE_LINE);
if(!line)
goto fail;
while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) {
if(checkprefix("Set-Cookie:", line)) {
/* This is a cookie line, get it! */
lineptr = &line[11];
headerline = TRUE;
}
if(data) {
FILE *fp = NULL;
if(file) {
if(!strcmp(file, "-"))
fp = stdin;
else {
lineptr = line;
headerline = FALSE;
fp = fopen(file, "rb");
if(!fp)
infof(data, "WARNING: failed to open cookie file \"%s\"", file);
else
handle = fp;
}
while(*lineptr && ISBLANK(*lineptr))
lineptr++;
Curl_cookie_add(data, c, headerline, TRUE, lineptr, NULL, NULL, TRUE);
}
free(line); /* free the line buffer */
/*
* Remove expired cookies from the hash. We must make sure to run this
* after reading the file, and not on every cookie.
*/
remove_expired(c);
c->running = FALSE; /* this is not running, this is init */
if(fp) {
char *lineptr;
bool headerline;
if(fromfile)
fclose(fp);
}
line = malloc(MAX_COOKIE_LINE);
if(!line)
goto fail;
while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) {
if(checkprefix("Set-Cookie:", line)) {
/* This is a cookie line, get it! */
lineptr = &line[11];
headerline = TRUE;
}
else {
lineptr = line;
headerline = FALSE;
}
while(*lineptr && ISBLANK(*lineptr))
lineptr++;
c->running = TRUE; /* now, we're running */
if(data)
Curl_cookie_add(data, c, headerline, TRUE, lineptr, NULL, NULL, TRUE);
}
free(line); /* free the line buffer */
/*
* Remove expired cookies from the hash. We must make sure to run this
* after reading the file, and not on every cookie.
*/
remove_expired(c);
if(handle)
fclose(handle);
}
data->state.cookie_engine = TRUE;
c->running = TRUE; /* now, we're running */
}
return c;
@@ -1317,8 +1300,8 @@ fail:
*/
if(!inc)
Curl_cookie_cleanup(c);
if(fromfile && fp)
fclose(fp);
if(handle)
fclose(handle);
return NULL; /* out of memory */
}
@@ -1404,7 +1387,7 @@ static struct Cookie *dup_cookie(struct Cookie *src)
}
return d;
fail:
fail:
freecookie(d);
return NULL;
}
@@ -1448,7 +1431,7 @@ struct Cookie *Curl_cookie_getlist(struct Curl_easy *data,
/* now check if the domain is correct */
if(!co->domain ||
(co->tailmatch && !is_ip &&
tailmatch(co->domain, co->domain? strlen(co->domain):0, host)) ||
tailmatch(co->domain, strlen(co->domain), host)) ||
((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) {
/*
* the right part of the host matches the domain stuff in the
@@ -1738,7 +1721,7 @@ static CURLcode cookie_output(struct Curl_easy *data,
}
/*
* If we reach here we have successfully written a cookie file so theree is
* If we reach here we have successfully written a cookie file so there is
* no need to inspect the error, any error case should have jumped into the
* error block below.
*/
+20 -15
View File
@@ -61,7 +61,6 @@ struct Cookie {
struct CookieInfo {
/* linked list of cookies we know of */
struct Cookie *cookies[COOKIE_HASH_SIZE];
char *filename; /* file we read from/write to */
long numcookies; /* number of cookies in the "jar" */
bool running; /* state info, for cookie adding information */
@@ -70,23 +69,34 @@ struct CookieInfo {
curl_off_t next_expiration; /* the next time at which expiration happens */
};
/* This is the maximum line length we accept for a cookie line. RFC 2109
section 6.3 says:
/* The maximum sizes we accept for cookies. RFC 6265 section 6.1 says
"general-use user agents SHOULD provide each of the following minimum
capabilities":
"at least 4096 bytes per cookie (as measured by the size of the characters
that comprise the cookie non-terminal in the syntax description of the
Set-Cookie header)"
We allow max 5000 bytes cookie header. Max 4095 bytes length per cookie
name and value. Name + value may not exceed 4096 bytes.
- At least 4096 bytes per cookie (as measured by the sum of the length of
the cookie's name, value, and attributes).
In the 6265bis draft document section 5.4 it is phrased even stronger: "If
the sum of the lengths of the name string and the value string is more than
4096 octets, abort these steps and ignore the set-cookie-string entirely."
*/
/** Limits for INCOMING cookies **/
/* The longest we allow a line to be when reading a cookie from a HTTP header
or from a cookie jar */
#define MAX_COOKIE_LINE 5000
/* Maximum length of an incoming cookie name or content we deal with. Longer
cookies are ignored. */
#define MAX_NAME 4096
#define MAX_NAME_TXT "4095"
/* Maximum number of Set-Cookie: lines accepted in a single response. If more
such header lines are received, they are ignored. This value must be less
than 256 since an unsigned char is used to count. */
#define MAX_SET_COOKIE_AMOUNT 50
/** Limits for OUTGOING cookies **/
/* Maximum size for an outgoing cookie line libcurl will use in an http
request. This is the default maximum length used in some versions of Apache
@@ -98,11 +108,6 @@ struct CookieInfo {
keep the maximum HTTP request within the maximum allowed size. */
#define MAX_COOKIE_SEND_AMOUNT 150
/* Maximum number of Set-Cookie: lines accepted in a single response. If more
such header lines are received, they are ignored. This value must be less
than 256 since an unsigned char is used to count. */
#define MAX_SET_COOKIE_AMOUNT 50
struct Curl_easy;
/*
* Add a cookie to the internal list of cookies. The domain and path arguments
+1 -1
View File
@@ -274,7 +274,7 @@ Curl_he2ai(const struct hostent *he, int port)
for(i = 0; (curr = he->h_addr_list[i]) != NULL; i++) {
size_t ss_size;
size_t namelen = strlen(he->h_name) + 1; /* include null-terminatior */
size_t namelen = strlen(he->h_name) + 1; /* include null-terminator */
#ifdef ENABLE_IPV6
if(he->h_addrtype == AF_INET6)
ss_size = sizeof(struct sockaddr_in6);
+7
View File
@@ -38,6 +38,9 @@
#include "connect.h"
#include "http2.h"
#include "http_proxy.h"
#include "cf-h1-proxy.h"
#include "cf-h2-proxy.h"
#include "cf-haproxy.h"
#include "cf-https-connect.h"
#include "socks.h"
#include "strtok.h"
@@ -160,6 +163,10 @@ static struct Curl_cftype *cf_types[] = {
#endif
#if !defined(CURL_DISABLE_PROXY)
#if !defined(CURL_DISABLE_HTTP)
&Curl_cft_h1_proxy,
#ifdef USE_NGHTTP2
&Curl_cft_h2_proxy,
#endif
&Curl_cft_http_proxy,
#endif /* !CURL_DISABLE_HTTP */
&Curl_cft_haproxy,
-36
View File
@@ -52,39 +52,12 @@
* mentioned above will compile without any indication, but it will
* trigger weird memory related issues at runtime.
*
* OTOH some source files from 'lib' subdirectory may additionally be
* used directly as source code when using some curlx_ functions by
* third party programs that don't even use libcurl at all. When using
* these source files in this way it is necessary these are compiled
* with CURLX_NO_MEMORY_CALLBACKS defined, in order to ensure that no
* attempt of calling libcurl's memory callbacks is done from code
* which can not use this machinery.
*
* Notice that libcurl's 'memory tracking' system works chaining into
* the memory callback machinery. This implies that when compiling
* 'lib' source files with CURLX_NO_MEMORY_CALLBACKS defined this file
* disengages usage of libcurl's 'memory tracking' system, defining
* MEMDEBUG_NODEFINES and overriding CURLDEBUG purpose.
*
* CURLX_NO_MEMORY_CALLBACKS takes precedence over CURLDEBUG. This is
* done in order to allow building a 'memory tracking' enabled libcurl
* and at the same time allow building programs which do not use it.
*
* Programs and libraries in 'tests' subdirectories have specific
* purposes and needs, and as such each one will use whatever fits
* best, depending additionally whether it links with libcurl or not.
*
* Caveat emptor. Proper curlx_* separation is a work in progress
* the same as CURLX_NO_MEMORY_CALLBACKS usage, some adjustments may
* still be required. IOW don't use them yet, there are sharp edges.
*/
#ifdef HEADER_CURL_MEMDEBUG_H
#error "Header memdebug.h shall not be included before curl_memory.h"
#endif
#ifndef CURLX_NO_MEMORY_CALLBACKS
#ifndef CURL_DID_MEMORY_FUNC_TYPEDEFS /* only if not already done */
/*
* The following memory function replacement typedef's are COPIED from
@@ -146,13 +119,4 @@ extern curl_wcsdup_callback Curl_cwcsdup;
#endif
#endif /* CURLDEBUG */
#else /* CURLX_NO_MEMORY_CALLBACKS */
#ifndef MEMDEBUG_NODEFINES
#define MEMDEBUG_NODEFINES
#endif
#endif /* CURLX_NO_MEMORY_CALLBACKS */
#endif /* HEADER_CURL_MEMORY_H */
+4
View File
@@ -83,6 +83,10 @@
# define DES_ecb_encrypt des_ecb_encrypt
# define DESKEY(x) x
# define DESKEYARG(x) x
# elif defined(OPENSSL_IS_AWSLC)
# define DES_set_key_unchecked (void)DES_set_key
# define DESKEYARG(x) *x
# define DESKEY(x) &x
# else
# define DESKEYARG(x) *x
# define DESKEY(x) &x
+17 -14
View File
@@ -62,24 +62,27 @@ CURLcode Curl_getworkingpath(struct Curl_easy *data,
}
}
else if((data->conn->handler->protocol & CURLPROTO_SFTP) &&
(working_path_len > 2) && !memcmp(working_path, "/~/", 3)) {
size_t len;
const char *p;
int copyfrom = 3;
(!strcmp("/~", working_path) ||
((working_path_len > 2) && !memcmp(working_path, "/~/", 3)))) {
if(Curl_dyn_add(&npath, homedir)) {
free(working_path);
return CURLE_OUT_OF_MEMORY;
}
/* Copy a separating '/' if homedir does not end with one */
len = Curl_dyn_len(&npath);
p = Curl_dyn_ptr(&npath);
if(len && (p[len-1] != '/'))
copyfrom = 2;
if(working_path_len > 2) {
size_t len;
const char *p;
int copyfrom = 3;
/* Copy a separating '/' if homedir does not end with one */
len = Curl_dyn_len(&npath);
p = Curl_dyn_ptr(&npath);
if(len && (p[len-1] != '/'))
copyfrom = 2;
if(Curl_dyn_addn(&npath,
&working_path[copyfrom], working_path_len - copyfrom)) {
free(working_path);
return CURLE_OUT_OF_MEMORY;
if(Curl_dyn_addn(&npath,
&working_path[copyfrom], working_path_len - copyfrom)) {
free(working_path);
return CURLE_OUT_OF_MEMORY;
}
}
}
@@ -188,7 +191,7 @@ CURLcode Curl_get_pathname(const char **cpp, char **path, char *homedir)
}
return CURLE_OK;
fail:
fail:
Curl_safefree(*path);
return CURLE_QUOTE_ERROR;
}
+2 -2
View File
@@ -231,7 +231,7 @@ static CURLcode rtmp_connect(struct Curl_easy *data, bool *done)
/* We have to know if it's a write before we send the
* connect request packet
*/
if(data->set.upload)
if(data->state.upload)
r->Link.protocol |= RTMP_FEATURE_WRITE;
/* For plain streams, use the buffer toggle trick to keep data flowing */
@@ -263,7 +263,7 @@ static CURLcode rtmp_do(struct Curl_easy *data, bool *done)
if(!RTMP_ConnectStream(r, 0))
return CURLE_FAILED_INIT;
if(data->set.upload) {
if(data->state.upload) {
Curl_pgrsSetUploadSize(data, data->state.infilesize);
Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET);
}
+2 -18
View File
@@ -792,23 +792,6 @@ endings either CRLF or LF so 't' is appropriate.
#define FOPEN_APPENDTEXT "a"
#endif
/* 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 */
# endif
#else /* DONT_USE_RECV_BEFORE_SEND_WORKAROUND */
# ifdef USE_RECV_BEFORE_SEND_WORKAROUND
# undef USE_RECV_BEFORE_SEND_WORKAROUND
# endif
#endif /* DONT_USE_RECV_BEFORE_SEND_WORKAROUND */
/* for systems that don't detect this in configure */
#ifndef CURL_SA_FAMILY_T
# if defined(HAVE_SA_FAMILY_T)
@@ -848,7 +831,8 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf,
#define USE_HTTP2
#endif
#if defined(USE_NGTCP2) || defined(USE_QUICHE) || defined(USE_MSH3)
#if (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \
defined(USE_QUICHE) || defined(USE_MSH3)
#define ENABLE_QUIC
#define USE_HTTP3
#endif
+1 -1
View File
@@ -312,7 +312,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done)
}
}
error:
error:
free(eword);
free(path);
return result;
+2 -2
View File
@@ -347,7 +347,7 @@ static CURLcode dohprobe(struct Curl_easy *data,
free(nurl);
return CURLE_OK;
error:
error:
free(nurl);
Curl_close(&doh);
return result;
@@ -409,7 +409,7 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
#endif
return NULL;
error:
error:
curl_slist_free_all(dohp->headers);
data->req.doh->headers = NULL;
for(slot = 0; slot < DOH_PROBE_SLOTS; slot++) {
+7 -1
View File
@@ -76,6 +76,7 @@ static CURLcode dyn_nappend(struct dynbuf *s,
DEBUGASSERT(s->toobig);
DEBUGASSERT(indx < s->toobig);
DEBUGASSERT(!s->leng || s->bufr);
DEBUGASSERT(a <= s->toobig);
if(fit > s->toobig) {
Curl_dyn_free(s);
@@ -84,7 +85,9 @@ static CURLcode dyn_nappend(struct dynbuf *s,
else if(!a) {
DEBUGASSERT(!indx);
/* first invoke */
if(fit < MIN_FIRST_ALLOC)
if(MIN_FIRST_ALLOC > s->toobig)
a = s->toobig;
else if(fit < MIN_FIRST_ALLOC)
a = MIN_FIRST_ALLOC;
else
a = fit;
@@ -92,6 +95,9 @@ static CURLcode dyn_nappend(struct dynbuf *s,
else {
while(a < fit)
a *= 2;
if(a > s->toobig)
/* no point in allocating a larger buffer than this is allowed to use */
a = s->toobig;
}
if(a != s->allc) {
+366
View File
@@ -0,0 +1,366 @@
/***************************************************************************
* _ _ ____ _
* 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 "dynhds.h"
#include "strcase.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
static struct dynhds_entry *
entry_new(const char *name, size_t namelen,
const char *value, size_t valuelen, int opts)
{
struct dynhds_entry *e;
char *p;
DEBUGASSERT(name);
DEBUGASSERT(value);
e = calloc(1, sizeof(*e) + namelen + valuelen + 2);
if(!e)
return NULL;
e->name = p = ((char *)e) + sizeof(*e);
memcpy(p, name, namelen);
e->namelen = namelen;
e->value = p += namelen + 1; /* leave a \0 at the end of name */
memcpy(p, value, valuelen);
e->valuelen = valuelen;
if(opts & DYNHDS_OPT_LOWERCASE)
Curl_strntolower(e->name, e->name, e->namelen);
return e;
}
static struct dynhds_entry *
entry_append(struct dynhds_entry *e,
const char *value, size_t valuelen)
{
struct dynhds_entry *e2;
size_t valuelen2 = e->valuelen + 1 + valuelen;
char *p;
DEBUGASSERT(value);
e2 = calloc(1, sizeof(*e) + e->namelen + valuelen2 + 2);
if(!e2)
return NULL;
e2->name = p = ((char *)e2) + sizeof(*e2);
memcpy(p, e->name, e->namelen);
e2->namelen = e->namelen;
e2->value = p += e->namelen + 1; /* leave a \0 at the end of name */
memcpy(p, e->value, e->valuelen);
p += e->valuelen;
p[0] = ' ';
memcpy(p + 1, value, valuelen);
e2->valuelen = valuelen2;
return e2;
}
static void entry_free(struct dynhds_entry *e)
{
free(e);
}
void Curl_dynhds_init(struct dynhds *dynhds, size_t max_entries,
size_t max_strs_size)
{
DEBUGASSERT(dynhds);
DEBUGASSERT(max_strs_size);
dynhds->hds = NULL;
dynhds->hds_len = dynhds->hds_allc = dynhds->strs_len = 0;
dynhds->max_entries = max_entries;
dynhds->max_strs_size = max_strs_size;
dynhds->opts = 0;
}
void Curl_dynhds_free(struct dynhds *dynhds)
{
DEBUGASSERT(dynhds);
if(dynhds->hds && dynhds->hds_len) {
size_t i;
DEBUGASSERT(dynhds->hds);
for(i = 0; i < dynhds->hds_len; ++i) {
entry_free(dynhds->hds[i]);
}
}
Curl_safefree(dynhds->hds);
dynhds->hds_len = dynhds->hds_allc = dynhds->strs_len = 0;
}
void Curl_dynhds_reset(struct dynhds *dynhds)
{
DEBUGASSERT(dynhds);
if(dynhds->hds_len) {
size_t i;
DEBUGASSERT(dynhds->hds);
for(i = 0; i < dynhds->hds_len; ++i) {
entry_free(dynhds->hds[i]);
dynhds->hds[i] = NULL;
}
}
dynhds->hds_len = dynhds->strs_len = 0;
}
size_t Curl_dynhds_count(struct dynhds *dynhds)
{
return dynhds->hds_len;
}
void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts)
{
dynhds->opts = opts;
}
struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n)
{
DEBUGASSERT(dynhds);
return (n < dynhds->hds_len)? dynhds->hds[n] : NULL;
}
struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds, const char *name,
size_t namelen)
{
size_t i;
for(i = 0; i < dynhds->hds_len; ++i) {
if(dynhds->hds[i]->namelen == namelen &&
strncasecompare(dynhds->hds[i]->name, name, namelen)) {
return dynhds->hds[i];
}
}
return NULL;
}
struct dynhds_entry *Curl_dynhds_cget(struct dynhds *dynhds, const char *name)
{
return Curl_dynhds_get(dynhds, name, strlen(name));
}
CURLcode Curl_dynhds_add(struct dynhds *dynhds,
const char *name, size_t namelen,
const char *value, size_t valuelen)
{
struct dynhds_entry *entry = NULL;
CURLcode result = CURLE_OUT_OF_MEMORY;
DEBUGASSERT(dynhds);
if(dynhds->max_entries && dynhds->hds_len >= dynhds->max_entries)
return CURLE_OUT_OF_MEMORY;
if(dynhds->strs_len + namelen + valuelen > dynhds->max_strs_size)
return CURLE_OUT_OF_MEMORY;
entry = entry_new(name, namelen, value, valuelen, dynhds->opts);
if(!entry)
goto out;
if(dynhds->hds_len + 1 >= dynhds->hds_allc) {
size_t nallc = dynhds->hds_len + 16;
struct dynhds_entry **nhds;
if(dynhds->max_entries && nallc > dynhds->max_entries)
nallc = dynhds->max_entries;
nhds = calloc(nallc, sizeof(struct dynhds_entry *));
if(!nhds)
goto out;
if(dynhds->hds) {
memcpy(nhds, dynhds->hds,
dynhds->hds_len * sizeof(struct dynhds_entry *));
Curl_safefree(dynhds->hds);
}
dynhds->hds = nhds;
dynhds->hds_allc = nallc;
}
dynhds->hds[dynhds->hds_len++] = entry;
entry = NULL;
dynhds->strs_len += namelen + valuelen;
result = CURLE_OK;
out:
if(entry)
entry_free(entry);
return result;
}
CURLcode Curl_dynhds_cadd(struct dynhds *dynhds,
const char *name, const char *value)
{
return Curl_dynhds_add(dynhds, name, strlen(name), value, strlen(value));
}
CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds,
const char *line, size_t line_len)
{
const char *p;
const char *name;
size_t namelen;
const char *value;
size_t valuelen, i;
if(!line || !line_len)
return CURLE_OK;
if((line[0] == ' ') || (line[0] == '\t')) {
struct dynhds_entry *e, *e2;
/* header continuation, yikes! */
if(!dynhds->hds_len)
return CURLE_BAD_FUNCTION_ARGUMENT;
while(line_len && ISBLANK(line[0])) {
++line;
--line_len;
}
if(!line_len)
return CURLE_BAD_FUNCTION_ARGUMENT;
e = dynhds->hds[dynhds->hds_len-1];
e2 = entry_append(e, line, line_len);
if(!e2)
return CURLE_OUT_OF_MEMORY;
dynhds->hds[dynhds->hds_len-1] = e2;
entry_free(e);
return CURLE_OK;
}
else {
p = memchr(line, ':', line_len);
if(!p)
return CURLE_BAD_FUNCTION_ARGUMENT;
name = line;
namelen = p - line;
p++; /* move past the colon */
for(i = namelen + 1; i < line_len; ++i, ++p) {
if(!ISBLANK(*p))
break;
}
value = p;
valuelen = line_len - i;
p = memchr(value, '\r', valuelen);
if(!p)
p = memchr(value, '\n', valuelen);
if(p)
valuelen = (size_t)(p - value);
return Curl_dynhds_add(dynhds, name, namelen, value, valuelen);
}
}
CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line)
{
return Curl_dynhds_h1_add_line(dynhds, line, line? strlen(line) : 0);
}
#ifdef DEBUGBUILD
/* used by unit2602.c */
bool Curl_dynhds_contains(struct dynhds *dynhds,
const char *name, size_t namelen)
{
return !!Curl_dynhds_get(dynhds, name, namelen);
}
bool Curl_dynhds_ccontains(struct dynhds *dynhds, const char *name)
{
return Curl_dynhds_contains(dynhds, name, strlen(name));
}
size_t Curl_dynhds_count_name(struct dynhds *dynhds,
const char *name, size_t namelen)
{
size_t n = 0;
if(dynhds->hds_len) {
size_t i;
for(i = 0; i < dynhds->hds_len; ++i) {
if((namelen == dynhds->hds[i]->namelen) &&
strncasecompare(name, dynhds->hds[i]->name, namelen))
++n;
}
}
return n;
}
size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name)
{
return Curl_dynhds_count_name(dynhds, name, strlen(name));
}
CURLcode Curl_dynhds_set(struct dynhds *dynhds,
const char *name, size_t namelen,
const char *value, size_t valuelen)
{
Curl_dynhds_remove(dynhds, name, namelen);
return Curl_dynhds_add(dynhds, name, namelen, value, valuelen);
}
size_t Curl_dynhds_remove(struct dynhds *dynhds,
const char *name, size_t namelen)
{
size_t n = 0;
if(dynhds->hds_len) {
size_t i, len;
for(i = 0; i < dynhds->hds_len; ++i) {
if((namelen == dynhds->hds[i]->namelen) &&
strncasecompare(name, dynhds->hds[i]->name, namelen)) {
++n;
--dynhds->hds_len;
dynhds->strs_len -= (dynhds->hds[i]->namelen +
dynhds->hds[i]->valuelen);
entry_free(dynhds->hds[i]);
len = dynhds->hds_len - i; /* remaining entries */
if(len) {
memmove(&dynhds->hds[i], &dynhds->hds[i + 1],
len * sizeof(dynhds->hds[i]));
}
--i; /* do this index again */
}
}
}
return n;
}
size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name)
{
return Curl_dynhds_remove(dynhds, name, strlen(name));
}
CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf)
{
CURLcode result = CURLE_OK;
size_t i;
if(!dynhds->hds_len)
return result;
for(i = 0; i < dynhds->hds_len; ++i) {
result = Curl_dyn_addf(dbuf, "%.*s: %.*s\r\n",
(int)dynhds->hds[i]->namelen, dynhds->hds[i]->name,
(int)dynhds->hds[i]->valuelen, dynhds->hds[i]->value);
if(result)
break;
}
return result;
}
#endif
+174
View File
@@ -0,0 +1,174 @@
#ifndef HEADER_CURL_DYNHDS_H
#define HEADER_CURL_DYNHDS_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 <curl/curl.h>
#include "dynbuf.h"
struct dynbuf;
/**
* A single header entry.
* `name` and `value` are non-NULL and always NUL terminated.
*/
struct dynhds_entry {
char *name;
char *value;
size_t namelen;
size_t valuelen;
};
struct dynhds {
struct dynhds_entry **hds;
size_t hds_len; /* number of entries in hds */
size_t hds_allc; /* size of hds allocation */
size_t max_entries; /* size limit number of entries */
size_t strs_len; /* length of all strings */
size_t max_strs_size; /* max length of all strings */
int opts;
};
#define DYNHDS_OPT_NONE (0)
#define DYNHDS_OPT_LOWERCASE (1 << 0)
/**
* Init for use on first time or after a reset.
* Allow `max_entries` headers to be added, 0 for unlimited.
* Allow size of all name and values added to not exceed `max_strs_size``
*/
void Curl_dynhds_init(struct dynhds *dynhds, size_t max_entries,
size_t max_strs_size);
/**
* Frees all data held in `dynhds`, but not the struct itself.
*/
void Curl_dynhds_free(struct dynhds *dynhds);
/**
* Reset `dyndns` to the initial init state. May keep allocations
* around.
*/
void Curl_dynhds_reset(struct dynhds *dynhds);
/**
* Return the number of header entries.
*/
size_t Curl_dynhds_count(struct dynhds *dynhds);
/**
* Set the options to use, replacing any existing ones.
* This will not have an effect on already existing headers.
*/
void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts);
/**
* Return the n-th header entry or NULL if it does not exist.
*/
struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n);
/**
* Return the 1st header entry of the name or NULL if none exists.
*/
struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds,
const char *name, size_t namelen);
struct dynhds_entry *Curl_dynhds_cget(struct dynhds *dynhds, const char *name);
/**
* Return TRUE iff one or more headers with the given name exist.
*/
bool Curl_dynhds_contains(struct dynhds *dynhds,
const char *name, size_t namelen);
bool Curl_dynhds_ccontains(struct dynhds *dynhds, const char *name);
/**
* Return how often the given name appears in `dynhds`.
* Names are case-insensitive.
*/
size_t Curl_dynhds_count_name(struct dynhds *dynhds,
const char *name, size_t namelen);
/**
* Return how often the given 0-terminated name appears in `dynhds`.
* Names are case-insensitive.
*/
size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name);
/**
* Add a header, name + value, to `dynhds` at the end. Does *not*
* check for duplicate names.
*/
CURLcode Curl_dynhds_add(struct dynhds *dynhds,
const char *name, size_t namelen,
const char *value, size_t valuelen);
/**
* Add a header, c-string name + value, to `dynhds` at the end.
*/
CURLcode Curl_dynhds_cadd(struct dynhds *dynhds,
const char *name, const char *value);
/**
* Remove all entries with the given name.
* Returns number of entries removed.
*/
size_t Curl_dynhds_remove(struct dynhds *dynhds,
const char *name, size_t namelen);
size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name);
/**
* Set the give header name and value, replacing any entries with
* the same name. The header is added at the end of all (remaining)
* entries.
*/
CURLcode Curl_dynhds_set(struct dynhds *dynhds,
const char *name, size_t namelen,
const char *value, size_t valuelen);
CURLcode Curl_dynhds_cset(struct dynhds *dynhds,
const char *name, const char *value);
/**
* Add a single header from a HTTP/1.1 formatted line at the end. Line
* may contain a delimiting \r\n or just \n. Any characters after
* that will be ignored.
*/
CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line);
/**
* Add a single header from a HTTP/1.1 formatted line at the end. Line
* may contain a delimiting \r\n or just \n. Any characters after
* that will be ignored.
*/
CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds,
const char *line, size_t line_len);
/**
* Add the headers to the given `dynbuf` in HTTP/1.1 format with
* cr+lf line endings. Will NOT output a last empty line.
*/
CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf);
#endif /* HEADER_CURL_DYNHDS_H */
+28 -18
View File
@@ -24,14 +24,6 @@
#include "curl_setup.h"
/*
* See comment in curl_memory.h for the explanation of this sanity check.
*/
#ifdef CURLX_NO_MEMORY_CALLBACKS
#error "libcurl shall not ever be built with CURLX_NO_MEMORY_CALLBACKS defined"
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
@@ -217,7 +209,7 @@ static CURLcode global_init(long flags, bool memoryfuncs)
return CURLE_OK;
fail:
fail:
initialized--; /* undo the increase */
return CURLE_FAILED_INIT;
}
@@ -795,14 +787,12 @@ CURLcode curl_easy_perform_ev(struct Curl_easy *data)
*/
void curl_easy_cleanup(struct Curl_easy *data)
{
SIGPIPE_VARIABLE(pipe_st);
if(!data)
return;
sigpipe_ignore(data, &pipe_st);
Curl_close(&data);
sigpipe_restore(&pipe_st);
if(GOOD_EASY_HANDLE(data)) {
SIGPIPE_VARIABLE(pipe_st);
sigpipe_ignore(data, &pipe_st);
Curl_close(&data);
sigpipe_restore(&pipe_st);
}
}
/*
@@ -1003,7 +993,7 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data)
return outcurl;
fail:
fail:
if(outcurl) {
#ifndef CURL_DISABLE_COOKIES
@@ -1231,6 +1221,26 @@ CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen,
return CURLE_OK;
}
#ifdef USE_WEBSOCKETS
CURLcode Curl_connect_only_attach(struct Curl_easy *data)
{
curl_socket_t sfd;
CURLcode result;
struct connectdata *c = NULL;
result = easy_connection(data, &sfd, &c);
if(result)
return result;
if(!data->conn)
/* on first invoke, the transfer has been detached from the connection and
needs to be reattached */
Curl_attach_connection(data, c);
return CURLE_OK;
}
#endif /* USE_WEBSOCKETS */
/*
* Sends data over the connected socket.
*
+4
View File
@@ -30,6 +30,10 @@
CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer,
size_t buflen, ssize_t *n);
#ifdef USE_WEBSOCKETS
CURLcode Curl_connect_only_attach(struct Curl_easy *data);
#endif
#ifdef CURLDEBUG
CURL_EXTERN CURLcode curl_easy_perform_ev(struct Curl_easy *easy);
#endif
+2 -2
View File
@@ -240,7 +240,7 @@ static CURLcode file_connect(struct Curl_easy *data, bool *done)
file->freepath = real_path; /* free this when done */
file->fd = fd;
if(!data->set.upload && (fd == -1)) {
if(!data->state.upload && (fd == -1)) {
failf(data, "Couldn't open file %s", data->state.up.path);
file_done(data, CURLE_FILE_COULDNT_READ_FILE, FALSE);
return CURLE_FILE_COULDNT_READ_FILE;
@@ -422,7 +422,7 @@ static CURLcode file_do(struct Curl_easy *data, bool *done)
Curl_pgrsStartNow(data);
if(data->set.upload)
if(data->state.upload)
return file_upload(data);
file = data->req.p.file;
+1 -1
View File
@@ -40,7 +40,7 @@ void Curl_fileinfo_cleanup(struct fileinfo *finfo)
if(!finfo)
return;
Curl_safefree(finfo->info.b_data);
Curl_dyn_free(&finfo->buf);
free(finfo);
}
#endif
+2
View File
@@ -26,10 +26,12 @@
#include <curl/curl.h>
#include "llist.h"
#include "dynbuf.h"
struct fileinfo {
struct curl_fileinfo info;
struct Curl_llist_element list;
struct dynbuf buf;
};
struct fileinfo *Curl_fileinfo_alloc(void);
+12 -18
View File
@@ -1085,8 +1085,6 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
host = NULL;
/* step 2, create a socket for the requested address */
portsock = CURL_SOCKET_BAD;
error = 0;
for(ai = res; ai; ai = ai->ai_next) {
if(Curl_socket_open(data, ai, NULL, conn->transport, &portsock)) {
@@ -1350,7 +1348,7 @@ static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data)
data->set.str[STRING_CUSTOMREQUEST]?
data->set.str[STRING_CUSTOMREQUEST]:
(data->state.list_only?"NLST":"LIST"));
else if(data->set.upload)
else if(data->state.upload)
result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s",
conn->proto.ftpc.file);
else
@@ -3386,7 +3384,7 @@ static CURLcode ftp_done(struct Curl_easy *data, CURLcode status,
/* the response code from the transfer showed an error already so no
use checking further */
;
else if(data->set.upload) {
else if(data->state.upload) {
if((-1 != data->state.infilesize) &&
(data->state.infilesize != data->req.writebytecount) &&
!data->set.crlf &&
@@ -3621,7 +3619,7 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep)
/* a transfer is about to take place, or if not a file name was given
so we'll do a SIZE on it later and then we need the right TYPE first */
if(ftpc->wait_data_conn == TRUE) {
if(ftpc->wait_data_conn) {
bool serv_conned;
result = ReceivedServerConnect(data, &serv_conned);
@@ -3642,20 +3640,14 @@ static CURLcode ftp_do_more(struct Curl_easy *data, int *completep)
connected back to us */
}
}
else if(data->set.upload) {
else if(data->state.upload) {
result = ftp_nb_type(data, conn, data->state.prefer_ascii,
FTP_STOR_TYPE);
if(result)
return result;
result = ftp_multi_statemach(data, &complete);
if(ftpc->wait_data_conn)
/* if we reach the end of the FTP state machine here, *complete will be
TRUE but so is ftpc->wait_data_conn, which says we need to wait for
the data connection and therefore we're not actually complete */
*completep = 0;
else
*completep = (int)complete;
*completep = (int)complete;
}
else {
/* download */
@@ -3847,7 +3839,7 @@ static CURLcode init_wc_data(struct Curl_easy *data)
infof(data, "Wildcard - Parsing started");
return CURLE_OK;
fail:
fail:
if(ftpwc) {
Curl_ftp_parselist_data_free(&ftpwc->parser);
free(ftpwc);
@@ -3978,8 +3970,10 @@ static CURLcode wc_statemach(struct Curl_easy *data)
case CURLWC_DONE:
case CURLWC_ERROR:
case CURLWC_CLEAR:
if(wildcard->dtor)
if(wildcard->dtor) {
wildcard->dtor(wildcard->ftpwc);
wildcard->ftpwc = NULL;
}
return result;
}
}
@@ -4141,7 +4135,7 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data)
case FTPFILE_NOCWD: /* fastest, but less standard-compliant */
if((pathLen > 0) && (rawPath[pathLen - 1] != '/'))
fileName = rawPath; /* this is a full file path */
fileName = rawPath; /* this is a full file path */
/*
else: ftpc->file is not used anywhere other than for operations on
a file. In other words, never for directory operations.
@@ -4187,7 +4181,7 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data)
size_t dirAlloc = 0;
const char *str = rawPath;
for(; *str != 0; ++str)
if (*str == '/')
if(*str == '/')
++dirAlloc;
if(dirAlloc) {
@@ -4232,7 +4226,7 @@ CURLcode ftp_parse_url_path(struct Curl_easy *data)
ftpc->file = NULL; /* instead of point to a zero byte,
we make it a NULL pointer */
if(data->set.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) {
if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) {
/* We need a file name when uploading. Return error! */
failf(data, "Uploading to a URL without a file name");
free(rawPath);
+55 -68
View File
@@ -318,8 +318,8 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data,
bool add = TRUE;
struct curl_fileinfo *finfo = &infop->info;
/* move finfo pointers to b_data */
char *str = finfo->b_data;
/* set the finfo pointers */
char *str = Curl_dyn_ptr(&infop->buf);
finfo->filename = str + parser->offsets.filename;
finfo->strings.group = parser->offsets.group ?
str + parser->offsets.group : NULL;
@@ -362,6 +362,8 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data,
return CURLE_OK;
}
#define MAX_FTPLIST_BUFFER 10000 /* arbitrarily set */
size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
void *connptr)
{
@@ -369,8 +371,6 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
struct Curl_easy *data = (struct Curl_easy *)connptr;
struct ftp_wc *ftpwc = data->wildcard->ftpwc;
struct ftp_parselist_data *parser = ftpwc->parser;
struct fileinfo *infop;
struct curl_fileinfo *finfo;
size_t i = 0;
CURLcode result;
size_t retsize = bufflen;
@@ -387,48 +387,35 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) {
/* considering info about FILE response format */
parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ?
OS_TYPE_WIN_NT : OS_TYPE_UNIX;
parser->os_type = ISDIGIT(buffer[0]) ? OS_TYPE_WIN_NT : OS_TYPE_UNIX;
}
while(i < bufflen) { /* FSM */
char *mem;
size_t len; /* number of bytes of data in the dynbuf */
char c = buffer[i];
struct fileinfo *infop;
struct curl_fileinfo *finfo;
if(!parser->file_data) { /* tmp file data is not allocated yet */
parser->file_data = Curl_fileinfo_alloc();
if(!parser->file_data) {
parser->error = CURLE_OUT_OF_MEMORY;
goto fail;
}
parser->file_data->info.b_data = malloc(FTP_BUFFER_ALLOCSIZE);
if(!parser->file_data->info.b_data) {
parser->error = CURLE_OUT_OF_MEMORY;
goto fail;
}
parser->file_data->info.b_size = FTP_BUFFER_ALLOCSIZE;
parser->item_offset = 0;
parser->item_length = 0;
Curl_dyn_init(&parser->file_data->buf, MAX_FTPLIST_BUFFER);
}
infop = parser->file_data;
finfo = &infop->info;
finfo->b_data[finfo->b_used++] = c;
if(finfo->b_used >= finfo->b_size - 1) {
/* if it is important, extend buffer space for file data */
char *tmp = realloc(finfo->b_data,
finfo->b_size + FTP_BUFFER_ALLOCSIZE);
if(tmp) {
finfo->b_size += FTP_BUFFER_ALLOCSIZE;
finfo->b_data = tmp;
}
else {
Curl_fileinfo_cleanup(parser->file_data);
parser->file_data = NULL;
parser->error = CURLE_OUT_OF_MEMORY;
goto fail;
}
if(Curl_dyn_addn(&infop->buf, &c, 1)) {
parser->error = CURLE_OUT_OF_MEMORY;
goto fail;
}
len = Curl_dyn_len(&infop->buf);
mem = Curl_dyn_ptr(&infop->buf);
switch(parser->os_type) {
case OS_TYPE_UNIX:
@@ -443,7 +430,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
else {
parser->state.UNIX.main = PL_UNIX_FILETYPE;
/* start FSM again not considering size of directory */
finfo->b_used = 0;
Curl_dyn_reset(&infop->buf);
continue;
}
break;
@@ -451,12 +438,12 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
parser->item_length++;
if(c == '\r') {
parser->item_length--;
finfo->b_used--;
Curl_dyn_setlen(&infop->buf, --len);
}
else if(c == '\n') {
finfo->b_data[parser->item_length - 1] = 0;
if(strncmp("total ", finfo->b_data, 6) == 0) {
char *endptr = finfo->b_data + 6;
mem[parser->item_length - 1] = 0;
if(!strncmp("total ", mem, 6)) {
char *endptr = mem + 6;
/* here we can deal with directory size, pass the leading
whitespace and then the digits */
while(ISBLANK(*endptr))
@@ -468,7 +455,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
goto fail;
}
parser->state.UNIX.main = PL_UNIX_FILETYPE;
finfo->b_used = 0;
Curl_dyn_reset(&infop->buf);
}
else {
parser->error = CURLE_FTP_BAD_FILE_LIST;
@@ -526,8 +513,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
parser->error = CURLE_FTP_BAD_FILE_LIST;
goto fail;
}
finfo->b_data[10] = 0; /* terminate permissions */
perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset);
mem[10] = 0; /* terminate permissions */
perm = ftp_pl_get_permission(mem + parser->item_offset);
if(perm & FTP_LP_MALFORMATED_PERM) {
parser->error = CURLE_FTP_BAD_FILE_LIST;
goto fail;
@@ -545,8 +532,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
switch(parser->state.UNIX.sub.hlinks) {
case PL_UNIX_HLINKS_PRESPACE:
if(c != ' ') {
if(c >= '0' && c <= '9') {
parser->item_offset = finfo->b_used - 1;
if(ISDIGIT(c)) {
parser->item_offset = len - 1;
parser->item_length = 1;
parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER;
}
@@ -561,8 +548,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
if(c == ' ') {
char *p;
long int hlinks;
finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10);
mem[parser->item_offset + parser->item_length - 1] = 0;
hlinks = strtol(mem + parser->item_offset, &p, 10);
if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) {
parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT;
parser->file_data->info.hardlinks = hlinks;
@@ -572,7 +559,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
parser->state.UNIX.main = PL_UNIX_USER;
parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE;
}
else if(c < '0' || c > '9') {
else if(!ISDIGIT(c)) {
parser->error = CURLE_FTP_BAD_FILE_LIST;
goto fail;
}
@@ -583,7 +570,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
switch(parser->state.UNIX.sub.user) {
case PL_UNIX_USER_PRESPACE:
if(c != ' ') {
parser->item_offset = finfo->b_used - 1;
parser->item_offset = len - 1;
parser->item_length = 1;
parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING;
}
@@ -591,7 +578,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
case PL_UNIX_USER_PARSING:
parser->item_length++;
if(c == ' ') {
finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
mem[parser->item_offset + parser->item_length - 1] = 0;
parser->offsets.user = parser->item_offset;
parser->state.UNIX.main = PL_UNIX_GROUP;
parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE;
@@ -605,7 +592,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
switch(parser->state.UNIX.sub.group) {
case PL_UNIX_GROUP_PRESPACE:
if(c != ' ') {
parser->item_offset = finfo->b_used - 1;
parser->item_offset = len - 1;
parser->item_length = 1;
parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME;
}
@@ -613,7 +600,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
case PL_UNIX_GROUP_NAME:
parser->item_length++;
if(c == ' ') {
finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
mem[parser->item_offset + parser->item_length - 1] = 0;
parser->offsets.group = parser->item_offset;
parser->state.UNIX.main = PL_UNIX_SIZE;
parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE;
@@ -627,8 +614,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
switch(parser->state.UNIX.sub.size) {
case PL_UNIX_SIZE_PRESPACE:
if(c != ' ') {
if(c >= '0' && c <= '9') {
parser->item_offset = finfo->b_used - 1;
if(ISDIGIT(c)) {
parser->item_offset = len - 1;
parser->item_length = 1;
parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER;
}
@@ -643,8 +630,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
if(c == ' ') {
char *p;
curl_off_t fsize;
finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
if(!curlx_strtoofft(finfo->b_data + parser->item_offset,
mem[parser->item_offset + parser->item_length - 1] = 0;
if(!curlx_strtoofft(mem + parser->item_offset,
&p, 10, &fsize)) {
if(p[0] == '\0' && fsize != CURL_OFF_T_MAX &&
fsize != CURL_OFF_T_MIN) {
@@ -669,7 +656,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
case PL_UNIX_TIME_PREPART1:
if(c != ' ') {
if(ISALNUM(c)) {
parser->item_offset = finfo->b_used -1;
parser->item_offset = len -1;
parser->item_length = 1;
parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1;
}
@@ -726,10 +713,10 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
case PL_UNIX_TIME_PART3:
parser->item_length++;
if(c == ' ') {
finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
mem[parser->item_offset + parser->item_length -1] = 0;
parser->offsets.time = parser->item_offset;
/*
if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) {
if(ftp_pl_gettime(parser, finfo->mem + parser->item_offset)) {
parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME;
}
*/
@@ -753,7 +740,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
switch(parser->state.UNIX.sub.filename) {
case PL_UNIX_FILENAME_PRESPACE:
if(c != ' ') {
parser->item_offset = finfo->b_used - 1;
parser->item_offset = len - 1;
parser->item_length = 1;
parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME;
}
@@ -764,7 +751,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL;
}
else if(c == '\n') {
finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
mem[parser->item_offset + parser->item_length - 1] = 0;
parser->offsets.filename = parser->item_offset;
parser->state.UNIX.main = PL_UNIX_FILETYPE;
result = ftp_pl_insert_finfo(data, infop);
@@ -776,7 +763,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
break;
case PL_UNIX_FILENAME_WINDOWSEOL:
if(c == '\n') {
finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
mem[parser->item_offset + parser->item_length - 1] = 0;
parser->offsets.filename = parser->item_offset;
parser->state.UNIX.main = PL_UNIX_FILETYPE;
result = ftp_pl_insert_finfo(data, infop);
@@ -796,7 +783,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
switch(parser->state.UNIX.sub.symlink) {
case PL_UNIX_SYMLINK_PRESPACE:
if(c != ' ') {
parser->item_offset = finfo->b_used - 1;
parser->item_offset = len - 1;
parser->item_length = 1;
parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME;
}
@@ -842,7 +829,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
if(c == ' ') {
parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4;
/* now place where is symlink following */
finfo->b_data[parser->item_offset + parser->item_length - 4] = 0;
mem[parser->item_offset + parser->item_length - 4] = 0;
parser->offsets.filename = parser->item_offset;
parser->item_length = 0;
parser->item_offset = 0;
@@ -858,7 +845,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
case PL_UNIX_SYMLINK_PRETARGET4:
if(c != '\r' && c != '\n') {
parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET;
parser->item_offset = finfo->b_used - 1;
parser->item_offset = len - 1;
parser->item_length = 1;
}
else {
@@ -872,7 +859,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL;
}
else if(c == '\n') {
finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
mem[parser->item_offset + parser->item_length - 1] = 0;
parser->offsets.symlink_target = parser->item_offset;
result = ftp_pl_insert_finfo(data, infop);
if(result) {
@@ -884,7 +871,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
break;
case PL_UNIX_SYMLINK_WINDOWSEOL:
if(c == '\n') {
finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
mem[parser->item_offset + parser->item_length - 1] = 0;
parser->offsets.symlink_target = parser->item_offset;
result = ftp_pl_insert_finfo(data, infop);
if(result) {
@@ -938,7 +925,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
case PL_WINNT_TIME_TIME:
if(c == ' ') {
parser->offsets.time = parser->item_offset;
finfo->b_data[parser->item_offset + parser->item_length -1] = 0;
mem[parser->item_offset + parser->item_length -1] = 0;
parser->state.NT.main = PL_WINNT_DIRORSIZE;
parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE;
parser->item_length = 0;
@@ -954,7 +941,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
switch(parser->state.NT.sub.dirorsize) {
case PL_WINNT_DIRORSIZE_PRESPACE:
if(c != ' ') {
parser->item_offset = finfo->b_used - 1;
parser->item_offset = len - 1;
parser->item_length = 1;
parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT;
}
@@ -962,14 +949,14 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
case PL_WINNT_DIRORSIZE_CONTENT:
parser->item_length ++;
if(c == ' ') {
finfo->b_data[parser->item_offset + parser->item_length - 1] = 0;
if(strcmp("<DIR>", finfo->b_data + parser->item_offset) == 0) {
mem[parser->item_offset + parser->item_length - 1] = 0;
if(strcmp("<DIR>", mem + parser->item_offset) == 0) {
finfo->filetype = CURLFILETYPE_DIRECTORY;
finfo->size = 0;
}
else {
char *endptr;
if(curlx_strtoofft(finfo->b_data +
if(curlx_strtoofft(mem +
parser->item_offset,
&endptr, 10, &finfo->size)) {
parser->error = CURLE_FTP_BAD_FILE_LIST;
@@ -991,7 +978,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
switch(parser->state.NT.sub.filename) {
case PL_WINNT_FILENAME_PRESPACE:
if(c != ' ') {
parser->item_offset = finfo->b_used -1;
parser->item_offset = len -1;
parser->item_length = 1;
parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT;
}
@@ -1000,11 +987,11 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb,
parser->item_length++;
if(c == '\r') {
parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL;
finfo->b_data[finfo->b_used - 1] = 0;
mem[len - 1] = 0;
}
else if(c == '\n') {
parser->offsets.filename = parser->item_offset;
finfo->b_data[finfo->b_used - 1] = 0;
mem[len - 1] = 0;
result = ftp_pl_insert_finfo(data, infop);
if(result) {
parser->error = result;
-316
View File
@@ -1,316 +0,0 @@
/***************************************************************************
* _ _ ____ _
* 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 "urldata.h"
#include "h2h3.h"
#include "transfer.h"
#include "sendf.h"
#include "strcase.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
/*
* Curl_pseudo_headers() creates the array with pseudo headers to be
* used in an HTTP/2 or HTTP/3 request.
*/
#if defined(USE_NGHTTP2) || defined(ENABLE_QUIC)
/* Index where :authority header field will appear in request header
field list. */
#define AUTHORITY_DST_IDX 3
/* USHRT_MAX is 65535 == 0xffff */
#define HEADER_OVERFLOW(x) \
(x.namelen > 0xffff || x.valuelen > 0xffff - x.namelen)
/*
* Check header memory for the token "trailers".
* Parse the tokens as separated by comma and surrounded by whitespace.
* Returns TRUE if found or FALSE if not.
*/
static bool contains_trailers(const char *p, size_t len)
{
const char *end = p + len;
for(;;) {
for(; p != end && (*p == ' ' || *p == '\t'); ++p)
;
if(p == end || (size_t)(end - p) < sizeof("trailers") - 1)
return FALSE;
if(strncasecompare("trailers", p, sizeof("trailers") - 1)) {
p += sizeof("trailers") - 1;
for(; p != end && (*p == ' ' || *p == '\t'); ++p)
;
if(p == end || *p == ',')
return TRUE;
}
/* skip to next token */
for(; p != end && *p != ','; ++p)
;
if(p == end)
return FALSE;
++p;
}
}
typedef enum {
/* Send header to server */
HEADERINST_FORWARD,
/* Don't send header to server */
HEADERINST_IGNORE,
/* Discard header, and replace it with "te: trailers" */
HEADERINST_TE_TRAILERS
} header_instruction;
/* Decides how to treat given header field. */
static header_instruction inspect_header(const char *name, size_t namelen,
const char *value, size_t valuelen) {
switch(namelen) {
case 2:
if(!strncasecompare("te", name, namelen))
return HEADERINST_FORWARD;
return contains_trailers(value, valuelen) ?
HEADERINST_TE_TRAILERS : HEADERINST_IGNORE;
case 7:
return strncasecompare("upgrade", name, namelen) ?
HEADERINST_IGNORE : HEADERINST_FORWARD;
case 10:
return (strncasecompare("connection", name, namelen) ||
strncasecompare("keep-alive", name, namelen)) ?
HEADERINST_IGNORE : HEADERINST_FORWARD;
case 16:
return strncasecompare("proxy-connection", name, namelen) ?
HEADERINST_IGNORE : HEADERINST_FORWARD;
case 17:
return strncasecompare("transfer-encoding", name, namelen) ?
HEADERINST_IGNORE : HEADERINST_FORWARD;
default:
return HEADERINST_FORWARD;
}
}
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;
size_t nheader = 0;
size_t i;
size_t authority_idx;
char *hdbuf = (char *)mem;
char *end, *line_end;
struct h2h3pseudo *nva = NULL;
struct h2h3req *hreq = NULL;
char *vptr;
/* Calculate number of headers contained in [mem, mem + len). Assumes a
correctly generated HTTP header field block. */
for(i = 1; i < len; ++i) {
if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') {
++nheader;
++i;
}
}
if(nheader < 2) {
goto fail;
}
/* We counted additional 2 \r\n in the first and last line. We need 3
new headers: :method, :path and :scheme. Therefore we need one
more space. */
nheader += 1;
hreq = malloc(sizeof(struct h2h3req) +
sizeof(struct h2h3pseudo) * (nheader - 1));
if(!hreq) {
goto fail;
}
nva = &hreq->header[0];
/* Extract :method, :path from request line
We do line endings with CRLF so checking for CR is enough */
line_end = memchr(hdbuf, '\r', len);
if(!line_end) {
goto fail;
}
/* Method does not contain spaces */
end = memchr(hdbuf, ' ', line_end - hdbuf);
if(!end || end == hdbuf)
goto fail;
nva[0].name = H2H3_PSEUDO_METHOD;
nva[0].namelen = sizeof(H2H3_PSEUDO_METHOD) - 1;
nva[0].value = hdbuf;
nva[0].valuelen = (size_t)(end - hdbuf);
hdbuf = end + 1;
/* Path may contain spaces so scan backwards */
end = NULL;
for(i = (size_t)(line_end - hdbuf); i; --i) {
if(hdbuf[i - 1] == ' ') {
end = &hdbuf[i - 1];
break;
}
}
if(!end || end == hdbuf)
goto fail;
nva[1].name = H2H3_PSEUDO_PATH;
nva[1].namelen = sizeof(H2H3_PSEUDO_PATH) - 1;
nva[1].value = hdbuf;
nva[1].valuelen = (end - hdbuf);
nva[2].name = H2H3_PSEUDO_SCHEME;
nva[2].namelen = sizeof(H2H3_PSEUDO_SCHEME) - 1;
vptr = Curl_checkheaders(data, STRCONST(H2H3_PSEUDO_SCHEME));
if(vptr) {
vptr += sizeof(H2H3_PSEUDO_SCHEME);
while(*vptr && ISBLANK(*vptr))
vptr++;
nva[2].value = vptr;
infof(data, "set pseudo header %s to %s", H2H3_PSEUDO_SCHEME, vptr);
}
else {
if(conn->handler->flags & PROTOPT_SSL)
nva[2].value = "https";
else
nva[2].value = "http";
}
nva[2].valuelen = strlen((char *)nva[2].value);
authority_idx = 0;
i = 3;
while(i < nheader) {
size_t hlen;
hdbuf = line_end + 2;
/* check for next CR, but only within the piece of data left in the given
buffer */
line_end = memchr(hdbuf, '\r', len - (hdbuf - (char *)mem));
if(!line_end || (line_end == hdbuf))
goto fail;
/* header continuation lines are not supported */
if(*hdbuf == ' ' || *hdbuf == '\t')
goto fail;
for(end = hdbuf; end < line_end && *end != ':'; ++end)
;
if(end == hdbuf || end == line_end)
goto fail;
hlen = end - hdbuf;
if(hlen == 4 && strncasecompare("host", hdbuf, 4)) {
authority_idx = i;
nva[i].name = H2H3_PSEUDO_AUTHORITY;
nva[i].namelen = sizeof(H2H3_PSEUDO_AUTHORITY) - 1;
}
else {
nva[i].namelen = (size_t)(end - hdbuf);
/* Lower case the header name for HTTP/3 */
Curl_strntolower((char *)hdbuf, hdbuf, nva[i].namelen);
nva[i].name = hdbuf;
}
hdbuf = end + 1;
while(*hdbuf == ' ' || *hdbuf == '\t')
++hdbuf;
end = line_end;
switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf,
end - hdbuf)) {
case HEADERINST_IGNORE:
/* skip header fields prohibited by HTTP/2 specification. */
--nheader;
continue;
case HEADERINST_TE_TRAILERS:
nva[i].value = "trailers";
nva[i].valuelen = sizeof("trailers") - 1;
break;
default:
nva[i].value = hdbuf;
nva[i].valuelen = (end - hdbuf);
}
++i;
}
/* :authority must come before non-pseudo header fields */
if(authority_idx && authority_idx != AUTHORITY_DST_IDX) {
struct h2h3pseudo authority = nva[authority_idx];
for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) {
nva[i] = nva[i - 1];
}
nva[i] = authority;
}
/* Warn stream may be rejected if cumulative length of headers is too
large. */
#define MAX_ACC 60000 /* <64KB to account for some overhead */
{
size_t acc = 0;
for(i = 0; i < nheader; ++i) {
acc += nva[i].namelen + nva[i].valuelen;
infof(data, "h2h3 [%.*s: %.*s]",
(int)nva[i].namelen, nva[i].name,
(int)nva[i].valuelen, nva[i].value);
}
if(acc > MAX_ACC) {
infof(data, "http_request: Warning: The cumulative length of all "
"headers exceeds %d bytes and that could cause the "
"stream to be rejected.", MAX_ACC);
}
}
if(hdrlen) {
/* Skip trailing CRLF */
end += 4;
*hdrlen = end - mem;
}
hreq->entries = nheader;
*hp = hreq;
return CURLE_OK;
fail:
free(hreq);
return CURLE_OUT_OF_MEMORY;
}
void Curl_pseudo_free(struct h2h3req *hp)
{
free(hp);
}
#endif /* USE_NGHTTP2 or HTTP/3 enabled */
-1
View File
@@ -330,7 +330,6 @@ Curl_hash_next_element(struct Curl_hash_iterator *iter)
struct Curl_hash_element *he = iter->current_element->ptr;
return he;
}
iter->current_element = NULL;
return NULL;
}
+2 -1
View File
@@ -325,7 +325,7 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header,
hs, &hs->node);
data->state.prevhead = hs;
return CURLE_OK;
fail:
fail:
free(hs);
return result;
}
@@ -336,6 +336,7 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header,
static void headers_init(struct Curl_easy *data)
{
Curl_llist_init(&data->state.httphdrs, NULL);
data->state.prevhead = NULL;
}
/*
+62 -21
View File
@@ -61,6 +61,7 @@
#include "doh.h"
#include "warnless.h"
#include "strcase.h"
#include "easy_lock.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
@@ -70,14 +71,19 @@
#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
#endif
#if defined(CURLRES_SYNCH) && \
defined(HAVE_ALARM) && defined(SIGALRM) && defined(HAVE_SIGSETJMP)
#if defined(CURLRES_SYNCH) && \
defined(HAVE_ALARM) && \
defined(SIGALRM) && \
defined(HAVE_SIGSETJMP) && \
defined(GLOBAL_INIT_IS_THREADSAFE)
/* alarm-based timeouts can only be used with all the dependencies satisfied */
#define USE_ALARM_TIMEOUT
#endif
#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
#define MAX_DNS_CACHE_SIZE 29999
/*
* hostip.c explained
* ==================
@@ -122,7 +128,7 @@ static void freednsentry(void *freethis);
/*
* Return # of addresses in a Curl_addrinfo struct
*/
int Curl_num_addresses(const struct Curl_addrinfo *addr)
static int num_addresses(const struct Curl_addrinfo *addr)
{
int i = 0;
while(addr) {
@@ -189,8 +195,9 @@ create_hostcache_id(const char *name,
}
struct hostcache_prune_data {
long cache_timeout;
time_t now;
time_t oldest; /* oldest time in cache not pruned. */
int cache_timeout;
};
/*
@@ -203,28 +210,40 @@ struct hostcache_prune_data {
static int
hostcache_timestamp_remove(void *datap, void *hc)
{
struct hostcache_prune_data *data =
struct hostcache_prune_data *prune =
(struct hostcache_prune_data *) datap;
struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
return (0 != c->timestamp)
&& (data->now - c->timestamp >= data->cache_timeout);
if(c->timestamp) {
/* age in seconds */
time_t age = prune->now - c->timestamp;
if(age >= prune->cache_timeout)
return TRUE;
if(age > prune->oldest)
prune->oldest = age;
}
return FALSE;
}
/*
* Prune the DNS cache. This assumes that a lock has already been taken.
* Returns the 'age' of the oldest still kept entry.
*/
static void
hostcache_prune(struct Curl_hash *hostcache, long cache_timeout, time_t now)
static time_t
hostcache_prune(struct Curl_hash *hostcache, int cache_timeout,
time_t now)
{
struct hostcache_prune_data user;
user.cache_timeout = cache_timeout;
user.now = now;
user.oldest = 0;
Curl_hash_clean_with_criterium(hostcache,
(void *) &user,
hostcache_timestamp_remove);
return user.oldest;
}
/*
@@ -234,10 +253,11 @@ hostcache_prune(struct Curl_hash *hostcache, long cache_timeout, time_t now)
void Curl_hostcache_prune(struct Curl_easy *data)
{
time_t now;
/* the timeout may be set -1 (forever) */
int timeout = data->set.dns_cache_timeout;
if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache)
/* cache forever means never prune, and NULL hostcache means
we can't do it */
if(!data->dns.hostcache)
/* NULL hostcache means we can't do it */
return;
if(data->share)
@@ -245,20 +265,29 @@ void Curl_hostcache_prune(struct Curl_easy *data)
time(&now);
/* Remove outdated and unused entries from the hostcache */
hostcache_prune(data->dns.hostcache,
data->set.dns_cache_timeout,
now);
do {
/* Remove outdated and unused entries from the hostcache */
time_t oldest = hostcache_prune(data->dns.hostcache, timeout, now);
if(oldest < INT_MAX)
timeout = (int)oldest; /* we know it fits */
else
timeout = INT_MAX - 1;
/* if the cache size is still too big, use the oldest age as new
prune limit */
} while(timeout && (data->dns.hostcache->size > MAX_DNS_CACHE_SIZE));
if(data->share)
Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
}
#ifdef HAVE_SIGSETJMP
#ifdef USE_ALARM_TIMEOUT
/* Beware this is a global and unique instance. This is used to store the
return address that we can jump back to from inside a signal handler. This
is not thread-safe stuff. */
sigjmp_buf curl_jmpenv;
static sigjmp_buf curl_jmpenv;
static curl_simple_lock curl_jmpenv_lock;
#endif
/* lookup address, returns entry if found and not stale */
@@ -290,6 +319,7 @@ static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
time(&user.now);
user.cache_timeout = data->set.dns_cache_timeout;
user.oldest = 0;
if(hostcache_timestamp_remove(&user, dns)) {
infof(data, "Hostname in DNS cache was stale, zapped");
@@ -380,7 +410,7 @@ UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
struct Curl_addrinfo **addr)
{
CURLcode result = CURLE_OK;
const int num_addrs = Curl_num_addresses(*addr);
const int num_addrs = num_addresses(*addr);
if(num_addrs > 1) {
struct Curl_addrinfo **nodes;
@@ -652,6 +682,14 @@ enum resolve_t Curl_resolv(struct Curl_easy *data,
CURLcode result;
enum resolve_t rc = CURLRESOLV_ERROR; /* default to failure */
struct connectdata *conn = data->conn;
/* We should intentionally error and not resolve .onion TLDs */
size_t hostname_len = strlen(hostname);
if(hostname_len >= 7 &&
(curl_strequal(&hostname[hostname_len - 6], ".onion") ||
curl_strequal(&hostname[hostname_len - 7], ".onion."))) {
failf(data, "Not resolving .onion address (RFC 7686)");
return CURLRESOLV_ERROR;
}
*entry = NULL;
#ifndef CURL_DISABLE_DOH
conn->bits.doh = FALSE; /* default is not */
@@ -824,7 +862,6 @@ enum resolve_t Curl_resolv(struct Curl_easy *data,
static
void alarmfunc(int sig)
{
/* this is for "-ansi -Wall -pedantic" to stop complaining! (rabe) */
(void)sig;
siglongjmp(curl_jmpenv, 1);
}
@@ -904,6 +941,8 @@ enum resolve_t Curl_resolv_timeout(struct Curl_easy *data,
This should be the last thing we do before calling Curl_resolv(),
as otherwise we'd have to worry about variables that get modified
before we invoke Curl_resolv() (and thus use "volatile"). */
curl_simple_lock_lock(&curl_jmpenv_lock);
if(sigsetjmp(curl_jmpenv, 1)) {
/* this is coming from a siglongjmp() after an alarm signal */
failf(data, "name lookup timed out");
@@ -972,6 +1011,8 @@ clean_up:
#endif
#endif /* HAVE_SIGACTION */
curl_simple_lock_unlock(&curl_jmpenv_lock);
/* switch back the alarm() to either zero or to what it was before minus
the time we spent until now! */
if(prev_alarm) {
@@ -1196,7 +1237,7 @@ CURLcode Curl_loadhostpairs(struct Curl_easy *data)
goto err;
error = false;
err:
err:
if(error) {
failf(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'",
hostp->data);
-12
View File
@@ -132,9 +132,6 @@ void Curl_init_dnscache(struct Curl_hash *hash, int hashsize);
/* prune old entries from the DNS cache */
void Curl_hostcache_prune(struct Curl_easy *data);
/* Return # of addresses in a Curl_addrinfo struct */
int Curl_num_addresses(const struct Curl_addrinfo *addr);
/* IPv4 threadsafe resolve function used for synch and asynch builds */
struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port);
@@ -186,15 +183,6 @@ Curl_cache_addr(struct Curl_easy *data, struct Curl_addrinfo *addr,
#define CURL_INADDR_NONE INADDR_NONE
#endif
#ifdef HAVE_SIGSETJMP
/* Forward-declaration of variable defined in hostip.c. Beware this
* is a global and unique instance. This is used to store the return
* address that we can jump back to from inside a signal handler.
* This is not thread-safe stuff.
*/
extern sigjmp_buf curl_jmpenv;
#endif
/*
* Function provided by the resolver backend to set DNS servers to use.
*/
+3 -3
View File
@@ -204,7 +204,7 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname,
p++;
if(*p == ';')
p++;
} while (*p);
} while(*p);
if(!gotma)
/* max-age is mandatory */
@@ -390,7 +390,7 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h,
unlink(tempstore);
}
free(tempstore);
skipsave:
skipsave:
if(data->set.hsts_write) {
/* if there's a write callback */
struct curl_index i; /* count */
@@ -534,7 +534,7 @@ static CURLcode hsts_load(struct hsts *h, const char *file)
}
return result;
fail:
fail:
Curl_safefree(h->filename);
fclose(fp);
return CURLE_OUT_OF_MEMORY;
+563 -26
View File
@@ -71,6 +71,7 @@
#include "url.h"
#include "share.h"
#include "hostip.h"
#include "dynhds.h"
#include "http.h"
#include "select.h"
#include "parsedate.h" /* for the week day and month names */
@@ -397,7 +398,7 @@ static CURLcode http_output_basic(struct Curl_easy *data, bool proxy)
goto fail;
}
fail:
fail:
free(out);
return result;
}
@@ -423,7 +424,7 @@ static CURLcode http_output_bearer(struct Curl_easy *data)
goto fail;
}
fail:
fail:
return result;
}
@@ -1009,7 +1010,7 @@ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy,
if(authp->picked == CURLAUTH_NEGOTIATE) {
CURLcode result = Curl_input_negotiate(data, conn, proxy, auth);
if(!result) {
DEBUGASSERT(!data->req.newurl);
free(data->req.newurl);
data->req.newurl = strdup(data->state.url);
if(!data->req.newurl)
return CURLE_OUT_OF_MEMORY;
@@ -1304,7 +1305,7 @@ CURLcode Curl_buffer_send(struct dynbuf *in,
if((conn->handler->flags & PROTOPT_SSL
#ifndef CURL_DISABLE_PROXY
|| conn->http_proxy.proxytype == CURLPROXY_HTTPS
|| IS_HTTPS_PROXY(conn->http_proxy.proxytype)
#endif
)
&& conn->httpversion != 20) {
@@ -1713,6 +1714,157 @@ CURLcode Curl_http_compile_trailers(struct curl_slist *trailers,
return result;
}
static bool hd_name_eq(const char *n1, size_t n1len,
const char *n2, size_t n2len)
{
if(n1len == n2len) {
return strncasecompare(n1, n2, n1len);
}
return FALSE;
}
CURLcode Curl_dynhds_add_custom(struct Curl_easy *data,
bool is_connect,
struct dynhds *hds)
{
struct connectdata *conn = data->conn;
char *ptr;
struct curl_slist *h[2];
struct curl_slist *headers;
int numlists = 1; /* by default */
int i;
#ifndef CURL_DISABLE_PROXY
enum proxy_use proxy;
if(is_connect)
proxy = HEADER_CONNECT;
else
proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy?
HEADER_PROXY:HEADER_SERVER;
switch(proxy) {
case HEADER_SERVER:
h[0] = data->set.headers;
break;
case HEADER_PROXY:
h[0] = data->set.headers;
if(data->set.sep_headers) {
h[1] = data->set.proxyheaders;
numlists++;
}
break;
case HEADER_CONNECT:
if(data->set.sep_headers)
h[0] = data->set.proxyheaders;
else
h[0] = data->set.headers;
break;
}
#else
(void)is_connect;
h[0] = data->set.headers;
#endif
/* loop through one or two lists */
for(i = 0; i < numlists; i++) {
for(headers = h[i]; headers; headers = headers->next) {
const char *name, *value;
size_t namelen, valuelen;
/* There are 2 quirks in place for custom headers:
* 1. setting only 'name:' to suppress a header from being sent
* 2. setting only 'name;' to send an empty (illegal) header
*/
ptr = strchr(headers->data, ':');
if(ptr) {
name = headers->data;
namelen = ptr - headers->data;
ptr++; /* pass the colon */
while(*ptr && ISSPACE(*ptr))
ptr++;
if(*ptr) {
value = ptr;
valuelen = strlen(value);
}
else {
/* quirk #1, suppress this header */
continue;
}
}
else {
ptr = strchr(headers->data, ';');
if(!ptr) {
/* neither : nor ; in provided header value. We seem
* to ignore this silently */
continue;
}
name = headers->data;
namelen = ptr - headers->data;
ptr++; /* pass the semicolon */
while(*ptr && ISSPACE(*ptr))
ptr++;
if(!*ptr) {
/* quirk #2, send an empty header */
value = "";
valuelen = 0;
}
else {
/* this may be used for something else in the future,
* ignore this for now */
continue;
}
}
DEBUGASSERT(name && value);
if(data->state.aptr.host &&
/* a Host: header was sent already, don't pass on any custom Host:
header as that will produce *two* in the same request! */
hd_name_eq(name, namelen, STRCONST("Host:")))
;
else if(data->state.httpreq == HTTPREQ_POST_FORM &&
/* this header (extended by formdata.c) is sent later */
hd_name_eq(name, namelen, STRCONST("Content-Type:")))
;
else if(data->state.httpreq == HTTPREQ_POST_MIME &&
/* this header is sent later */
hd_name_eq(name, namelen, STRCONST("Content-Type:")))
;
else if(conn->bits.authneg &&
/* while doing auth neg, don't allow the custom length since
we will force length zero then */
hd_name_eq(name, namelen, STRCONST("Content-Length:")))
;
else if(data->state.aptr.te &&
/* when asking for Transfer-Encoding, don't pass on a custom
Connection: */
hd_name_eq(name, namelen, STRCONST("Connection:")))
;
else if((conn->httpversion >= 20) &&
hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:")))
/* HTTP/2 doesn't support chunked requests */
;
else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) ||
hd_name_eq(name, namelen, STRCONST("Cookie:"))) &&
/* be careful of sending this potentially sensitive header to
other hosts */
!Curl_auth_allowed_to_host(data))
;
else {
CURLcode result;
result = Curl_dynhds_add(hds, name, namelen, value, valuelen);
if(result)
return result;
}
}
}
return CURLE_OK;
}
CURLcode Curl_add_custom_headers(struct Curl_easy *data,
bool is_connect,
#ifndef USE_HYPER
@@ -1960,7 +2112,7 @@ void Curl_http_method(struct Curl_easy *data, struct connectdata *conn,
Curl_HttpReq httpreq = (Curl_HttpReq)data->state.httpreq;
const char *request;
if((conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_FTP)) &&
data->set.upload)
data->state.upload)
httpreq = HTTPREQ_PUT;
/* Now set the 'request' pointer to the proper request string */
@@ -2011,6 +2163,7 @@ CURLcode Curl_http_useragent(struct Curl_easy *data)
CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn)
{
const char *ptr;
struct dynamically_allocated_data *aptr = &data->state.aptr;
if(!data->state.this_is_a_follow) {
/* Free to avoid leaking memory on multiple requests */
free(data->state.first_host);
@@ -2022,7 +2175,7 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn)
data->state.first_remote_port = conn->remote_port;
data->state.first_remote_protocol = conn->handler->protocol;
}
Curl_safefree(data->state.aptr.host);
Curl_safefree(aptr->host);
ptr = Curl_checkheaders(data, STRCONST("Host"));
if(ptr && (!data->state.this_is_a_follow ||
@@ -2057,19 +2210,16 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn)
if(colon)
*colon = 0; /* The host must not include an embedded port number */
}
Curl_safefree(data->state.aptr.cookiehost);
data->state.aptr.cookiehost = cookiehost;
Curl_safefree(aptr->cookiehost);
aptr->cookiehost = cookiehost;
}
#endif
if(strcmp("Host:", ptr)) {
data->state.aptr.host = aprintf("Host:%s\r\n", &ptr[5]);
if(!data->state.aptr.host)
aptr->host = aprintf("Host:%s\r\n", &ptr[5]);
if(!aptr->host)
return CURLE_OUT_OF_MEMORY;
}
else
/* when clearing the header */
data->state.aptr.host = NULL;
}
else {
/* When building Host: headers, we must put the host name within
@@ -2082,18 +2232,14 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn)
(conn->remote_port == PORT_HTTP)) )
/* if(HTTPS on port 443) OR (HTTP on port 80) then don't include
the port number in the host string */
data->state.aptr.host = aprintf("Host: %s%s%s\r\n",
conn->bits.ipv6_ip?"[":"",
host,
conn->bits.ipv6_ip?"]":"");
aptr->host = aprintf("Host: %s%s%s\r\n", conn->bits.ipv6_ip?"[":"",
host, conn->bits.ipv6_ip?"]":"");
else
data->state.aptr.host = aprintf("Host: %s%s%s:%d\r\n",
conn->bits.ipv6_ip?"[":"",
host,
conn->bits.ipv6_ip?"]":"",
conn->remote_port);
aptr->host = aprintf("Host: %s%s%s:%d\r\n", conn->bits.ipv6_ip?"[":"",
host, conn->bits.ipv6_ip?"]":"",
conn->remote_port);
if(!data->state.aptr.host)
if(!aptr->host)
/* without Host: we can't make a nice request */
return CURLE_OUT_OF_MEMORY;
}
@@ -2277,7 +2423,7 @@ CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn,
if((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
(((httpreq == HTTPREQ_POST_MIME || httpreq == HTTPREQ_POST_FORM) &&
http->postsize < 0) ||
((data->set.upload || httpreq == HTTPREQ_POST) &&
((data->state.upload || httpreq == HTTPREQ_POST) &&
data->state.infilesize == -1))) {
if(conn->bits.authneg)
/* don't enable chunked during auth neg */
@@ -2990,7 +3136,17 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
DEBUGASSERT(Curl_conn_is_http3(data, conn, FIRSTSOCKET));
break;
case CURL_HTTP_VERSION_2:
DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET));
#ifndef CURL_DISABLE_PROXY
if(!Curl_conn_is_http2(data, conn, FIRSTSOCKET) &&
conn->bits.proxy && !conn->bits.tunnel_proxy
) {
result = Curl_http2_switch(data, conn, FIRSTSOCKET);
if(result)
return result;
}
else
#endif
DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET));
break;
case CURL_HTTP_VERSION_1_1:
/* continue with HTTP/1.1 when explicitly requested */
@@ -3420,7 +3576,7 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn,
TRUE);
if(result)
return result;
if(!k->chunk) {
if(!k->chunk && data->set.http_transfer_encoding) {
/* if this isn't chunked, only close can signal the end of this transfer
as Content-Length is said not to be trusted for transfer-encoding! */
connclose(conn, "HTTP/1.1 transfer-encoding without chunks");
@@ -4344,4 +4500,385 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
return CURLE_OK;
}
/* Decode HTTP status code string. */
CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len)
{
CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT;
int status = 0;
int i;
if(len != 3)
goto out;
for(i = 0; i < 3; ++i) {
char c = s[i];
if(c < '0' || c > '9')
goto out;
status *= 10;
status += c - '0';
}
result = CURLE_OK;
out:
*pstatus = result? -1 : status;
return result;
}
/* simple implementation of strndup(), which isn't portable */
static char *my_strndup(const char *ptr, size_t len)
{
char *copy = malloc(len + 1);
if(!copy)
return NULL;
memcpy(copy, ptr, len);
copy[len] = '\0';
return copy;
}
CURLcode Curl_http_req_make(struct httpreq **preq,
const char *method, size_t m_len,
const char *scheme, size_t s_len,
const char *authority, size_t a_len,
const char *path, size_t p_len)
{
struct httpreq *req;
CURLcode result = CURLE_OUT_OF_MEMORY;
DEBUGASSERT(method);
if(m_len + 1 >= sizeof(req->method))
return CURLE_BAD_FUNCTION_ARGUMENT;
req = calloc(1, sizeof(*req));
if(!req)
goto out;
memcpy(req->method, method, m_len);
if(scheme) {
req->scheme = my_strndup(scheme, s_len);
if(!req->scheme)
goto out;
}
if(authority) {
req->authority = my_strndup(authority, a_len);
if(!req->authority)
goto out;
}
if(path) {
req->path = my_strndup(path, p_len);
if(!req->path)
goto out;
}
Curl_dynhds_init(&req->headers, 0, DYN_H2_HEADERS);
Curl_dynhds_init(&req->trailers, 0, DYN_H2_TRAILERS);
result = CURLE_OK;
out:
if(result && req)
Curl_http_req_free(req);
*preq = result? NULL : req;
return result;
}
static CURLcode req_assign_url_authority(struct httpreq *req, CURLU *url)
{
char *user, *pass, *host, *port;
struct dynbuf buf;
CURLUcode uc;
CURLcode result = CURLE_URL_MALFORMAT;
user = pass = host = port = NULL;
Curl_dyn_init(&buf, DYN_HTTP_REQUEST);
uc = curl_url_get(url, CURLUPART_HOST, &host, 0);
if(uc && uc != CURLUE_NO_HOST)
goto out;
if(!host) {
req->authority = NULL;
result = CURLE_OK;
goto out;
}
uc = curl_url_get(url, CURLUPART_PORT, &port, CURLU_NO_DEFAULT_PORT);
if(uc && uc != CURLUE_NO_PORT)
goto out;
uc = curl_url_get(url, CURLUPART_USER, &user, 0);
if(uc && uc != CURLUE_NO_USER)
goto out;
if(user) {
uc = curl_url_get(url, CURLUPART_PASSWORD, &pass, 0);
if(uc && uc != CURLUE_NO_PASSWORD)
goto out;
}
if(user) {
result = Curl_dyn_add(&buf, user);
if(result)
goto out;
if(pass) {
result = Curl_dyn_addf(&buf, ":%s", pass);
if(result)
goto out;
}
result = Curl_dyn_add(&buf, "@");
if(result)
goto out;
}
result = Curl_dyn_add(&buf, host);
if(result)
goto out;
if(port) {
result = Curl_dyn_addf(&buf, ":%s", port);
if(result)
goto out;
}
req->authority = strdup(Curl_dyn_ptr(&buf));
if(!req->authority)
goto out;
result = CURLE_OK;
out:
free(user);
free(pass);
free(host);
free(port);
Curl_dyn_free(&buf);
return result;
}
static CURLcode req_assign_url_path(struct httpreq *req, CURLU *url)
{
char *path, *query;
struct dynbuf buf;
CURLUcode uc;
CURLcode result = CURLE_URL_MALFORMAT;
path = query = NULL;
Curl_dyn_init(&buf, DYN_HTTP_REQUEST);
uc = curl_url_get(url, CURLUPART_PATH, &path, CURLU_PATH_AS_IS);
if(uc)
goto out;
uc = curl_url_get(url, CURLUPART_QUERY, &query, 0);
if(uc && uc != CURLUE_NO_QUERY)
goto out;
if(!path && !query) {
req->path = NULL;
}
else if(path && !query) {
req->path = path;
path = NULL;
}
else {
if(path) {
result = Curl_dyn_add(&buf, path);
if(result)
goto out;
}
if(query) {
result = Curl_dyn_addf(&buf, "?%s", query);
if(result)
goto out;
}
req->path = strdup(Curl_dyn_ptr(&buf));
if(!req->path)
goto out;
}
result = CURLE_OK;
out:
free(path);
free(query);
Curl_dyn_free(&buf);
return result;
}
CURLcode Curl_http_req_make2(struct httpreq **preq,
const char *method, size_t m_len,
CURLU *url, const char *scheme_default)
{
struct httpreq *req;
CURLcode result = CURLE_OUT_OF_MEMORY;
CURLUcode uc;
DEBUGASSERT(method);
if(m_len + 1 >= sizeof(req->method))
return CURLE_BAD_FUNCTION_ARGUMENT;
req = calloc(1, sizeof(*req));
if(!req)
goto out;
memcpy(req->method, method, m_len);
uc = curl_url_get(url, CURLUPART_SCHEME, &req->scheme, 0);
if(uc && uc != CURLUE_NO_SCHEME)
goto out;
if(!req->scheme && scheme_default) {
req->scheme = strdup(scheme_default);
if(!req->scheme)
goto out;
}
result = req_assign_url_authority(req, url);
if(result)
goto out;
result = req_assign_url_path(req, url);
if(result)
goto out;
Curl_dynhds_init(&req->headers, 0, DYN_H2_HEADERS);
Curl_dynhds_init(&req->trailers, 0, DYN_H2_TRAILERS);
result = CURLE_OK;
out:
if(result && req)
Curl_http_req_free(req);
*preq = result? NULL : req;
return result;
}
void Curl_http_req_free(struct httpreq *req)
{
if(req) {
free(req->scheme);
free(req->authority);
free(req->path);
Curl_dynhds_free(&req->headers);
Curl_dynhds_free(&req->trailers);
free(req);
}
}
struct name_const {
const char *name;
size_t namelen;
};
static struct name_const H2_NON_FIELD[] = {
{ STRCONST("Host") },
{ STRCONST("Upgrade") },
{ STRCONST("Connection") },
{ STRCONST("Keep-Alive") },
{ STRCONST("Proxy-Connection") },
{ STRCONST("Transfer-Encoding") },
};
static bool h2_non_field(const char *name, size_t namelen)
{
size_t i;
for(i = 0; i < sizeof(H2_NON_FIELD)/sizeof(H2_NON_FIELD[0]); ++i) {
if(namelen < H2_NON_FIELD[i].namelen)
return FALSE;
if(namelen == H2_NON_FIELD[i].namelen &&
strcasecompare(H2_NON_FIELD[i].name, name))
return TRUE;
}
return FALSE;
}
CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers,
struct httpreq *req, struct Curl_easy *data)
{
const char *scheme = NULL, *authority = NULL;
struct dynhds_entry *e;
size_t i;
CURLcode result;
DEBUGASSERT(req);
DEBUGASSERT(h2_headers);
if(req->scheme) {
scheme = req->scheme;
}
else if(strcmp("CONNECT", req->method)) {
scheme = Curl_checkheaders(data, STRCONST(HTTP_PSEUDO_SCHEME));
if(scheme) {
scheme += sizeof(HTTP_PSEUDO_SCHEME);
while(*scheme && ISBLANK(*scheme))
scheme++;
infof(data, "set pseudo header %s to %s", HTTP_PSEUDO_SCHEME, scheme);
}
else {
scheme = (data->conn && data->conn->handler->flags & PROTOPT_SSL)?
"https" : "http";
}
}
if(req->authority) {
authority = req->authority;
}
else {
e = Curl_dynhds_get(&req->headers, STRCONST("Host"));
if(e)
authority = e->value;
}
Curl_dynhds_reset(h2_headers);
Curl_dynhds_set_opts(h2_headers, DYNHDS_OPT_LOWERCASE);
result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_METHOD),
req->method, strlen(req->method));
if(!result && scheme) {
result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_SCHEME),
scheme, strlen(scheme));
}
if(!result && authority) {
result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_AUTHORITY),
authority, strlen(authority));
}
if(!result && req->path) {
result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_PATH),
req->path, strlen(req->path));
}
for(i = 0; !result && i < Curl_dynhds_count(&req->headers); ++i) {
e = Curl_dynhds_getn(&req->headers, i);
if(!h2_non_field(e->name, e->namelen)) {
result = Curl_dynhds_add(h2_headers, e->name, e->namelen,
e->value, e->valuelen);
}
}
return result;
}
CURLcode Curl_http_resp_make(struct http_resp **presp,
int status,
const char *description)
{
struct http_resp *resp;
CURLcode result = CURLE_OUT_OF_MEMORY;
resp = calloc(1, sizeof(*resp));
if(!resp)
goto out;
resp->status = status;
if(description) {
resp->description = strdup(description);
if(!resp->description)
goto out;
}
Curl_dynhds_init(&resp->headers, 0, DYN_H2_HEADERS);
Curl_dynhds_init(&resp->trailers, 0, DYN_H2_TRAILERS);
result = CURLE_OK;
out:
if(result && resp)
Curl_http_resp_free(resp);
*presp = result? NULL : resp;
return result;
}
void Curl_http_resp_free(struct http_resp *resp)
{
if(resp) {
free(resp->description);
Curl_dynhds_free(&resp->headers);
Curl_dynhds_free(&resp->trailers);
if(resp->prev)
Curl_http_resp_free(resp->prev);
free(resp);
}
}
#endif /* CURL_DISABLE_HTTP */
+85 -85
View File
@@ -29,6 +29,8 @@
#include <pthread.h>
#endif
#include "bufq.h"
#include "dynhds.h"
#include "ws.h"
typedef enum {
@@ -42,7 +44,7 @@ typedef enum {
#ifndef CURL_DISABLE_HTTP
#if defined(ENABLE_QUIC) || defined(USE_NGHTTP2)
#if defined(ENABLE_QUIC)
#include <stdint.h>
#endif
@@ -60,6 +62,7 @@ extern const struct Curl_handler Curl_handler_wss;
#endif
#endif /* websockets */
struct dynhds;
/* Header specific functions */
bool Curl_compareheader(const char *headerline, /* line to check */
@@ -97,6 +100,10 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
void *headers
#endif
);
CURLcode Curl_dynhds_add_custom(struct Curl_easy *data,
bool is_connect,
struct dynhds *hds);
CURLcode Curl_http_compile_trailers(struct curl_slist *trailers,
struct dynbuf *buf,
struct Curl_easy *handle);
@@ -178,10 +185,6 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data);
#endif /* CURL_DISABLE_HTTP */
#ifdef USE_NGHTTP3
struct h3out; /* see ngtcp2 */
#endif
/****************************************************************************
* HTTP unique setup
***************************************************************************/
@@ -209,91 +212,13 @@ struct HTTP {
HTTPSEND_BODY /* sending body */
} sending;
#ifdef USE_WEBSOCKETS
struct websocket ws;
#endif
#ifndef CURL_DISABLE_HTTP
void *h2_ctx; /* HTTP/2 implementation context */
void *h3_ctx; /* HTTP/3 implementation context */
struct dynbuf send_buffer; /* used if the request couldn't be sent in one
chunk, points to an allocated send_buffer
struct */
#endif
#ifdef USE_NGHTTP2
/*********** for HTTP/2 we store stream-local data here *************/
int32_t stream_id; /* stream we are interested in */
/* We store non-final and final response headers here, per-stream */
struct dynbuf header_recvbuf;
size_t nread_header_recvbuf; /* number of bytes in header_recvbuf fed into
upper layer */
struct dynbuf trailer_recvbuf;
const uint8_t *pausedata; /* pointer to data received in on_data_chunk */
size_t pauselen; /* the number of bytes left in data */
bool close_handled; /* TRUE if stream closure is handled by libcurl */
char **push_headers; /* allocated array */
size_t push_headers_used; /* number of entries filled in */
size_t push_headers_alloc; /* number of entries allocated */
uint32_t error; /* HTTP/2 stream error code */
#endif
#if defined(USE_NGHTTP2) || defined(USE_NGHTTP3)
bool bodystarted;
int status_code; /* HTTP status code */
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 */
#endif
#if defined(USE_NGHTTP2) || defined(ENABLE_QUIC)
/* fields used by both HTTP/2 and HTTP/3 */
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
#ifndef USE_MSH3
/*********** for HTTP/3 we store stream-local data here *************/
int64_t stream3_id; /* stream we are interested in */
uint64_t error3; /* HTTP/3 stream error code */
bool firstheader; /* FALSE until headers arrive */
bool firstbody; /* FALSE until body arrives */
bool h3req; /* FALSE until request is issued */
#endif /* !USE_MSH3 */
bool upload_done;
#endif /* ENABLE_QUIC */
#ifdef USE_NGHTTP3
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 /* USE_NGHTTP3 */
#ifdef USE_MSH3
struct MSH3_REQUEST *req;
#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;
/* Receive Data */
size_t recv_data_len;
bool recv_data_complete;
/* General Receive Error */
CURLcode recv_error;
#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);
@@ -328,4 +253,79 @@ Curl_http_output_auth(struct Curl_easy *data,
bool proxytunnel); /* TRUE if this is the request setting
up the proxy tunnel */
/* Decode HTTP status code string. */
CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len);
/**
* All about a core HTTP request, excluding body and trailers
*/
struct httpreq {
char method[12];
char *scheme;
char *authority;
char *path;
struct dynhds headers;
struct dynhds trailers;
};
/**
* Create a HTTP request struct.
*/
CURLcode Curl_http_req_make(struct httpreq **preq,
const char *method, size_t m_len,
const char *scheme, size_t s_len,
const char *authority, size_t a_len,
const char *path, size_t p_len);
CURLcode Curl_http_req_make2(struct httpreq **preq,
const char *method, size_t m_len,
CURLU *url, const char *scheme_default);
void Curl_http_req_free(struct httpreq *req);
#define HTTP_PSEUDO_METHOD ":method"
#define HTTP_PSEUDO_SCHEME ":scheme"
#define HTTP_PSEUDO_AUTHORITY ":authority"
#define HTTP_PSEUDO_PATH ":path"
#define HTTP_PSEUDO_STATUS ":status"
/**
* Create the list of HTTP/2 headers which represent the request,
* using HTTP/2 pseudo headers preceeding the `req->headers`.
*
* Applies the following transformations:
* - if `authority` is set, any "Host" header is removed.
* - if `authority` is unset and a "Host" header is present, use
* that as `authority` and remove "Host"
* - removes and Connection header fields as defined in rfc9113 ch. 8.2.2
* - lower-cases the header field names
*
* @param h2_headers will contain the HTTP/2 headers on success
* @param req the request to transform
* @param data the handle to lookup defaults like ' :scheme' from
*/
CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers,
struct httpreq *req, struct Curl_easy *data);
/**
* All about a core HTTP response, excluding body and trailers
*/
struct http_resp {
int status;
char *description;
struct dynhds headers;
struct dynhds trailers;
struct http_resp *prev;
};
/**
* Create a HTTP response struct.
*/
CURLcode Curl_http_resp_make(struct http_resp **presp,
int status,
const char *description);
void Curl_http_resp_free(struct http_resp *resp);
#endif /* HEADER_CURL_HTTP_H */
+349
View File
@@ -0,0 +1,349 @@
/***************************************************************************
* _ _ ____ _
* 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"
#ifndef CURL_DISABLE_HTTP
#include "urldata.h"
#include <curl/curl.h>
#include "http.h"
#include "http1.h"
#include "urlapi-int.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
#define MAX_URL_LEN (4*1024)
void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len)
{
memset(parser, 0, sizeof(*parser));
parser->max_line_len = max_line_len;
Curl_bufq_init(&parser->scratch, max_line_len, 1);
}
void Curl_h1_req_parse_free(struct h1_req_parser *parser)
{
if(parser) {
Curl_http_req_free(parser->req);
Curl_bufq_free(&parser->scratch);
parser->req = NULL;
parser->done = FALSE;
}
}
static ssize_t detect_line(struct h1_req_parser *parser,
const char *buf, const size_t buflen, int options,
CURLcode *err)
{
const char *line_end;
size_t len;
DEBUGASSERT(!parser->line);
line_end = memchr(buf, '\n', buflen);
if(!line_end) {
*err = (buflen > parser->max_line_len)? CURLE_URL_MALFORMAT : CURLE_AGAIN;
return -1;
}
len = line_end - buf + 1;
if(len > parser->max_line_len) {
*err = CURLE_URL_MALFORMAT;
return -1;
}
if(options & H1_PARSE_OPT_STRICT) {
if((len == 1) || (buf[len - 2] != '\r')) {
*err = CURLE_URL_MALFORMAT;
return -1;
}
parser->line = buf;
parser->line_len = len - 2;
}
else {
parser->line = buf;
parser->line_len = len - (((len == 1) || (buf[len - 2] != '\r'))? 1 : 2);
}
*err = CURLE_OK;
return (ssize_t)len;
}
static ssize_t next_line(struct h1_req_parser *parser,
const char *buf, const size_t buflen, int options,
CURLcode *err)
{
ssize_t nread = 0, n;
if(parser->line) {
if(parser->scratch_skip) {
/* last line was from scratch. Remove it now, since we are done
* with it and look for the next one. */
Curl_bufq_skip_and_shift(&parser->scratch, parser->scratch_skip);
parser->scratch_skip = 0;
}
parser->line = NULL;
parser->line_len = 0;
}
if(Curl_bufq_is_empty(&parser->scratch)) {
nread = detect_line(parser, buf, buflen, options, err);
if(nread < 0) {
if(*err != CURLE_AGAIN)
return -1;
/* not a complete line, add to scratch for later revisit */
nread = Curl_bufq_write(&parser->scratch,
(const unsigned char *)buf, buflen, err);
return nread;
}
/* found one */
}
else {
const char *sbuf;
size_t sbuflen;
/* scratch contains bytes from last attempt, add more to it */
if(buflen) {
const char *line_end;
size_t add_len;
ssize_t pos;
line_end = memchr(buf, '\n', buflen);
pos = line_end? (line_end - buf + 1) : -1;
add_len = (pos >= 0)? (size_t)pos : buflen;
nread = Curl_bufq_write(&parser->scratch,
(const unsigned char *)buf, add_len, err);
if(nread < 0) {
/* Unable to add anything to scratch is an error, since we should
* have seen a line there then before. */
if(*err == CURLE_AGAIN)
*err = CURLE_URL_MALFORMAT;
return -1;
}
}
if(Curl_bufq_peek(&parser->scratch,
(const unsigned char **)&sbuf, &sbuflen)) {
n = detect_line(parser, sbuf, sbuflen, options, err);
if(n < 0 && *err != CURLE_AGAIN)
return -1; /* real error */
parser->scratch_skip = (size_t)n;
}
else {
/* we SHOULD be able to peek at scratch data */
DEBUGASSERT(0);
}
}
return nread;
}
static CURLcode start_req(struct h1_req_parser *parser,
const char *scheme_default, int options)
{
const char *p, *m, *target, *hv, *scheme, *authority, *path;
size_t m_len, target_len, hv_len, scheme_len, authority_len, path_len;
size_t i;
CURLU *url = NULL;
CURLcode result = CURLE_URL_MALFORMAT; /* Use this as default fail */
DEBUGASSERT(!parser->req);
/* line must match: "METHOD TARGET HTTP_VERSION" */
p = memchr(parser->line, ' ', parser->line_len);
if(!p || p == parser->line)
goto out;
m = parser->line;
m_len = p - parser->line;
target = p + 1;
target_len = hv_len = 0;
hv = NULL;
/* URL may contain spaces so scan backwards */
for(i = parser->line_len; i > m_len; --i) {
if(parser->line[i] == ' ') {
hv = &parser->line[i + 1];
hv_len = parser->line_len - i;
target_len = (hv - target) - 1;
break;
}
}
/* no SPACE found or empty TARGET or empy HTTP_VERSION */
if(!target_len || !hv_len)
goto out;
/* TODO: we do not check HTTP_VERSION for conformity, should
+ do that when STRICT option is supplied. */
(void)hv;
/* The TARGET can be (rfc 9112, ch. 3.2):
* origin-form: path + optional query
* absolute-form: absolute URI
* authority-form: host+port for CONNECT
* asterisk-form: '*' for OPTIONS
*
* from TARGET, we derive `scheme` `authority` `path`
* origin-form -- -- TARGET
* absolute-form URL* URL* URL*
* authority-form -- TARGET --
* asterisk-form -- -- TARGET
*/
scheme = authority = path = NULL;
scheme_len = authority_len = path_len = 0;
if(target_len == 1 && target[0] == '*') {
/* asterisk-form */
path = target;
path_len = target_len;
}
else if(!strncmp("CONNECT", m, m_len)) {
/* authority-form */
authority = target;
authority_len = target_len;
}
else if(target[0] == '/') {
/* origin-form */
path = target;
path_len = target_len;
}
else {
/* origin-form OR absolute-form */
CURLUcode uc;
char tmp[MAX_URL_LEN];
/* default, unless we see an absolute URL */
path = target;
path_len = target_len;
/* URL parser wants 0-termination */
if(target_len >= sizeof(tmp))
goto out;
memcpy(tmp, target, target_len);
tmp[target_len] = '\0';
/* See if treating TARGET as an absolute URL makes sense */
if(Curl_is_absolute_url(tmp, NULL, 0, FALSE)) {
int url_options;
url = curl_url();
if(!url) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
url_options = (CURLU_NON_SUPPORT_SCHEME|
CURLU_PATH_AS_IS|
CURLU_NO_DEFAULT_PORT);
if(!(options & H1_PARSE_OPT_STRICT))
url_options |= CURLU_ALLOW_SPACE;
uc = curl_url_set(url, CURLUPART_URL, tmp, url_options);
if(uc) {
goto out;
}
}
if(!url && (options & H1_PARSE_OPT_STRICT)) {
/* we should have an absolute URL or have seen `/` earlier */
goto out;
}
}
if(url) {
result = Curl_http_req_make2(&parser->req, m, m_len, url, scheme_default);
}
else {
if(!scheme && scheme_default) {
scheme = scheme_default;
scheme_len = strlen(scheme_default);
}
result = Curl_http_req_make(&parser->req, m, m_len, scheme, scheme_len,
authority, authority_len, path, path_len);
}
out:
curl_url_cleanup(url);
return result;
}
ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser,
const char *buf, size_t buflen,
const char *scheme_default, int options,
CURLcode *err)
{
ssize_t nread = 0, n;
*err = CURLE_OK;
while(!parser->done) {
n = next_line(parser, buf, buflen, options, err);
if(n < 0) {
if(*err != CURLE_AGAIN) {
nread = -1;
}
*err = CURLE_OK;
goto out;
}
/* Consume this line */
nread += (size_t)n;
buf += (size_t)n;
buflen -= (size_t)n;
if(!parser->line) {
/* consumed bytes, but line not complete */
if(!buflen)
goto out;
}
else if(!parser->req) {
*err = start_req(parser, scheme_default, options);
if(*err) {
nread = -1;
goto out;
}
}
else if(parser->line_len == 0) {
/* last, empty line, we are finished */
if(!parser->req) {
*err = CURLE_URL_MALFORMAT;
nread = -1;
goto out;
}
parser->done = TRUE;
Curl_bufq_free(&parser->scratch);
/* last chance adjustments */
}
else {
*err = Curl_dynhds_h1_add_line(&parser->req->headers,
parser->line, parser->line_len);
if(*err) {
nread = -1;
goto out;
}
}
}
out:
return nread;
}
#endif /* !CURL_DISABLE_HTTP */
@@ -1,5 +1,5 @@
#ifndef HEADER_CURL_H2H3_H
#define HEADER_CURL_H2H3_H
#ifndef HEADER_CURL_HTTP1_H
#define HEADER_CURL_HTTP1_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
@@ -23,40 +23,39 @@
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "curl_setup.h"
#define H2H3_PSEUDO_METHOD ":method"
#define H2H3_PSEUDO_SCHEME ":scheme"
#define H2H3_PSEUDO_AUTHORITY ":authority"
#define H2H3_PSEUDO_PATH ":path"
#define H2H3_PSEUDO_STATUS ":status"
#ifndef CURL_DISABLE_HTTP
#include "bufq.h"
#include "http.h"
struct h2h3pseudo {
const char *name;
size_t namelen;
const char *value;
size_t valuelen;
#define H1_PARSE_OPT_NONE (0)
#define H1_PARSE_OPT_STRICT (1 << 0)
#define H1_PARSE_DEFAULT_MAX_LINE_LEN (8 * 1024)
struct h1_req_parser {
struct httpreq *req;
struct bufq scratch;
size_t scratch_skip;
const char *line;
size_t max_line_len;
size_t line_len;
bool done;
};
struct h2h3req {
size_t entries;
struct h2h3pseudo header[1]; /* the array is allocated to contain entries */
};
void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len);
void Curl_h1_req_parse_free(struct h1_req_parser *parser);
/*
* Curl_pseudo_headers() creates the array with pseudo headers to be
* used in an HTTP/2 or HTTP/3 request. Returns an allocated struct.
* Free it with Curl_pseudo_free().
*/
CURLcode Curl_pseudo_headers(struct Curl_easy *data,
const char *request,
const size_t len,
size_t* hdrlen /* optional */,
struct h2h3req **hp);
ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser,
const char *buf, size_t buflen,
const char *scheme_default, int options,
CURLcode *err);
/*
* Curl_pseudo_free() frees a h2h3req struct.
*/
void Curl_pseudo_free(struct h2h3req *hp);
CURLcode Curl_h1_req_dprint(const struct httpreq *req,
struct dynbuf *dbuf);
#endif /* HEADER_CURL_H2H3_H */
#endif /* !CURL_DISABLE_HTTP */
#endif /* HEADER_CURL_HTTP1_H */
+1039 -1059
View File
@@ -25,8 +25,11 @@
#include "curl_setup.h"
#ifdef USE_NGHTTP2
#include <stdint.h>
#include <nghttp2/nghttp2.h>
#include "urldata.h"
#include "bufq.h"
#include "http1.h"
#include "http2.h"
#include "http.h"
#include "sendf.h"
@@ -35,21 +38,19 @@
#include "strcase.h"
#include "multiif.h"
#include "url.h"
#include "urlapi-int.h"
#include "cfilters.h"
#include "connect.h"
#include "strtoofft.h"
#include "strdup.h"
#include "transfer.h"
#include "dynbuf.h"
#include "h2h3.h"
#include "headers.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"
#define H2_BUFSIZE 32768
#if (NGHTTP2_VERSION_NUM < 0x010c00)
#error too old nghttp2 version, upgrade!
#endif
@@ -62,8 +63,30 @@
#define NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE 1
#endif
#define HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */
/* buffer dimensioning:
* use 16K as chunk size, as that fits H2 DATA frames well */
#define H2_CHUNK_SIZE (16 * 1024)
/* this is how much we want "in flight" for a stream */
#define H2_STREAM_WINDOW_SIZE (10 * 1024 * 1024)
/* on receving from TLS, we prep for holding a full stream window */
#define H2_NW_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE)
/* on send into TLS, we just want to accumulate small frames */
#define H2_NW_SEND_CHUNKS 1
/* stream recv/send chunks are a result of window / chunk sizes */
#define H2_STREAM_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE)
/* keep smaller stream upload buffer (default h2 window size) to have
* our progress bars and "upload done" reporting closer to reality */
#define H2_STREAM_SEND_CHUNKS ((64 * 1024) / H2_CHUNK_SIZE)
/* spare chunks we keep for a full window */
#define H2_STREAM_POOL_SPARES (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE)
/* We need to accommodate the max number of streams with their window
* sizes on the overall connection. Streams might become PAUSED which
* will block their received QUOTA in the connection window. And if we
* run out of space, the server is blocked from sending us any data.
* See #10988 for an issue with this. */
#define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE)
#define H2_SETTINGS_IV_LEN 3
#define H2_BINSETTINGS_LEN 80
@@ -75,7 +98,7 @@ static int populate_settings(nghttp2_settings_entry *iv,
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[1].value = H2_STREAM_WINDOW_SIZE;
iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
iv[2].value = data->multi->push_cb != NULL;
@@ -101,22 +124,14 @@ struct cf_h2_ctx {
/* 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 bufq inbufq; /* network input */
struct bufq outbufq; /* network output */
struct bufc_pool stream_bufcp; /* spares for stream buffers */
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 */
size_t drain_total; /* sum of all stream's UrlState drain */
int32_t goaway_error;
int32_t last_stream_id;
BIT(conn_closed);
BIT(goaway);
BIT(enable_push);
};
@@ -125,7 +140,6 @@ struct cf_h2_ctx {
#define CF_CTX_CALL_DATA(cf) \
((struct cf_h2_ctx *)(cf)->ctx)->call_data
static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx)
{
struct cf_call_data save = ctx->call_data;
@@ -133,8 +147,9 @@ static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx)
if(ctx->h2) {
nghttp2_session_del(ctx->h2);
}
free(ctx->inbuf);
Curl_dyn_free(&ctx->outbuf);
Curl_bufq_free(&ctx->inbufq);
Curl_bufq_free(&ctx->outbufq);
Curl_bufcp_free(&ctx->stream_bufcp);
memset(ctx, 0, sizeof(*ctx));
ctx->call_data = save;
}
@@ -147,26 +162,202 @@ static void cf_h2_ctx_free(struct cf_h2_ctx *ctx)
}
}
static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
struct Curl_easy *data);
/**
* All about the H3 internals of a stream
*/
struct stream_ctx {
/*********** for HTTP/2 we store stream-local data here *************/
int32_t id; /* HTTP/2 protocol identifier for stream */
struct bufq recvbuf; /* response buffer */
struct bufq sendbuf; /* request buffer */
struct dynhds resp_trailers; /* response trailer fields */
size_t resp_hds_len; /* amount of response header bytes in recvbuf */
curl_off_t upload_left; /* number of request bytes left to upload */
char **push_headers; /* allocated array */
size_t push_headers_used; /* number of entries filled in */
size_t push_headers_alloc; /* number of entries allocated */
int status_code; /* HTTP response status code */
uint32_t error; /* stream error code */
bool closed; /* TRUE on stream close */
bool reset; /* TRUE on stream reset */
bool close_handled; /* TRUE if stream closure is handled by libcurl */
bool bodystarted;
bool send_closed; /* transfer is done sending, we might have still
buffered data in stream->sendbuf to upload. */
};
#define H2_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \
((struct HTTP *)(d)->req.p.http)->h2_ctx \
: NULL))
#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx
#define H2_STREAM_ID(d) (H2_STREAM_CTX(d)? \
H2_STREAM_CTX(d)->id : -2)
/*
* Mark this transfer to get "drained".
*/
static void drain_stream(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct stream_ctx *stream)
{
unsigned char bits;
(void)cf;
bits = CURL_CSELECT_IN;
if(!stream->send_closed && stream->upload_left)
bits |= CURL_CSELECT_OUT;
if(data->state.dselect_bits != bits) {
data->state.dselect_bits = bits;
Curl_expire(data, 0, EXPIRE_RUN_NOW);
}
}
static CURLcode http2_data_setup(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct stream_ctx **pstream)
{
struct cf_h2_ctx *ctx = cf->ctx;
struct stream_ctx *stream;
(void)cf;
DEBUGASSERT(data);
if(!data->req.p.http) {
failf(data, "initialization failure, transfer not http initialized");
return CURLE_FAILED_INIT;
}
stream = H2_STREAM_CTX(data);
if(stream) {
*pstream = stream;
return CURLE_OK;
}
stream = calloc(1, sizeof(*stream));
if(!stream)
return CURLE_OUT_OF_MEMORY;
stream->id = -1;
Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp,
H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
Curl_dynhds_init(&stream->resp_trailers, 0, DYN_H2_TRAILERS);
stream->resp_hds_len = 0;
stream->bodystarted = FALSE;
stream->status_code = -1;
stream->closed = FALSE;
stream->close_handled = FALSE;
stream->error = NGHTTP2_NO_ERROR;
stream->upload_left = 0;
H2_STREAM_LCTX(data) = stream;
*pstream = stream;
return CURLE_OK;
}
static void http2_data_done(struct Curl_cfilter *cf,
struct Curl_easy *data, bool premature)
{
struct cf_h2_ctx *ctx = cf->ctx;
struct stream_ctx *stream = H2_STREAM_CTX(data);
DEBUGASSERT(ctx);
(void)premature;
if(!stream)
return;
if(ctx->h2) {
if(!stream->closed && stream->id > 0) {
/* RST_STREAM */
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] premature DATA_DONE, RST stream",
stream->id));
if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE,
stream->id, NGHTTP2_STREAM_CLOSED))
(void)nghttp2_session_send(ctx->h2);
}
if(!Curl_bufq_is_empty(&stream->recvbuf)) {
/* Anything in the recvbuf is still being counted
* in stream and connection window flow control. Need
* to free that space or the connection window might get
* exhausted eventually. */
nghttp2_session_consume(ctx->h2, stream->id,
Curl_bufq_len(&stream->recvbuf));
/* give WINDOW_UPATE a chance to be sent, but ignore any error */
(void)h2_progress_egress(cf, data);
}
/* -1 means unassigned and 0 means cleared */
if(nghttp2_session_get_stream_user_data(ctx->h2, stream->id)) {
int rv = nghttp2_session_set_stream_user_data(ctx->h2,
stream->id, 0);
if(rv) {
infof(data, "http/2: failed to clear user_data for stream %u",
stream->id);
DEBUGASSERT(0);
}
}
}
Curl_bufq_free(&stream->sendbuf);
Curl_bufq_free(&stream->recvbuf);
Curl_dynhds_free(&stream->resp_trailers);
if(stream->push_headers) {
/* if they weren't used and then freed before */
for(; stream->push_headers_used > 0; --stream->push_headers_used) {
free(stream->push_headers[stream->push_headers_used - 1]);
}
free(stream->push_headers);
stream->push_headers = NULL;
}
free(stream);
H2_STREAM_LCTX(data) = NULL;
}
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;
/* We handle window updates ourself to enforce buffer limits */
nghttp2_option_set_no_auto_window_update(o, 1);
#if NGHTTP2_VERSION_NUM >= 0x013200
/* with 1.50.0 */
/* 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);
#endif
rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o);
nghttp2_option_del(o);
return rc;
#endif
}
static ssize_t nw_in_reader(void *reader_ctx,
unsigned char *buf, size_t buflen,
CURLcode *err)
{
struct Curl_cfilter *cf = reader_ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
return Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err);
}
static ssize_t nw_out_writer(void *writer_ctx,
const unsigned char *buf, size_t buflen,
CURLcode *err)
{
struct Curl_cfilter *cf = writer_ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
return Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, err);
}
static ssize_t send_callback(nghttp2_session *h2,
@@ -201,37 +392,6 @@ static void multi_connchanged(struct Curl_multi *multi)
multi->recheckstate = TRUE;
}
static CURLcode http2_data_setup(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct HTTP *stream = data->req.p.http;
(void)cf;
DEBUGASSERT(stream);
DEBUGASSERT(data->state.buffer);
stream->stream_id = -1;
Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS);
Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS);
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;
}
/*
* Initialize the cfilter context
*/
@@ -240,17 +400,16 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
bool via_h1_upgrade)
{
struct cf_h2_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
struct stream_ctx *stream;
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);
Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES);
Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0);
Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0);
ctx->last_stream_id = 2147483647;
rc = nghttp2_session_callbacks_new(&cbs);
if(rc) {
@@ -276,10 +435,6 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
}
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
@@ -289,7 +444,11 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
binlen = populate_binsettings(binsettings, data);
stream->stream_id = 1;
result = http2_data_setup(cf, data, &stream);
if(result)
goto out;
DEBUGASSERT(stream);
stream->id = 1;
/* queue SETTINGS frame (again) */
rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen,
data->state.httpreq == HTTPREQ_HEAD,
@@ -301,11 +460,11 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
goto out;
}
rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->stream_id,
rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->id,
data);
if(rc) {
infof(data, "http/2: failed to set user_data for stream %u",
stream->stream_id);
stream->id);
DEBUGASSERT(0);
}
}
@@ -313,9 +472,6 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
int ivlen;
/* H2 Settings need to be submitted. Stream is not open yet. */
DEBUGASSERT(stream->stream_id == -1);
ivlen = populate_settings(iv, data);
rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE,
iv, ivlen);
@@ -345,27 +501,6 @@ out:
return result;
}
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;
}
}
/*
* Returns nonzero if current HTTP/2 session should be closed.
*/
@@ -375,6 +510,51 @@ static int should_close_session(struct cf_h2_ctx *ctx)
!nghttp2_session_want_write(ctx->h2);
}
/*
* Processes pending input left in network input buffer.
* 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_cfilter *cf,
struct Curl_easy *data,
CURLcode *err)
{
struct cf_h2_ctx *ctx = cf->ctx;
const unsigned char *buf;
size_t blen;
ssize_t rv;
while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) {
rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen);
if(rv < 0) {
failf(data,
"process_pending_input: nghttp2_session_mem_recv() returned "
"%zd:%s", rv, nghttp2_strerror((int)rv));
*err = CURLE_RECV_ERROR;
return -1;
}
Curl_bufq_skip(&ctx->inbufq, (size_t)rv);
if(Curl_bufq_is_empty(&ctx->inbufq)) {
break;
}
else {
DEBUGF(LOG_CF(data, cf, "process_pending_input: %zu bytes left "
"in connection buffer", Curl_bufq_len(&ctx->inbufq)));
}
}
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(cf->conn, "http/2: No new requests allowed");
}
return 0;
}
/*
* 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.
@@ -401,13 +581,10 @@ static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data,
*input_pending = FALSE;
Curl_attach_connection(data, cf->conn);
nread = Curl_conn_cf_recv(cf->next, data,
ctx->inbuf, H2_BUFSIZE, &result);
nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result);
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;
DEBUGF(LOG_CF(data, cf, "%zd bytes stray data read before trying "
"h2 connection", nread));
if(h2_process_pending_input(cf, data, &result) < 0)
/* immediate error, considered dead */
alive = FALSE;
@@ -456,30 +633,23 @@ 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,
static CURLcode nw_out_flush(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;
ssize_t nwritten;
CURLcode result;
if(!buflen)
(void)data;
if(Curl_bufq_is_empty(&ctx->outbufq))
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) {
DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes",
Curl_bufq_len(&ctx->outbufq)));
nwritten = Curl_bufq_pass(&ctx->outbufq, nw_out_writer, cf, &result);
if(nwritten < 0 && result != CURLE_AGAIN) {
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;
}
@@ -495,49 +665,27 @@ static ssize_t send_callback(nghttp2_session *h2,
struct Curl_cfilter *cf = userp;
struct cf_h2_ctx *ctx = cf->ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
ssize_t written;
ssize_t nwritten;
CURLcode result = CURLE_OK;
size_t buflen = Curl_dyn_len(&ctx->outbuf);
(void)h2;
(void)flags;
DEBUGASSERT(data);
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;
nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen,
nw_out_writer, cf, &result);
if(nwritten < 0) {
if(result == CURLE_AGAIN) {
return NGHTTP2_ERR_WOULDBLOCK;
}
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;
}
if(written == -1) {
failf(data, "Failed sending HTTP2 data");
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
if(!written)
if(!nwritten)
return NGHTTP2_ERR_WOULDBLOCK;
return written;
return nwritten;
}
@@ -558,8 +706,8 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num)
if(!h || !GOOD_EASY_HANDLE(h->data))
return NULL;
else {
struct HTTP *stream = h->data->req.p.http;
if(num < stream->push_headers_used)
struct stream_ctx *stream = H2_STREAM_CTX(h->data);
if(stream && num < stream->push_headers_used)
return stream->push_headers[num];
}
return NULL;
@@ -570,6 +718,9 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num)
*/
char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
{
struct stream_ctx *stream;
size_t len;
size_t i;
/* Verify that we got a good easy handle in the push header struct,
mostly to detect rubbish input fast(er). Also empty header name
is just a rubbish too. We have to allow ":" at the beginning of
@@ -579,50 +730,23 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
if(!h || !GOOD_EASY_HANDLE(h->data) || !header || !header[0] ||
!strcmp(header, ":") || strchr(header + 1, ':'))
return NULL;
else {
struct HTTP *stream = h->data->req.p.http;
size_t len = strlen(header);
size_t i;
for(i = 0; i<stream->push_headers_used; i++) {
if(!strncmp(header, stream->push_headers[i], len)) {
/* sub-match, make sure that it is followed by a colon */
if(stream->push_headers[i][len] != ':')
continue;
return &stream->push_headers[i][len + 1];
}
stream = H2_STREAM_CTX(h->data);
if(!stream)
return NULL;
len = strlen(header);
for(i = 0; i<stream->push_headers_used; i++) {
if(!strncmp(header, stream->push_headers[i], len)) {
/* sub-match, make sure that it is followed by a colon */
if(stream->push_headers[i][len] != ':')
continue;
return &stream->push_headers[i][len + 1];
}
}
return NULL;
}
/*
* This specific transfer on this connection has been "drained".
*/
static void drained_transfer(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
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_cfilter *cf,
struct Curl_easy *data)
{
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 *h2_duphandle(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
@@ -634,8 +758,10 @@ static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf,
(void)Curl_close(&second);
}
else {
struct stream_ctx *second_stream;
second->req.p.http = http;
http2_data_setup(cf, second);
http2_data_setup(cf, second, &second_stream);
second->state.priority.weight = data->state.priority.weight;
}
}
@@ -654,7 +780,7 @@ static int set_transfer_url(struct Curl_easy *data,
if(!u)
return 5;
v = curl_pushheader_byname(hp, H2H3_PSEUDO_SCHEME);
v = curl_pushheader_byname(hp, HTTP_PSEUDO_SCHEME);
if(v) {
uc = curl_url_set(u, CURLUPART_SCHEME, v, 0);
if(uc) {
@@ -663,16 +789,16 @@ static int set_transfer_url(struct Curl_easy *data,
}
}
v = curl_pushheader_byname(hp, H2H3_PSEUDO_AUTHORITY);
v = curl_pushheader_byname(hp, HTTP_PSEUDO_AUTHORITY);
if(v) {
uc = curl_url_set(u, CURLUPART_HOST, v, 0);
uc = Curl_url_set_authority(u, v, CURLU_DISALLOW_USER);
if(uc) {
rc = 2;
goto fail;
}
}
v = curl_pushheader_byname(hp, H2H3_PSEUDO_PATH);
v = curl_pushheader_byname(hp, HTTP_PSEUDO_PATH);
if(v) {
uc = curl_url_set(u, CURLUPART_PATH, v, 0);
if(uc) {
@@ -684,7 +810,7 @@ static int set_transfer_url(struct Curl_easy *data,
uc = curl_url_get(u, CURLUPART_URL, &url, 0);
if(uc)
rc = 4;
fail:
fail:
curl_url_cleanup(u);
if(rc)
return rc;
@@ -696,6 +822,16 @@ static int set_transfer_url(struct Curl_easy *data,
return 0;
}
static void discard_newhandle(struct Curl_cfilter *cf,
struct Curl_easy *newhandle)
{
if(!newhandle->req.p.http) {
http2_data_done(cf, newhandle, TRUE);
newhandle->req.p.http = NULL;
}
(void)Curl_close(&newhandle);
}
static int push_promise(struct Curl_cfilter *cf,
struct Curl_easy *data,
const nghttp2_push_promise *frame)
@@ -703,13 +839,14 @@ static int push_promise(struct Curl_cfilter *cf,
struct cf_h2_ctx *ctx = cf->ctx;
int rv; /* one of the CURL_PUSH_* defines */
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] PUSH_PROMISE received",
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] PUSH_PROMISE received",
frame->promised_stream_id));
if(data->multi->push_cb) {
struct HTTP *stream;
struct HTTP *newstream;
struct stream_ctx *stream;
struct stream_ctx *newstream;
struct curl_pushheaders heads;
CURLMcode rc;
CURLcode result;
size_t i;
/* clone the parent */
struct Curl_easy *newhandle = h2_duphandle(cf, data);
@@ -724,21 +861,30 @@ static int push_promise(struct Curl_cfilter *cf,
/* ask the application */
DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ask application"));
stream = data->req.p.http;
stream = H2_STREAM_CTX(data);
if(!stream) {
failf(data, "Internal NULL stream");
(void)Curl_close(&newhandle);
discard_newhandle(cf, newhandle);
rv = CURL_PUSH_DENY;
goto fail;
}
rv = set_transfer_url(newhandle, &heads);
if(rv) {
(void)Curl_close(&newhandle);
discard_newhandle(cf, newhandle);
rv = CURL_PUSH_DENY;
goto fail;
}
result = http2_data_setup(cf, newhandle, &newstream);
if(result) {
failf(data, "error setting up stream: %d", result);
discard_newhandle(cf, newhandle);
rv = CURL_PUSH_DENY;
goto fail;
}
DEBUGASSERT(stream);
Curl_set_in_callback(data, true);
rv = data->multi->push_cb(data, newhandle,
stream->push_headers_used, &heads,
@@ -755,14 +901,11 @@ static int push_promise(struct Curl_cfilter *cf,
if(rv) {
DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT));
/* denied, kill off the new handle again */
http2_stream_free(newhandle->req.p.http);
newhandle->req.p.http = NULL;
(void)Curl_close(&newhandle);
discard_newhandle(cf, newhandle);
goto fail;
}
newstream = newhandle->req.p.http;
newstream->stream_id = frame->promised_stream_id;
newstream->id = frame->promised_stream_id;
newhandle->req.maxdownload = -1;
newhandle->req.size = -1;
@@ -771,46 +914,164 @@ static int push_promise(struct Curl_cfilter *cf,
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);
newhandle->req.p.http = NULL;
Curl_close(&newhandle);
discard_newhandle(cf, newhandle);
rv = CURL_PUSH_DENY;
goto fail;
}
rv = nghttp2_session_set_stream_user_data(ctx->h2,
frame->promised_stream_id,
newstream->id,
newhandle);
if(rv) {
infof(data, "failed to set user_data for stream %u",
frame->promised_stream_id);
newstream->id);
DEBUGASSERT(0);
rv = CURL_PUSH_DENY;
goto fail;
}
Curl_dyn_init(&newstream->header_recvbuf, DYN_H2_HEADERS);
Curl_dyn_init(&newstream->trailer_recvbuf, DYN_H2_TRAILERS);
}
else {
DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ignore it"));
rv = CURL_PUSH_DENY;
}
fail:
fail:
return rv;
}
static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *buf, size_t blen)
{
struct stream_ctx *stream = H2_STREAM_CTX(data);
ssize_t nwritten;
CURLcode result;
(void)cf;
nwritten = Curl_bufq_write(&stream->recvbuf,
(const unsigned char *)buf, blen, &result);
if(nwritten < 0)
return result;
stream->resp_hds_len += (size_t)nwritten;
DEBUGASSERT((size_t)nwritten == blen);
return CURLE_OK;
}
static CURLcode on_stream_frame(struct Curl_cfilter *cf,
struct Curl_easy *data,
const nghttp2_frame *frame)
{
struct cf_h2_ctx *ctx = cf->ctx;
struct stream_ctx *stream = H2_STREAM_CTX(data);
int32_t stream_id = frame->hd.stream_id;
CURLcode result;
int rv;
if(!stream) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] No proto pointer", stream_id));
return CURLE_FAILED_INIT;
}
switch(frame->hd.type) {
case NGHTTP2_DATA:
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[DATA len=%zu pad=%zu], "
"buffered=%zu, window=%d/%d",
stream_id, frame->hd.length, frame->data.padlen,
Curl_bufq_len(&stream->recvbuf),
nghttp2_session_get_stream_effective_recv_data_length(
ctx->h2, stream->id),
nghttp2_session_get_stream_effective_local_window_size(
ctx->h2, stream->id)));
/* If !body started on this stream, then receiving DATA is illegal. */
if(!stream->bodystarted) {
rv = nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE,
stream_id, NGHTTP2_PROTOCOL_ERROR);
if(nghttp2_is_fatal(rv)) {
return CURLE_RECV_ERROR;
}
}
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
drain_stream(cf, data, stream);
}
break;
case NGHTTP2_HEADERS:
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[HEADERS]", stream_id));
if(stream->bodystarted) {
/* Only valid HEADERS after body started is trailer HEADERS. We
buffer them in on_header callback. */
break;
}
/* nghttp2 guarantees that :status is received, and we store it to
stream->status_code. Fuzzing has proven this can still be reached
without status code having been set. */
if(stream->status_code == -1)
return CURLE_RECV_ERROR;
/* Only final status code signals the end of header */
if(stream->status_code / 100 != 1) {
stream->bodystarted = TRUE;
stream->status_code = -1;
}
result = recvbuf_write_hds(cf, data, STRCONST("\r\n"));
if(result)
return result;
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] %zu header bytes",
stream_id, Curl_bufq_len(&stream->recvbuf)));
drain_stream(cf, data, stream);
break;
case NGHTTP2_PUSH_PROMISE:
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[PUSH_PROMISE]", stream_id));
rv = push_promise(cf, data, &frame->push_promise);
if(rv) { /* deny! */
DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT));
rv = nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE,
frame->push_promise.promised_stream_id,
NGHTTP2_CANCEL);
if(nghttp2_is_fatal(rv))
return CURLE_SEND_ERROR;
else if(rv == CURL_PUSH_ERROROUT) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] fail in PUSH_PROMISE received",
stream_id));
return CURLE_RECV_ERROR;
}
}
break;
case NGHTTP2_RST_STREAM:
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[RST]", stream_id));
stream->closed = TRUE;
stream->reset = TRUE;
stream->send_closed = TRUE;
data->req.keepon &= ~KEEP_SEND_HOLD;
drain_stream(cf, data, stream);
break;
case NGHTTP2_WINDOW_UPDATE:
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[WINDOW_UPDATE]", stream_id));
if((data->req.keepon & KEEP_SEND_HOLD) &&
(data->req.keepon & KEEP_SEND)) {
data->req.keepon &= ~KEEP_SEND_HOLD;
drain_stream(cf, data, stream);
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] un-holding after win update",
stream_id));
}
break;
default:
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] FRAME[%x]",
stream_id, frame->hd.type));
break;
}
return CURLE_OK;
}
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
void *userp)
{
struct Curl_cfilter *cf = userp;
struct cf_h2_ctx *ctx = cf->ctx;
struct Curl_easy *data_s = NULL;
struct HTTP *stream = NULL;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
int rv;
size_t left, ncopy;
struct Curl_easy *data = CF_DATA_CURRENT(cf), *data_s;
int32_t stream_id = frame->hd.stream_id;
CURLcode result;
DEBUGASSERT(data);
if(!stream_id) {
@@ -819,7 +1080,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
switch(frame->hd.type) {
case NGHTTP2_SETTINGS: {
uint32_t max_conn = ctx->max_concurrent_streams;
DEBUGF(LOG_CF(data, cf, "recv frame SETTINGS"));
DEBUGF(LOG_CF(data, cf, "FRAME[SETTINGS]"));
ctx->max_concurrent_streams = nghttp2_session_get_remote_settings(
session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
ctx->enable_push = nghttp2_session_get_remote_settings(
@@ -841,138 +1102,30 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
ctx->goaway_error = frame->goaway.error_code;
ctx->last_stream_id = frame->goaway.last_stream_id;
if(data) {
infof(data, "recveived GOAWAY, error=%d, last_stream=%u",
DEBUGF(LOG_CF(data, cf, "FRAME[GOAWAY, error=%d, last_stream=%u]",
ctx->goaway_error, ctx->last_stream_id));
infof(data, "received GOAWAY, error=%d, last_stream=%u",
ctx->goaway_error, ctx->last_stream_id);
multi_connchanged(data->multi);
}
break;
case NGHTTP2_WINDOW_UPDATE:
DEBUGF(LOG_CF(data, cf, "recv frame WINDOW_UPDATE"));
DEBUGF(LOG_CF(data, cf, "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) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] No Curl_easy associated",
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] No Curl_easy associated",
stream_id));
return 0;
}
stream = data_s->req.p.http;
if(!stream) {
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] No proto pointer", stream_id));
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
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);
if(nghttp2_is_fatal(rv)) {
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. */
break;
}
/* nghttp2 guarantees that :status is received, and we store it to
stream->status_code. Fuzzing has proven this can still be reached
without status code having been set. */
if(stream->status_code == -1)
return NGHTTP2_ERR_CALLBACK_FAILURE;
/* Only final status code signals the end of header */
if(stream->status_code / 100 != 1) {
stream->bodystarted = TRUE;
stream->status_code = -1;
}
result = Curl_dyn_addn(&stream->header_recvbuf, STRCONST("\r\n"));
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
left = Curl_dyn_len(&stream->header_recvbuf) -
stream->nread_header_recvbuf;
ncopy = CURLMIN(stream->len, left);
memcpy(&stream->mem[stream->memlen],
Curl_dyn_ptr(&stream->header_recvbuf) +
stream->nread_header_recvbuf,
ncopy);
stream->nread_header_recvbuf += ncopy;
DEBUGASSERT(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(cf, data_s);
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
break;
case NGHTTP2_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));
h2 = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->push_promise.promised_stream_id,
NGHTTP2_CANCEL);
if(nghttp2_is_fatal(h2))
return NGHTTP2_ERR_CALLBACK_FAILURE;
else if(rv == CURL_PUSH_ERROROUT) {
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->closed = TRUE;
stream->reset = TRUE;
drain_this(cf, data);
Curl_expire(data, 0, EXPIRE_RUN_NOW);
break;
case NGHTTP2_WINDOW_UPDATE:
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv WINDOW_UPDATE", stream_id));
if((data_s->req.keepon & KEEP_SEND_HOLD) &&
(data_s->req.keepon & KEEP_SEND)) {
data_s->req.keepon &= ~KEEP_SEND_HOLD;
drain_this(cf, data_s);
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] un-holding after win update",
stream_id));
}
break;
default:
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame %x",
stream_id, frame->hd.type));
break;
}
return 0;
return on_stream_frame(cf, data_s, frame)? NGHTTP2_ERR_CALLBACK_FAILURE : 0;
}
static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
@@ -980,10 +1133,10 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
const uint8_t *mem, size_t len, void *userp)
{
struct Curl_cfilter *cf = userp;
struct cf_h2_ctx *ctx = cf->ctx;
struct HTTP *stream;
struct stream_ctx *stream;
struct Curl_easy *data_s;
size_t nread;
ssize_t nwritten;
CURLcode result;
(void)flags;
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
@@ -995,42 +1148,29 @@ 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. */
DEBUGF(LOG_CF(CF_DATA_CURRENT(cf), cf, "[h2sid=%u] Data for unknown",
DEBUGF(LOG_CF(CF_DATA_CURRENT(cf), cf, "[h2sid=%d] Data for unknown",
stream_id));
/* consumed explicitly as no one will read it */
nghttp2_session_consume(session, stream_id, len);
return 0;
}
stream = data_s->req.p.http;
stream = H2_STREAM_CTX(data_s);
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
nread = CURLMIN(stream->len, len);
memcpy(&stream->mem[stream->memlen], mem, nread);
nwritten = Curl_bufq_write(&stream->recvbuf, mem, len, &result);
if(nwritten < 0) {
if(result != CURLE_AGAIN)
return NGHTTP2_ERR_CALLBACK_FAILURE;
stream->len -= nread;
stream->memlen += nread;
nwritten = 0;
}
/* if we receive data for another handle, wake that up */
if(CF_DATA_CURRENT(cf) != data_s) {
drain_this(cf, data_s);
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
}
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;
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;
}
drain_stream(cf, data_s, stream);
DEBUGASSERT((size_t)nwritten == len);
return 0;
}
@@ -1038,9 +1178,8 @@ 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 stream_ctx *stream;
int rv;
(void)session;
@@ -1051,8 +1190,8 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
if(!data_s) {
return 0;
}
stream = data_s->req.p.http;
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] on_stream_close(), %s (err %d)",
stream = H2_STREAM_CTX(data_s);
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] on_stream_close(), %s (err %d)",
stream_id, nghttp2_http2_strerror(error_code), error_code));
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
@@ -1061,11 +1200,9 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
stream->error = error_code;
if(stream->error)
stream->reset = TRUE;
data_s->req.keepon &= ~KEEP_SEND_HOLD;
if(CF_DATA_CURRENT(cf) != data_s) {
drain_this(cf, data_s);
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
}
drain_stream(cf, data_s, stream);
/* remove `data_s` from the nghttp2 stream */
rv = nghttp2_session_set_stream_user_data(session, stream_id, 0);
@@ -1074,12 +1211,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
stream_id);
DEBUGASSERT(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;
}
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed now", stream_id));
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] closed now", stream_id));
return 0;
}
@@ -1087,7 +1219,7 @@ static int on_begin_headers(nghttp2_session *session,
const nghttp2_frame *frame, void *userp)
{
struct Curl_cfilter *cf = userp;
struct HTTP *stream;
struct stream_ctx *stream;
struct Curl_easy *data_s = NULL;
(void)cf;
@@ -1102,7 +1234,7 @@ static int on_begin_headers(nghttp2_session *session,
return 0;
}
stream = data_s->req.p.http;
stream = H2_STREAM_CTX(data_s);
if(!stream || !stream->bodystarted) {
return 0;
}
@@ -1110,33 +1242,6 @@ static int on_begin_headers(nghttp2_session *session,
return 0;
}
/* Decode HTTP status code. Returns -1 if no valid status code was
decoded. */
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;
}
/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */
static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
@@ -1145,7 +1250,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
void *userp)
{
struct Curl_cfilter *cf = userp;
struct HTTP *stream;
struct stream_ctx *stream;
struct Curl_easy *data_s;
int32_t stream_id = frame->hd.stream_id;
CURLcode result;
@@ -1160,7 +1265,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
internal error more than anything else! */
return NGHTTP2_ERR_CALLBACK_FAILURE;
stream = data_s->req.p.http;
stream = H2_STREAM_CTX(data_s);
if(!stream) {
failf(data_s, "Internal NULL stream");
return NGHTTP2_ERR_CALLBACK_FAILURE;
@@ -1171,7 +1276,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
if(frame->hd.type == NGHTTP2_PUSH_PROMISE) {
char *h;
if(!strcmp(H2H3_PSEUDO_AUTHORITY, (const char *)name)) {
if(!strcmp(HTTP_PSEUDO_AUTHORITY, (const char *)name)) {
/* pseudo headers are lower case */
int rc = 0;
char *check = aprintf("%s:%d", cf->conn->host.name,
@@ -1230,89 +1335,90 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
if(stream->bodystarted) {
/* This is a trailer */
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] trailer: %.*s: %.*s",
stream->stream_id,
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] trailer: %.*s: %.*s",
stream->id,
(int)namelen, name,
(int)valuelen, value));
result = Curl_dyn_addf(&stream->trailer_recvbuf,
"%.*s: %.*s\r\n", (int)namelen, name,
(int)valuelen, value);
result = Curl_dynhds_add(&stream->resp_trailers,
(const char *)name, namelen,
(const char *)value, valuelen);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
return 0;
}
if(namelen == sizeof(H2H3_PSEUDO_STATUS) - 1 &&
memcmp(H2H3_PSEUDO_STATUS, name, namelen) == 0) {
/* nghttp2 guarantees :status is received first and only once, and
value is 3 digits status code, and decode_status_code always
succeeds. */
if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 &&
memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) {
/* nghttp2 guarantees :status is received first and only once. */
char buffer[32];
stream->status_code = decode_status_code(value, valuelen);
DEBUGASSERT(stream->status_code != -1);
msnprintf(buffer, sizeof(buffer), H2H3_PSEUDO_STATUS ":%u\r",
result = Curl_http_decode_status(&stream->status_code,
(const char *)value, valuelen);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
msnprintf(buffer, sizeof(buffer), HTTP_PSEUDO_STATUS ":%u\r",
stream->status_code);
result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
result = Curl_dyn_addn(&stream->header_recvbuf, STRCONST("HTTP/2 "));
result = recvbuf_write_hds(cf, data_s, STRCONST("HTTP/2 "));
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
result = Curl_dyn_addn(&stream->header_recvbuf, value, valuelen);
result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
/* the space character after the status code is mandatory */
result = Curl_dyn_addn(&stream->header_recvbuf, STRCONST(" \r\n"));
result = recvbuf_write_hds(cf, data_s, STRCONST(" \r\n"));
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
/* if we receive data for another handle, wake that up */
if(CF_DATA_CURRENT(cf) != data_s)
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] status: HTTP/2 %03d",
stream->stream_id, stream->status_code));
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] status: HTTP/2 %03d",
stream->id, stream->status_code));
return 0;
}
/* nghttp2 guarantees that namelen > 0, and :status was already
received, and this is not pseudo-header field . */
/* convert to an HTTP1-style header */
result = Curl_dyn_addn(&stream->header_recvbuf, name, namelen);
result = recvbuf_write_hds(cf, data_s, (const char *)name, namelen);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
result = Curl_dyn_addn(&stream->header_recvbuf, STRCONST(": "));
result = recvbuf_write_hds(cf, data_s, STRCONST(": "));
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
result = Curl_dyn_addn(&stream->header_recvbuf, value, valuelen);
result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen);
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
result = Curl_dyn_addn(&stream->header_recvbuf, STRCONST("\r\n"));
result = recvbuf_write_hds(cf, data_s, STRCONST("\r\n"));
if(result)
return NGHTTP2_ERR_CALLBACK_FAILURE;
/* if we receive data for another handle, wake that up */
if(CF_DATA_CURRENT(cf) != data_s)
Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] header: %.*s: %.*s",
stream->stream_id,
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] header: %.*s: %.*s",
stream->id,
(int)namelen, name,
(int)valuelen, value));
return 0; /* 0 is successful */
}
static ssize_t data_source_read_callback(nghttp2_session *session,
int32_t stream_id,
uint8_t *buf, size_t length,
uint32_t *data_flags,
nghttp2_data_source *source,
void *userp)
static ssize_t req_body_read_callback(nghttp2_session *session,
int32_t stream_id,
uint8_t *buf, size_t length,
uint32_t *data_flags,
nghttp2_data_source *source,
void *userp)
{
struct Curl_cfilter *cf = userp;
struct Curl_easy *data_s;
struct HTTP *stream = NULL;
size_t nread;
struct stream_ctx *stream = NULL;
CURLcode result;
ssize_t nread;
(void)source;
(void)cf;
@@ -1325,30 +1431,32 @@ static ssize_t data_source_read_callback(nghttp2_session *session,
internal error more than anything else! */
return NGHTTP2_ERR_CALLBACK_FAILURE;
stream = data_s->req.p.http;
stream = H2_STREAM_CTX(data_s);
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
else
return NGHTTP2_ERR_INVALID_ARGUMENT;
nread = CURLMIN(stream->upload_len, length);
if(nread > 0) {
memcpy(buf, stream->upload_mem, nread);
stream->upload_mem += nread;
stream->upload_len -= nread;
if(data_s->state.infilesize != -1)
stream->upload_left -= nread;
nread = Curl_bufq_read(&stream->sendbuf, buf, length, &result);
if(nread < 0) {
if(result != CURLE_AGAIN)
return NGHTTP2_ERR_CALLBACK_FAILURE;
nread = 0;
}
if(nread > 0 && stream->upload_left != -1)
stream->upload_left -= nread;
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] req_body_read(len=%zu) left=%zd"
" -> %zd, %d",
stream_id, length, stream->upload_left, nread, result));
if(stream->upload_left == 0)
*data_flags = NGHTTP2_DATA_FLAG_EOF;
else if(nread == 0)
return NGHTTP2_ERR_DEFERRED;
DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] data_source_read_callback: "
"returns %zu bytes", stream_id, nread));
return nread;
}
@@ -1366,59 +1474,6 @@ static int error_callback(nghttp2_session *session,
}
#endif
static void http2_data_done(struct Curl_cfilter *cf,
struct Curl_easy *data, bool premature)
{
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(&stream->header_recvbuf);
Curl_dyn_free(&stream->trailer_recvbuf);
if(stream->push_headers) {
/* if they weren't used and then freed before */
for(; stream->push_headers_used > 0; --stream->push_headers_used) {
free(stream->push_headers[stream->push_headers_used - 1]);
}
free(stream->push_headers);
stream->push_headers = NULL;
}
if(!ctx || !ctx->h2)
return;
/* do this before the reset handling, as that might clear ->stream_id */
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;
}
(void)premature;
if(!stream->closed && stream->stream_id) {
/* RST_STREAM */
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(cf, data);
/* -1 means unassigned and 0 means cleared */
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",
stream->stream_id);
DEBUGASSERT(0);
}
}
}
/*
* Append headers to ask for an HTTP1.1 to HTTP2 upgrade.
*/
@@ -1458,113 +1513,26 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
return result;
}
/*
* h2_process_pending_input() processes pending input left in
* httpc->inbuf. Then, call h2_session_send() to send pending data.
* 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_cfilter *cf,
struct Curl_easy *data,
CURLcode *err)
{
struct cf_h2_ctx *ctx = cf->ctx;
ssize_t nread;
ssize_t rv;
nread = ctx->inbuflen - ctx->nread_inbuf;
if(nread) {
char *inbuf = ctx->inbuf + ctx->nread_inbuf;
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) {
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(cf, data);
if(rv) {
*err = CURLE_SEND_ERROR;
return -1;
}
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(cf->conn, "http/2: No new requests allowed");
}
if(should_close_session(ctx)) {
struct HTTP *stream = data->req.p.http;
DEBUGF(LOG_CF(data, cf,
"h2_process_pending_input: nothing to do in this session"));
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(cf->conn, "GOAWAY received");
*err = CURLE_OK;
}
return -1;
}
return 0;
}
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;
struct stream_ctx *stream = H2_STREAM_CTX(data);
if(!ctx || !ctx->h2)
if(!ctx || !ctx->h2 || !stream)
goto out;
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(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(ctx->h2)) {
struct SingleRequest *k = &data->req;
int rv;
DEBUGF(LOG_CF(data, cf, "HTTP/2 still wants to send data"));
/* and attempt to send the pending frames */
rv = h2_session_send(cf, data);
if(rv)
result = CURLE_SEND_ERROR;
if(nghttp2_session_want_write(ctx->h2)) {
/* re-set KEEP_SEND to make sure we are called again */
k->keepon |= KEEP_SEND;
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] data done send", stream->id));
if(!stream->send_closed) {
stream->send_closed = TRUE;
if(stream->upload_left) {
/* we now know that everything that is buffered is all there is. */
stream->upload_left = Curl_bufq_len(&stream->sendbuf);
/* resume sending here to trigger the callback to get called again so
that it can signal EOF to nghttp2 */
(void)nghttp2_session_resume_data(ctx->h2, stream->id);
drain_stream(cf, data, stream);
}
}
@@ -1574,79 +1542,76 @@ out:
static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct HTTP *stream, CURLcode *err)
struct stream_ctx *stream,
CURLcode *err)
{
struct cf_h2_ctx *ctx = cf->ctx;
if(ctx->pause_stream_id == stream->stream_id) {
ctx->pause_stream_id = 0;
}
drained_transfer(cf, data);
if(ctx->pause_stream_id == 0) {
if(h2_process_pending_input(cf, data, err) != 0) {
return -1;
}
}
ssize_t rv = 0;
if(stream->error == NGHTTP2_REFUSED_STREAM) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] REFUSED_STREAM, try again on a new "
"connection", stream->stream_id));
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] REFUSED_STREAM, try again on a new "
"connection", 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 */
*err = CURLE_SEND_ERROR; /* trigger Curl_retry_request() later */
return -1;
}
else if(stream->reset) {
failf(data, "HTTP/2 stream %u was reset", stream->id);
*err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
return -1;
}
else if(stream->error != NGHTTP2_NO_ERROR) {
failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
stream->stream_id, nghttp2_http2_strerror(stream->error),
stream->id, nghttp2_http2_strerror(stream->error),
stream->error);
*err = CURLE_HTTP2_STREAM;
return -1;
}
else if(stream->reset) {
failf(data, "HTTP/2 stream %u was reset", stream->stream_id);
*err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
return -1;
}
if(!stream->bodystarted) {
failf(data, "HTTP/2 stream %u was closed cleanly, but before getting "
" all response header fields, treated as error",
stream->stream_id);
stream->id);
*err = CURLE_HTTP2_STREAM;
return -1;
}
if(Curl_dyn_len(&stream->trailer_recvbuf)) {
char *trailp = Curl_dyn_ptr(&stream->trailer_recvbuf);
char *lf;
if(Curl_dynhds_count(&stream->resp_trailers)) {
struct dynhds_entry *e;
struct dynbuf dbuf;
size_t i;
do {
size_t len = 0;
CURLcode result;
/* each trailer line ends with a newline */
lf = strchr(trailp, '\n');
if(!lf)
*err = CURLE_OK;
Curl_dyn_init(&dbuf, DYN_TRAILERS);
for(i = 0; i < Curl_dynhds_count(&stream->resp_trailers); ++i) {
e = Curl_dynhds_getn(&stream->resp_trailers, i);
if(!e)
break;
len = lf + 1 - trailp;
Curl_debug(data, CURLINFO_HEADER_IN, trailp, len);
/* pass the trailers one by one to the callback */
result = Curl_client_write(data, CLIENTWRITE_HEADER, trailp, len);
if(result) {
*err = result;
return -1;
}
trailp = ++lf;
} while(lf);
Curl_dyn_reset(&dbuf);
*err = Curl_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a",
(int)e->namelen, e->name,
(int)e->valuelen, e->value);
if(*err)
break;
Curl_debug(data, CURLINFO_HEADER_IN, Curl_dyn_ptr(&dbuf),
Curl_dyn_len(&dbuf));
*err = Curl_client_write(data, CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER,
Curl_dyn_ptr(&dbuf), Curl_dyn_len(&dbuf));
if(*err)
break;
}
Curl_dyn_free(&dbuf);
if(*err)
goto out;
}
stream->close_handled = TRUE;
*err = CURLE_OK;
rv = 0;
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] closed cleanly", stream->stream_id));
return 0;
out:
DEBUGF(LOG_CF(data, cf, "handle_stream_close -> %zd, %d", rv, *err));
return rv;
}
static int sweight_wanted(const struct Curl_easy *data)
@@ -1673,9 +1638,8 @@ static void h2_pri_spec(struct Curl_easy *data,
nghttp2_priority_spec *pri_spec)
{
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;
struct stream_ctx *depstream = H2_STREAM_CTX(prio->parent);
int32_t depstream_id = depstream? depstream->id:0;
nghttp2_priority_spec_init(pri_spec, depstream_id,
sweight_wanted(data),
data->set.priority.exclusive);
@@ -1683,15 +1647,16 @@ static void h2_pri_spec(struct Curl_easy *data,
}
/*
* h2_session_send() checks if there's been an update in the priority /
* Check if there's been an update in the priority /
* dependency settings and if so it submits a PRIORITY frame with the updated
* info.
* Flush any out data pending in the network buffer.
*/
static CURLcode h2_session_send(struct Curl_cfilter *cf,
struct Curl_easy *data)
static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
struct stream_ctx *stream = H2_STREAM_CTX(data);
int rv = 0;
if((sweight_wanted(data) != sweight_in_effect(data)) ||
@@ -1701,401 +1666,265 @@ static CURLcode h2_session_send(struct Curl_cfilter *cf,
nghttp2_priority_spec pri_spec;
h2_pri_spec(data, &pri_spec);
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Queuing PRIORITY",
stream->stream_id));
DEBUGASSERT(stream->stream_id != -1);
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] Queuing PRIORITY",
stream->id));
DEBUGASSERT(stream->id != -1);
rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE,
stream->stream_id, &pri_spec);
stream->id, &pri_spec);
if(rv)
goto out;
}
rv = nghttp2_session_send(ctx->h2);
while(!rv && nghttp2_session_want_write(ctx->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);
return nw_out_flush(cf, data);
}
static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
struct cf_h2_ctx *ctx = cf->ctx;
struct stream_ctx *stream = H2_STREAM_CTX(data);
ssize_t nread = -1;
*err = CURLE_AGAIN;
if(!Curl_bufq_is_empty(&stream->recvbuf)) {
nread = Curl_bufq_read(&stream->recvbuf,
(unsigned char *)buf, len, err);
DEBUGF(LOG_CF(data, cf, "recvbuf read(len=%zu) -> %zd, %d",
len, nread, *err));
if(nread < 0)
goto out;
DEBUGASSERT(nread > 0);
}
if(nread < 0) {
if(stream->closed) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] returning CLOSE", stream->id));
nread = http2_handle_stream_close(cf, data, stream, err);
}
else if(stream->reset ||
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
(ctx->goaway && ctx->last_stream_id < stream->id)) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] returning ERR", stream->id));
*err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
nread = -1;
}
}
else if(nread == 0) {
*err = CURLE_AGAIN;
nread = -1;
}
out:
DEBUGF(LOG_CF(data, cf, "stream_recv(len=%zu) -> %zd, %d",
len, nread, *err));
return nread;
}
static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_ctx *ctx = cf->ctx;
struct stream_ctx *stream;
CURLcode result = CURLE_OK;
ssize_t nread;
/* Process network input buffer fist */
if(!Curl_bufq_is_empty(&ctx->inbufq)) {
DEBUGF(LOG_CF(data, cf, "Process %zd bytes in connection buffer",
Curl_bufq_len(&ctx->inbufq)));
if(h2_process_pending_input(cf, data, &result) < 0)
return result;
}
/* Receive data from the "lower" filters, e.g. network until
* it is time to stop due to connection close or us not processing
* all network input */
while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
stream = H2_STREAM_CTX(data);
if(stream && (stream->closed || Curl_bufq_is_full(&stream->recvbuf))) {
/* We would like to abort here and stop processing, so that
* the transfer loop can handle the data/close here. However,
* this may leave data in underlying buffers that will not
* be consumed. */
if(!cf->next || !cf->next->cft->has_data_pending(cf->next, data))
break;
}
nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result);
/* DEBUGF(LOG_CF(data, cf, "read %zd bytes nw data -> %zd, %d",
Curl_bufq_len(&ctx->inbufq), nread, result)); */
if(nread < 0) {
if(result != CURLE_AGAIN) {
failf(data, "Failed receiving HTTP2 data: %d(%s)", result,
curl_easy_strerror(result));
return result;
}
break;
}
else if(nread == 0) {
ctx->conn_closed = TRUE;
break;
}
if(h2_process_pending_input(cf, data, &result))
return result;
}
if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
connclose(cf->conn, "GOAWAY received");
}
return CURLE_OK;
}
static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
struct cf_h2_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
struct stream_ctx *stream = H2_STREAM_CTX(data);
ssize_t nread = -1;
CURLcode result;
struct cf_call_data save;
bool conn_is_closed = FALSE;
CF_DATA_SAVE(save, cf, data);
/* If the h2 session has told us to GOAWAY with an error AND
* indicated the highest stream id it has processes AND
* the stream we are trying to read has a higher id, this
* means we will most likely not receive any more for it.
* Treat this as if the server explicitly had RST the stream */
if((ctx->goaway && ctx->goaway_error &&
ctx->last_stream_id > 0 &&
ctx->last_stream_id < stream->stream_id)) {
stream->reset = TRUE;
}
/* If a stream is RST, it does not matter what state the h2 session
* is in, our answer to receiving data is always the same. */
if(stream->reset) {
*err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
nread = -1;
nread = stream_recv(cf, data, buf, len, err);
if(nread < 0 && *err != CURLE_AGAIN)
goto out;
}
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;
nread = 0;
if(nread < 0) {
*err = h2_progress_ingress(cf, data);
if(*err)
goto out;
}
*err = CURLE_HTTP2;
nread = -1;
goto out;
nread = stream_recv(cf, data, buf, len, err);
}
/* Nullify here because we call nghttp2_session_send() and they
might refer to the old buffer. */
stream->upload_mem = NULL;
stream->upload_len = 0;
/*
* At this point 'stream' is just in the Curl_easy the connection
* identifies as its owner at this time.
*/
if(stream->bodystarted &&
stream->nread_header_recvbuf < Curl_dyn_len(&stream->header_recvbuf)) {
/* If there is header data pending for this stream to return, do that */
size_t left =
Curl_dyn_len(&stream->header_recvbuf) - stream->nread_header_recvbuf;
size_t ncopy = CURLMIN(len, left);
memcpy(buf, Curl_dyn_ptr(&stream->header_recvbuf) +
stream->nread_header_recvbuf, ncopy);
stream->nread_header_recvbuf += ncopy;
DEBUGF(LOG_CF(data, cf, "recv: Got %d bytes from header_recvbuf",
(int)ncopy));
nread = ncopy;
goto out;
}
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_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(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(buf, stream->mem, stream->memlen);
stream->len = len - stream->memlen;
stream->mem = buf;
}
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). */
ctx->pause_stream_id = 0;
if(h2_process_pending_input(cf, data, err) != 0) {
nread = -1;
goto out;
}
}
}
else if(stream->pausedata) {
DEBUGASSERT(ctx->pause_stream_id == stream->stream_id);
nread = CURLMIN(len, stream->pauselen);
memcpy(buf, stream->pausedata, nread);
stream->pausedata += nread;
stream->pauselen -= nread;
drain_this(cf, data);
if(stream->pauselen == 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;
}
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: returns unpaused %zd bytes",
stream->stream_id, nread));
goto out;
}
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
overwrite that buffer. To avoid that situation, just return
here with CURLE_AGAIN. This could be busy loop since data in
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) {
/* closed overrides paused */
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;
nread = -1;
goto out;
}
else {
/* 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(ctx->inbuflen > 0) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] %zd bytes in inbuf",
stream->stream_id, ctx->inbuflen - ctx->nread_inbuf));
if(h2_process_pending_input(cf, data, err))
return -1;
}
while(stream->memlen == 0 && /* have no data for this stream */
!stream->closed && /* and it is not closed/reset */
!ctx->pause_stream_id && /* we are not paused either */
ctx->inbuflen == 0 && /* and out input buffer is empty */
!conn_is_closed) { /* and connection is not closed */
/* Receive data from the "lower" filters */
nread = Curl_conn_cf_recv(cf->next, data, ctx->inbuf, H2_BUFSIZE, err);
if(nread < 0) {
DEBUGASSERT(*err);
if(*err == CURLE_AGAIN) {
break;
}
failf(data, "Failed receiving HTTP2 data");
conn_is_closed = TRUE;
}
else if(nread == 0) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] underlying connection is closed",
stream->stream_id));
conn_is_closed = TRUE;
}
else {
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] read %zd from connection",
stream->stream_id, nread));
ctx->inbuflen = nread;
DEBUGASSERT(ctx->nread_inbuf == 0);
if(h2_process_pending_input(cf, data, err))
return -1;
}
}
}
if(stream->memlen) {
ssize_t retlen = stream->memlen;
/* TODO: all this buffer handling is very brittle */
stream->len += stream->memlen;
stream->memlen = 0;
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 */
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 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);
if(nread > 0) {
size_t data_consumed = (size_t)nread;
/* Now that we transferred this to the upper layer, we report
* the actual amount of DATA consumed to the H2 session, so
* that it adjusts stream flow control */
if(stream->resp_hds_len >= data_consumed) {
stream->resp_hds_len -= data_consumed; /* no DATA */
}
else {
drained_transfer(cf, data);
if(stream->resp_hds_len) {
data_consumed -= stream->resp_hds_len;
stream->resp_hds_len = 0;
}
if(data_consumed) {
nghttp2_session_consume(ctx->h2, stream->id, data_consumed);
}
}
*err = CURLE_OK;
nread = retlen;
goto out;
if(stream->closed) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] closed stream, set drain",
stream->id));
drain_stream(cf, data, stream);
}
}
if(conn_is_closed && !stream->closed) {
/* underlying connection is closed and we have nothing for the stream.
* Treat this as a RST */
stream->closed = stream->reset = TRUE;
failf(data, "HTTP/2 stream %u was not closed cleanly before"
" end of the underlying connection",
stream->stream_id);
}
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);
}
*err = CURLE_AGAIN;
nread = -1;
out:
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv -> %zd, %d",
stream->stream_id, nread, *err));
result = h2_progress_egress(cf, data);
if(result) {
*err = result;
nread = -1;
}
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_recv(len=%zu) -> %zd %d, "
"buffered=%zu, window=%d/%d, connection %d/%d",
stream->id, len, nread, *err,
Curl_bufq_len(&stream->recvbuf),
nghttp2_session_get_stream_effective_recv_data_length(
ctx->h2, stream->id),
nghttp2_session_get_stream_effective_local_window_size(
ctx->h2, stream->id),
nghttp2_session_get_local_window_size(ctx->h2),
HTTP2_HUGE_WINDOW_SIZE));
CF_DATA_RESTORE(cf, save);
return nread;
}
static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, CURLcode *err)
static ssize_t h2_submit(struct stream_ctx **pstream,
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 HTTP *stream = data->req.p.http;
struct stream_ctx *stream = NULL;
struct h1_req_parser h1;
struct dynhds h2_headers;
nghttp2_nv *nva = NULL;
size_t nheader;
size_t nheader, i;
nghttp2_data_provider data_prd;
int32_t stream_id;
nghttp2_priority_spec pri_spec;
CURLcode result;
struct h2h3req *hreq;
struct cf_call_data save;
ssize_t nwritten;
CF_DATA_SAVE(save, cf, data);
DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) start", len));
Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
if(stream->stream_id != -1) {
if(stream->close_handled) {
infof(data, "stream %u closed", stream->stream_id);
*err = CURLE_HTTP2_STREAM;
nwritten = -1;
goto out;
}
else if(stream->closed) {
nwritten = 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 = buf;
stream->upload_len = len;
rv = nghttp2_session_resume_data(ctx->h2, stream->stream_id);
if(nghttp2_is_fatal(rv)) {
*err = CURLE_SEND_ERROR;
nwritten = -1;
goto out;
}
result = h2_session_send(cf, data);
if(result) {
*err = result;
nwritten = -1;
goto out;
}
nwritten = (ssize_t)len - (ssize_t)stream->upload_len;
stream->upload_mem = NULL;
stream->upload_len = 0;
if(should_close_session(ctx)) {
DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
*err = CURLE_HTTP2;
nwritten = -1;
goto out;
}
if(stream->upload_left) {
/* we are sure that we have more data to send here. Calling the
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(ctx->h2, stream->stream_id);
}
if(!nwritten) {
size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2,
stream->stream_id);
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send: win %u/%zu",
stream->stream_id,
nghttp2_session_get_remote_window_size(ctx->h2), rwin));
if(rwin == 0) {
/* We cannot upload more as the stream's remote window size
* is 0. We need to receive WIN_UPDATEs before we can continue.
*/
data->req.keepon |= KEEP_SEND_HOLD;
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] holding send as remote flow "
"window is exhausted", stream->stream_id));
}
}
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send returns %zd ",
stream->stream_id, nwritten));
/* handled writing BODY for open stream. */
goto out;
}
/* Stream has not been opened yet. `buf` is expected to contain
* request headers. */
/* TODO: this assumes that the `buf` and `len` we are called with
* is *all* HEADERs and no body. We have no way to determine here
* if that is indeed the case. */
result = Curl_pseudo_headers(data, buf, len, NULL, &hreq);
if(result) {
*err = result;
*err = http2_data_setup(cf, data, &stream);
if(*err) {
nwritten = -1;
goto out;
}
nheader = hreq->entries;
nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err);
if(nwritten < 0)
goto out;
DEBUGASSERT(h1.done);
DEBUGASSERT(h1.req);
*err = Curl_http_req_to_h2(&h2_headers, h1.req, data);
if(*err) {
nwritten = -1;
goto out;
}
nheader = Curl_dynhds_count(&h2_headers);
nva = malloc(sizeof(nghttp2_nv) * nheader);
if(!nva) {
Curl_pseudo_free(hreq);
*err = CURLE_OUT_OF_MEMORY;
nwritten = -1;
goto out;
}
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 = NGHTTP2_NV_FLAG_NONE;
for(i = 0; i < nheader; ++i) {
struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
nva[i].name = (unsigned char *)e->name;
nva[i].namelen = e->namelen;
nva[i].value = (unsigned char *)e->value;
nva[i].valuelen = e->valuelen;
nva[i].flags = NGHTTP2_NV_FLAG_NONE;
}
#define MAX_ACC 60000 /* <64KB to account for some overhead */
{
size_t acc = 0;
for(i = 0; i < nheader; ++i) {
acc += nva[i].namelen + nva[i].valuelen;
infof(data, "h2 [%.*s: %.*s]",
(int)nva[i].namelen, nva[i].name,
(int)nva[i].valuelen, nva[i].value);
}
if(acc > MAX_ACC) {
infof(data, "http_request: Warning: The cumulative length of all "
"headers exceeds %d bytes and that could cause the "
"stream to be rejected.", MAX_ACC);
}
Curl_pseudo_free(hreq);
}
h2_pri_spec(data, &pri_spec);
@@ -2112,14 +1941,15 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
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_left = -1; /* unknown */
data_prd.read_callback = data_source_read_callback;
data_prd.read_callback = req_body_read_callback;
data_prd.source.ptr = NULL;
stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader,
&data_prd, data);
break;
default:
stream->upload_left = 0; /* no request body */
stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader,
NULL, data);
}
@@ -2134,37 +1964,167 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
goto out;
}
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send(len=%zu) submit %s",
stream_id, len, data->state.url));
infof(data, "Using Stream ID: %u (easy handle %p)",
stream_id, (void *)data);
stream->stream_id = stream_id;
/* See TODO above. We assume that the whole buf was consumed by
* generating the request headers. */
nwritten = len;
result = h2_session_send(cf, data);
if(result) {
*err = result;
nwritten = -1;
goto out;
}
if(should_close_session(ctx)) {
DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
*err = CURLE_HTTP2;
nwritten = -1;
goto out;
}
/* If whole HEADERS frame was sent off to the underlying socket, the nghttp2
library calls data_source_read_callback. But only it found that no data
available, so it deferred the DATA transmission. Which means that
nghttp2_session_want_write() returns 0 on http2_perform_getsock(), which
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(ctx->h2, stream->stream_id);
stream->id = stream_id;
out:
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] submit -> %zd, %d",
stream? stream->id : -1, nwritten, *err));
*pstream = stream;
Curl_h1_req_parse_free(&h1);
Curl_dynhds_free(&h2_headers);
return nwritten;
}
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;
struct stream_ctx *stream = H2_STREAM_CTX(data);
struct cf_call_data save;
int rv;
ssize_t nwritten;
CURLcode result;
CF_DATA_SAVE(save, cf, data);
if(stream && stream->id != -1) {
if(stream->close_handled) {
infof(data, "stream %u closed", stream->id);
*err = CURLE_HTTP2_STREAM;
nwritten = -1;
goto out;
}
else if(stream->closed) {
nwritten = 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 */
nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err);
if(nwritten < 0) {
if(*err != CURLE_AGAIN)
goto out;
nwritten = 0;
}
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] bufq_write(len=%zu) -> %zd, %d",
stream->id, len, nwritten, *err));
if(!Curl_bufq_is_empty(&stream->sendbuf)) {
rv = nghttp2_session_resume_data(ctx->h2, stream->id);
if(nghttp2_is_fatal(rv)) {
*err = CURLE_SEND_ERROR;
nwritten = -1;
goto out;
}
}
result = h2_progress_ingress(cf, data);
if(result) {
*err = result;
nwritten = -1;
goto out;
}
result = h2_progress_egress(cf, data);
if(result) {
*err = result;
nwritten = -1;
goto out;
}
if(should_close_session(ctx)) {
if(stream->closed) {
nwritten = http2_handle_stream_close(cf, data, stream, err);
}
else {
DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
*err = CURLE_HTTP2;
nwritten = -1;
}
goto out;
}
if(!nwritten) {
size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2,
stream->id);
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send: win %u/%zu",
stream->id,
nghttp2_session_get_remote_window_size(ctx->h2), rwin));
if(rwin == 0) {
/* We cannot upload more as the stream's remote window size
* is 0. We need to receive WIN_UPDATEs before we can continue.
*/
data->req.keepon |= KEEP_SEND_HOLD;
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] holding send as remote flow "
"window is exhausted", stream->id));
}
nwritten = -1;
*err = CURLE_AGAIN;
}
/* handled writing BODY for open stream. */
goto out;
}
else {
nwritten = h2_submit(&stream, cf, data, buf, len, err);
if(nwritten < 0) {
goto out;
}
result = h2_progress_ingress(cf, data);
if(result) {
*err = result;
nwritten = -1;
goto out;
}
result = h2_progress_egress(cf, data);
if(result) {
*err = result;
nwritten = -1;
goto out;
}
if(should_close_session(ctx)) {
if(stream->closed) {
nwritten = http2_handle_stream_close(cf, data, stream, err);
}
else {
DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
*err = CURLE_HTTP2;
nwritten = -1;
}
goto out;
}
}
out:
if(stream) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send(len=%zu) -> %zd, %d, "
"buffered=%zu, upload_left=%zu, stream-window=%d, "
"connection-window=%d",
stream->id, len, nwritten, *err,
Curl_bufq_len(&stream->sendbuf),
(ssize_t)stream->upload_left,
nghttp2_session_get_stream_remote_window_size(
ctx->h2, stream->id),
nghttp2_session_get_remote_window_size(ctx->h2)));
drain_stream(cf, data, stream);
}
else {
DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) -> %zd, %d, "
"connection-window=%d",
len, nwritten, *err,
nghttp2_session_get_remote_window_size(ctx->h2)));
}
CF_DATA_RESTORE(cf, save);
return nwritten;
}
@@ -2175,14 +2135,14 @@ static int cf_h2_get_select_socks(struct Curl_cfilter *cf,
{
struct cf_h2_ctx *ctx = cf->ctx;
struct SingleRequest *k = &data->req;
struct HTTP *stream = data->req.p.http;
struct stream_ctx *stream = H2_STREAM_CTX(data);
int bitmap = GETSOCK_BLANK;
struct cf_call_data save;
CF_DATA_SAVE(save, cf, data);
sock[0] = Curl_conn_cf_get_socket(cf, data);
if(!(k->keepon & KEEP_RECV_PAUSE))
if(!(k->keepon & (KEEP_RECV_PAUSE|KEEP_RECV_HOLD)))
/* Unless paused - in an HTTP/2 connection we can basically always get a
frame so we should always be ready for one */
bitmap |= GETSOCK_READSOCK(0);
@@ -2193,7 +2153,7 @@ static int cf_h2_get_select_socks(struct Curl_cfilter *cf,
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)))
stream->id)))
bitmap |= GETSOCK_WRITESOCK(0);
CF_DATA_RESTORE(cf, save);
@@ -2230,10 +2190,13 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
goto out;
}
if(-1 == h2_process_pending_input(cf, data, &result)) {
result = CURLE_HTTP2;
result = h2_progress_ingress(cf, data);
if(result)
goto out;
result = h2_progress_egress(cf, data);
if(result)
goto out;
}
*done = TRUE;
cf->connected = TRUE;
@@ -2272,18 +2235,18 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool pause)
{
#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
struct cf_h2_ctx *ctx = cf->ctx;
struct stream_ctx *stream = H2_STREAM_CTX(data);
DEBUGASSERT(data);
#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
if(ctx && ctx->h2) {
struct HTTP *stream = data->req.p.http;
uint32_t window = !pause * HTTP2_HUGE_WINDOW_SIZE;
if(ctx && ctx->h2 && stream) {
uint32_t window = !pause * H2_STREAM_WINDOW_SIZE;
CURLcode result;
int rv = nghttp2_session_set_local_window_size(ctx->h2,
NGHTTP2_FLAG_NONE,
stream->stream_id,
stream->id,
window);
if(rv) {
failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
@@ -2291,22 +2254,34 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf,
return CURLE_HTTP2;
}
if(!pause)
drain_stream(cf, data, stream);
/* make sure the window update gets sent */
result = h2_session_send(cf, data);
result = h2_progress_egress(cf, data);
if(result)
return result;
if(!pause) {
/* Unpausing a h2 transfer, requires it to be run again. The server
* may send new DATA on us increasing the flow window, and it may
* not. We may have already buffered and exhausted the new window
* by operating on things in flight during the handling of other
* transfers. */
drain_stream(cf, data, stream);
Curl_expire(data, 0, EXPIRE_RUN_NOW);
}
DEBUGF(infof(data, "Set HTTP/2 window size to %u for stream %u",
window, stream->stream_id));
window, stream->id));
#ifdef DEBUGBUILD
{
/* read out the stream local window again */
uint32_t window2 =
nghttp2_session_get_stream_local_window_size(ctx->h2,
stream->stream_id);
stream->id);
DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u",
window2, stream->stream_id));
window2, stream->id));
}
#endif
}
@@ -2325,14 +2300,11 @@ static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf,
CF_DATA_SAVE(save, cf, data);
switch(event) {
case CF_CTRL_DATA_SETUP: {
result = http2_data_setup(cf, data);
case CF_CTRL_DATA_SETUP:
break;
}
case CF_CTRL_DATA_PAUSE: {
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;
@@ -2352,7 +2324,11 @@ 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)
struct stream_ctx *stream = H2_STREAM_CTX(data);
if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq)
|| (stream && !Curl_bufq_is_empty(&stream->sendbuf))
|| (stream && !Curl_bufq_is_empty(&stream->recvbuf))))
return TRUE;
return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
}
@@ -2487,7 +2463,8 @@ out:
return result;
}
bool Curl_cf_is_http2(struct Curl_cfilter *cf, const struct Curl_easy *data)
static bool Curl_cf_is_http2(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
(void)data;
for(; cf; cf = cf->next) {
@@ -2606,23 +2583,26 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
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);
if(nread > 0) {
/* Remaining data from the protocol switch reply is already using
* the switched protocol, ie. HTTP/2. We add that to the network
* inbufq. */
ssize_t copied;
copied = Curl_bufq_write(&ctx->inbufq,
(const unsigned char *)mem, nread, &result);
if(copied < 0) {
failf(data, "error on copying HTTP Upgrade response: %d", result);
return CURLE_RECV_ERROR;
}
if((size_t)copied < nread) {
failf(data, "connection buffer size could not take all data "
"from HTTP Upgrade response header: copied=%zd, datalen=%zu",
copied, nread);
return CURLE_HTTP2;
}
infof(data, "Copying HTTP/2 data in stream buffer to connection buffer"
infof(data, "Copied 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;
}
conn->httpversion = 20; /* we know we're on HTTP/2 now */
@@ -2641,7 +2621,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
CURLE_HTTP2_STREAM error! */
bool Curl_h2_http_1_1_error(struct Curl_easy *data)
{
struct HTTP *stream = data->req.p.http;
struct stream_ctx *stream = H2_STREAM_CTX(data);
return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
}
-4
View File
@@ -38,8 +38,6 @@
*/
void Curl_http2_ver(char *p, size_t len);
const char *Curl_http2_strerror(uint32_t err);
CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
struct Curl_easy *data);
@@ -49,8 +47,6 @@ 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);
+1 -1
View File
@@ -192,7 +192,7 @@ static CURLcode make_headers(struct Curl_easy *data,
}
if (*content_sha256_header) {
if(*content_sha256_header) {
tmp_head = curl_slist_append(head, content_sha256_header);
if(!tmp_head)
goto fail;
+89 -1300
View File
@@ -26,7 +26,7 @@
#include "http_proxy.h"
#if !defined(CURL_DISABLE_PROXY)
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
#include <curl/curl.h>
#ifdef USE_HYPER
@@ -38,6 +38,8 @@
#include "select.h"
#include "progress.h"
#include "cfilters.h"
#include "cf-h1-proxy.h"
#include "cf-h2-proxy.h"
#include "connect.h"
#include "curlx.h"
#include "vtls/vtls.h"
@@ -50,1023 +52,17 @@
#include "memdebug.h"
#if !defined(CURL_DISABLE_HTTP)
typedef enum {
TUNNEL_INIT, /* init/default/no tunnel state */
TUNNEL_CONNECT, /* CONNECT request is being send */
TUNNEL_RECEIVE, /* CONNECT answer is being received */
TUNNEL_RESPONSE, /* CONNECT response received completely */
TUNNEL_ESTABLISHED,
TUNNEL_FAILED
} tunnel_state;
/* struct for HTTP CONNECT tunneling */
struct tunnel_state {
int sockindex;
const char *hostname;
int remote_port;
struct HTTP CONNECT;
struct dynbuf rcvbuf;
struct dynbuf req;
size_t nsend;
size_t headerlines;
enum keeponval {
KEEPON_DONE,
KEEPON_CONNECT,
KEEPON_IGNORE
} keepon;
curl_off_t cl; /* size of content to read and ignore */
tunnel_state tunnel_state;
BIT(chunked_encoding);
BIT(close_connection);
struct cf_proxy_ctx {
/* the protocol specific sub-filter we install during connect */
struct Curl_cfilter *cf_protocol;
};
static bool tunnel_is_established(struct tunnel_state *ts)
{
return ts && (ts->tunnel_state == TUNNEL_ESTABLISHED);
}
static bool tunnel_is_failed(struct tunnel_state *ts)
{
return ts && (ts->tunnel_state == TUNNEL_FAILED);
}
static CURLcode tunnel_reinit(struct tunnel_state *ts,
struct connectdata *conn,
struct Curl_easy *data)
{
(void)data;
DEBUGASSERT(ts);
Curl_dyn_reset(&ts->rcvbuf);
Curl_dyn_reset(&ts->req);
ts->tunnel_state = TUNNEL_INIT;
ts->keepon = KEEPON_CONNECT;
ts->cl = 0;
ts->close_connection = FALSE;
if(conn->bits.conn_to_host)
ts->hostname = conn->conn_to_host.name;
else if(ts->sockindex == SECONDARYSOCKET)
ts->hostname = conn->secondaryhostname;
else
ts->hostname = conn->host.name;
if(ts->sockindex == SECONDARYSOCKET)
ts->remote_port = conn->secondary_port;
else if(conn->bits.conn_to_port)
ts->remote_port = conn->conn_to_port;
else
ts->remote_port = conn->remote_port;
return CURLE_OK;
}
static CURLcode tunnel_init(struct tunnel_state **pts,
struct Curl_easy *data,
struct connectdata *conn,
int sockindex)
{
struct tunnel_state *ts;
CURLcode result;
if(conn->handler->flags & PROTOPT_NOTCPPROXY) {
failf(data, "%s cannot be done over CONNECT", conn->handler->scheme);
return CURLE_UNSUPPORTED_PROTOCOL;
}
/* we might need the upload buffer for streaming a partial request */
result = Curl_get_upload_buffer(data);
if(result)
return result;
ts = calloc(1, sizeof(*ts));
if(!ts)
return CURLE_OUT_OF_MEMORY;
ts->sockindex = sockindex;
infof(data, "allocate connect buffer");
Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST);
*pts = ts;
connkeep(conn, "HTTP proxy CONNECT");
return tunnel_reinit(ts, conn, data);
}
static void tunnel_go_state(struct Curl_cfilter *cf,
struct tunnel_state *ts,
tunnel_state new_state,
struct Curl_easy *data)
{
if(ts->tunnel_state == new_state)
return;
/* leaving this one */
switch(ts->tunnel_state) {
case TUNNEL_CONNECT:
data->req.ignorebody = FALSE;
break;
default:
break;
}
/* 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->info.httpcode = 0; /* clear it as it might've been used for the
proxy */
/* If a proxy-authorization header was used for the proxy, then we should
make sure that it isn't accidentally used for the document request
after we've connected. So let's free and clear it here. */
Curl_safefree(data->state.aptr.proxyuserpwd);
data->state.aptr.proxyuserpwd = NULL;
#ifdef USE_HYPER
data->state.hconnect = FALSE;
#endif
break;
}
}
static void tunnel_free(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct tunnel_state *ts = cf->ctx;
if(ts) {
tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
Curl_dyn_free(&ts->rcvbuf);
Curl_dyn_free(&ts->req);
free(ts);
cf->ctx = NULL;
}
}
static CURLcode CONNECT_host(struct Curl_easy *data,
struct connectdata *conn,
const char *hostname,
int remote_port,
char **connecthostp,
char **hostp)
{
char *hostheader; /* for CONNECT */
char *host = NULL; /* Host: */
bool ipv6_ip = conn->bits.ipv6_ip;
/* the hostname may be different */
if(hostname != conn->host.name)
ipv6_ip = (strchr(hostname, ':') != NULL);
hostheader = /* host:port with IPv6 support */
aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
remote_port);
if(!hostheader)
return CURLE_OUT_OF_MEMORY;
if(!Curl_checkProxyheaders(data, conn, STRCONST("Host"))) {
host = aprintf("Host: %s\r\n", hostheader);
if(!host) {
free(hostheader);
return CURLE_OUT_OF_MEMORY;
}
}
*connecthostp = hostheader;
*hostp = host;
return CURLE_OK;
}
#ifndef USE_HYPER
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;
CURLcode result;
infof(data, "Establish HTTP proxy tunnel to %s:%d",
ts->hostname, ts->remote_port);
/* This only happens if we've looped here due to authentication
reasons, and we don't really use the newly cloned URL here
then. Just free() it. */
Curl_safefree(data->req.newurl);
result = CONNECT_host(data, conn,
ts->hostname, ts->remote_port,
&hostheader, &host);
if(result)
goto out;
/* Setup the proxy-authorization header, if any */
result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
hostheader, TRUE);
if(result)
goto out;
httpv = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? "1.0" : "1.1";
result =
Curl_dyn_addf(&ts->req,
"CONNECT %s HTTP/%s\r\n"
"%s" /* Host: */
"%s", /* Proxy-Authorization */
hostheader,
httpv,
host?host:"",
data->state.aptr.proxyuserpwd?
data->state.aptr.proxyuserpwd:"");
if(result)
goto out;
if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent"))
&& data->set.str[STRING_USERAGENT])
result = Curl_dyn_addf(&ts->req, "User-Agent: %s\r\n",
data->set.str[STRING_USERAGENT]);
if(result)
goto out;
if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection")))
result = Curl_dyn_addn(&ts->req,
STRCONST("Proxy-Connection: Keep-Alive\r\n"));
if(result)
goto out;
result = Curl_add_custom_headers(data, TRUE, &ts->req);
if(result)
goto out;
/* CRLF terminate the request */
result = Curl_dyn_addn(&ts->req, STRCONST("\r\n"));
if(result)
goto out;
/* Send the connect request to the proxy */
result = Curl_buffer_send(&ts->req, data, &ts->CONNECT,
&data->info.request_size, 0,
ts->sockindex);
ts->headerlines = 0;
out:
if(result)
failf(data, "Failed sending CONNECT to proxy");
free(host);
free(hostheader);
return result;
}
static CURLcode send_CONNECT(struct Curl_easy *data,
struct connectdata *conn,
struct tunnel_state *ts,
bool *done)
{
struct SingleRequest *k = &data->req;
struct HTTP *http = &ts->CONNECT;
CURLcode result = CURLE_OK;
if(http->sending != HTTPSEND_REQUEST)
goto out;
if(!ts->nsend) {
size_t fillcount;
k->upload_fromhere = data->state.ulbuf;
result = Curl_fillreadbuffer(data, data->set.upload_buffer_size,
&fillcount);
if(result)
goto out;
ts->nsend = fillcount;
}
if(ts->nsend) {
ssize_t bytes_written;
/* write to socket (send away data) */
result = Curl_write(data,
conn->writesockfd, /* socket to send to */
k->upload_fromhere, /* buffer pointer */
ts->nsend, /* buffer size */
&bytes_written); /* actually sent */
if(result)
goto out;
/* send to debug callback! */
Curl_debug(data, CURLINFO_HEADER_OUT,
k->upload_fromhere, bytes_written);
ts->nsend -= bytes_written;
k->upload_fromhere += bytes_written;
}
if(!ts->nsend)
http->sending = HTTPSEND_NADA;
out:
if(result)
failf(data, "Failed sending CONNECT to proxy");
*done = (http->sending != HTTPSEND_REQUEST);
return result;
}
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;
(void)cf;
if((checkprefix("WWW-Authenticate:", header) &&
(401 == k->httpcode)) ||
(checkprefix("Proxy-authenticate:", header) &&
(407 == k->httpcode))) {
bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
char *auth = Curl_copy_header_value(header);
if(!auth)
return CURLE_OUT_OF_MEMORY;
DEBUGF(LOG_CF(data, cf, "CONNECT: fwd auth header '%s'", header));
result = Curl_http_input_auth(data, proxy, auth);
free(auth);
if(result)
return result;
}
else if(checkprefix("Content-Length:", header)) {
if(k->httpcode/100 == 2) {
/* A client MUST ignore any Content-Length or Transfer-Encoding
header fields received in a successful response to CONNECT.
"Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
infof(data, "Ignoring Content-Length in CONNECT %03d response",
k->httpcode);
}
else {
(void)curlx_strtoofft(header + strlen("Content-Length:"),
NULL, 10, &ts->cl);
}
}
else if(Curl_compareheader(header,
STRCONST("Connection:"), STRCONST("close")))
ts->close_connection = TRUE;
else if(checkprefix("Transfer-Encoding:", header)) {
if(k->httpcode/100 == 2) {
/* A client MUST ignore any Content-Length or Transfer-Encoding
header fields received in a successful response to CONNECT.
"Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
infof(data, "Ignoring Transfer-Encoding in "
"CONNECT %03d response", k->httpcode);
}
else if(Curl_compareheader(header,
STRCONST("Transfer-Encoding:"),
STRCONST("chunked"))) {
infof(data, "CONNECT responded chunked");
ts->chunked_encoding = TRUE;
/* init our chunky engine */
Curl_httpchunk_init(data);
}
}
else if(Curl_compareheader(header,
STRCONST("Proxy-Connection:"),
STRCONST("close")))
ts->close_connection = TRUE;
else if(!strncmp(header, "HTTP/1.", 7) &&
((header[7] == '0') || (header[7] == '1')) &&
(header[8] == ' ') &&
ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
!ISDIGIT(header[12])) {
/* store the HTTP code from the proxy */
data->info.httpproxycode = k->httpcode = (header[9] - '0') * 100 +
(header[10] - '0') * 10 + (header[11] - '0');
}
return result;
}
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 = Curl_conn_cf_get_socket(cf, data);
char *linep;
size_t perline;
int error;
#define SELECT_OK 0
#define SELECT_ERROR 1
error = SELECT_OK;
*done = FALSE;
if(!Curl_conn_data_pending(data, ts->sockindex))
return CURLE_OK;
while(ts->keepon) {
ssize_t gotbytes;
char byte;
/* Read one byte at a time to avoid a race condition. Wait at most one
second before looping to ensure continuous pgrsUpdates. */
result = Curl_read(data, tunnelsocket, &byte, 1, &gotbytes);
if(result == CURLE_AGAIN)
/* socket buffer drained, return */
return CURLE_OK;
if(Curl_pgrsUpdate(data))
return CURLE_ABORTED_BY_CALLBACK;
if(result) {
ts->keepon = KEEPON_DONE;
break;
}
if(gotbytes <= 0) {
if(data->set.proxyauth && data->state.authproxy.avail &&
data->state.aptr.proxyuserpwd) {
/* proxy auth was requested and there was proxy auth available,
then deem this as "mere" proxy disconnect */
ts->close_connection = TRUE;
infof(data, "Proxy CONNECT connection closed");
}
else {
error = SELECT_ERROR;
failf(data, "Proxy CONNECT aborted");
}
ts->keepon = KEEPON_DONE;
break;
}
if(ts->keepon == KEEPON_IGNORE) {
/* This means we are currently ignoring a response-body */
if(ts->cl) {
/* A Content-Length based body: simply count down the counter
and make sure to break out of the loop when we're done! */
ts->cl--;
if(ts->cl <= 0) {
ts->keepon = KEEPON_DONE;
break;
}
}
else {
/* chunked-encoded body, so we need to do the chunked dance
properly to know when the end of the body is reached */
CHUNKcode r;
CURLcode extra;
ssize_t tookcareof = 0;
/* now parse the chunked piece of data so that we can
properly tell when the stream ends */
r = Curl_httpchunk_read(data, &byte, 1, &tookcareof, &extra);
if(r == CHUNKE_STOP) {
/* we're done reading chunks! */
infof(data, "chunk reading DONE");
ts->keepon = KEEPON_DONE;
}
}
continue;
}
if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) {
failf(data, "CONNECT response too large");
return CURLE_RECV_ERROR;
}
/* if this is not the end of a header line then continue */
if(byte != 0x0a)
continue;
ts->headerlines++;
linep = Curl_dyn_ptr(&ts->rcvbuf);
perline = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */
/* output debug if that is requested */
Curl_debug(data, CURLINFO_HEADER_IN, linep, perline);
if(!data->set.suppress_connect_headers) {
/* send the header to the callback */
int writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
(data->set.include_header ? CLIENTWRITE_BODY : 0) |
(ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
result = Curl_client_write(data, writetype, linep, perline);
if(result)
return result;
}
data->info.header_size += (long)perline;
/* Newlines are CRLF, so the CR is ignored as the line isn't
really terminated until the LF comes. Treat a following CR
as end-of-headers as well.*/
if(('\r' == linep[0]) ||
('\n' == linep[0])) {
/* end of response-headers from the proxy */
if((407 == k->httpcode) && !data->state.authproblem) {
/* If we get a 407 response code with content length
when we have no auth problem, we must ignore the
whole response-body */
ts->keepon = KEEPON_IGNORE;
if(ts->cl) {
infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
" bytes of response-body", ts->cl);
}
else if(ts->chunked_encoding) {
CHUNKcode r;
CURLcode extra;
infof(data, "Ignore chunked response-body");
/* We set ignorebody true here since the chunked decoder
function will acknowledge that. Pay attention so that this is
cleared again when this function returns! */
k->ignorebody = TRUE;
if(linep[1] == '\n')
/* this can only be a LF if the letter at index 0 was a CR */
linep++;
/* now parse the chunked piece of data so that we can properly
tell when the stream ends */
r = Curl_httpchunk_read(data, linep + 1, 1, &gotbytes,
&extra);
if(r == CHUNKE_STOP) {
/* we're done reading chunks! */
infof(data, "chunk reading DONE");
ts->keepon = KEEPON_DONE;
}
}
else {
/* without content-length or chunked encoding, we
can't keep the connection alive since the close is
the end signal so we bail out at once instead */
DEBUGF(LOG_CF(data, cf, "CONNECT: no content-length or chunked"));
ts->keepon = KEEPON_DONE;
}
}
else {
ts->keepon = KEEPON_DONE;
}
DEBUGASSERT(ts->keepon == KEEPON_IGNORE
|| ts->keepon == KEEPON_DONE);
continue;
}
result = on_resp_header(cf, data, ts, linep);
if(result)
return result;
Curl_dyn_reset(&ts->rcvbuf);
} /* while there's buffer left and loop is requested */
if(error)
result = CURLE_RECV_ERROR;
*done = (ts->keepon == KEEPON_DONE);
if(!result && *done && data->info.httpproxycode/100 != 2) {
/* Deal with the possibly already received authenticate
headers. 'newurl' is set to a new URL if we must loop. */
result = Curl_http_auth_act(data);
}
return result;
}
#else /* USE_HYPER */
/* The Hyper version of CONNECT */
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 = Curl_conn_cf_get_socket(cf, data);
hyper_io *io = NULL;
hyper_request *req = NULL;
hyper_headers *headers = NULL;
hyper_clientconn_options *options = NULL;
hyper_task *handshake = NULL;
hyper_task *task = NULL; /* for the handshake */
hyper_clientconn *client = NULL;
hyper_task *sendtask = NULL; /* for the send */
char *hostheader = NULL; /* for CONNECT */
char *host = NULL; /* Host: */
CURLcode result = CURLE_OUT_OF_MEMORY;
io = hyper_io_new();
if(!io) {
failf(data, "Couldn't create hyper IO");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
/* tell Hyper how to read/write network data */
hyper_io_set_userdata(io, data);
hyper_io_set_read(io, Curl_hyper_recv);
hyper_io_set_write(io, Curl_hyper_send);
conn->sockfd = tunnelsocket;
data->state.hconnect = TRUE;
/* create an executor to poll futures */
if(!h->exec) {
h->exec = hyper_executor_new();
if(!h->exec) {
failf(data, "Couldn't create hyper executor");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
}
options = hyper_clientconn_options_new();
hyper_clientconn_options_set_preserve_header_case(options, 1);
hyper_clientconn_options_set_preserve_header_order(options, 1);
if(!options) {
failf(data, "Couldn't create hyper client options");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
hyper_clientconn_options_exec(options, h->exec);
/* "Both the `io` and the `options` are consumed in this function
call" */
handshake = hyper_clientconn_handshake(io, options);
if(!handshake) {
failf(data, "Couldn't create hyper client handshake");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
io = NULL;
options = NULL;
if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) {
failf(data, "Couldn't hyper_executor_push the handshake");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
handshake = NULL; /* ownership passed on */
task = hyper_executor_poll(h->exec);
if(!task) {
failf(data, "Couldn't hyper_executor_poll the handshake");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
client = hyper_task_value(task);
hyper_task_free(task);
req = hyper_request_new();
if(!req) {
failf(data, "Couldn't hyper_request_new");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(hyper_request_set_method(req, (uint8_t *)"CONNECT",
strlen("CONNECT"))) {
failf(data, "error setting method");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
infof(data, "Establish HTTP proxy tunnel to %s:%d",
ts->hostname, ts->remote_port);
/* This only happens if we've looped here due to authentication
reasons, and we don't really use the newly cloned URL here
then. Just free() it. */
Curl_safefree(data->req.newurl);
result = CONNECT_host(data, conn, ts->hostname, ts->remote_port,
&hostheader, &host);
if(result)
goto error;
if(hyper_request_set_uri(req, (uint8_t *)hostheader,
strlen(hostheader))) {
failf(data, "error setting path");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(data->set.verbose) {
char *se = aprintf("CONNECT %s HTTP/1.1\r\n", hostheader);
if(!se) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
Curl_debug(data, CURLINFO_HEADER_OUT, se, strlen(se));
free(se);
}
/* Setup the proxy-authorization header, if any */
result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
hostheader, TRUE);
if(result)
goto error;
Curl_safefree(hostheader);
/* default is 1.1 */
if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) &&
(HYPERE_OK != hyper_request_set_version(req,
HYPER_HTTP_VERSION_1_0))) {
failf(data, "error setting HTTP version");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
headers = hyper_request_headers(req);
if(!headers) {
failf(data, "hyper_request_headers");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(host) {
result = Curl_hyper_header(data, headers, host);
if(result)
goto error;
Curl_safefree(host);
}
if(data->state.aptr.proxyuserpwd) {
result = Curl_hyper_header(data, headers,
data->state.aptr.proxyuserpwd);
if(result)
goto error;
}
if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) &&
data->set.str[STRING_USERAGENT]) {
struct dynbuf ua;
Curl_dyn_init(&ua, DYN_HTTP_REQUEST);
result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n",
data->set.str[STRING_USERAGENT]);
if(result)
goto error;
result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&ua));
if(result)
goto error;
Curl_dyn_free(&ua);
}
if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) {
result = Curl_hyper_header(data, headers,
"Proxy-Connection: Keep-Alive");
if(result)
goto error;
}
result = Curl_add_custom_headers(data, TRUE, headers);
if(result)
goto error;
sendtask = hyper_clientconn_send(client, req);
if(!sendtask) {
failf(data, "hyper_clientconn_send");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) {
failf(data, "Couldn't hyper_executor_push the send");
result = CURLE_OUT_OF_MEMORY;
goto error;
}
error:
free(host);
free(hostheader);
if(io)
hyper_io_free(io);
if(options)
hyper_clientconn_options_free(options);
if(handshake)
hyper_task_free(handshake);
if(client)
hyper_clientconn_free(client);
return result;
}
static CURLcode send_CONNECT(struct Curl_easy *data,
struct connectdata *conn,
struct tunnel_state *ts,
bool *done)
{
struct hyptransfer *h = &data->hyp;
hyper_task *task = NULL;
hyper_error *hypererr = NULL;
CURLcode result = CURLE_OK;
(void)ts;
(void)conn;
do {
task = hyper_executor_poll(h->exec);
if(task) {
bool error = hyper_task_type(task) == HYPER_TASK_ERROR;
if(error)
hypererr = hyper_task_value(task);
hyper_task_free(task);
if(error) {
/* this could probably use a better error code? */
result = CURLE_OUT_OF_MEMORY;
goto error;
}
}
} while(task);
error:
*done = (result == CURLE_OK);
if(hypererr) {
uint8_t errbuf[256];
size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf));
failf(data, "Hyper: %.*s", (int)errlen, errbuf);
hyper_error_free(hypererr);
}
return result;
}
static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct tunnel_state *ts,
bool *done)
{
struct hyptransfer *h = &data->hyp;
CURLcode result;
int didwhat;
(void)ts;
*done = FALSE;
result = Curl_hyper_stream(data, cf->conn, &didwhat, done,
CURL_CSELECT_IN | CURL_CSELECT_OUT);
if(result || !*done)
return result;
if(h->exec) {
hyper_executor_free(h->exec);
h->exec = NULL;
}
if(h->read_waker) {
hyper_waker_free(h->read_waker);
h->read_waker = NULL;
}
if(h->write_waker) {
hyper_waker_free(h->write_waker);
h->write_waker = NULL;
}
return result;
}
#endif /* USE_HYPER */
static CURLcode CONNECT(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct tunnel_state *ts)
{
struct connectdata *conn = cf->conn;
CURLcode result;
bool done;
if(tunnel_is_established(ts))
return CURLE_OK;
if(tunnel_is_failed(ts))
return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
do {
timediff_t check;
check = Curl_timeleft(data, NULL, TRUE);
if(check <= 0) {
failf(data, "Proxy CONNECT aborted due to timeout");
result = CURLE_OPERATION_TIMEDOUT;
goto out;
}
switch(ts->tunnel_state) {
case TUNNEL_INIT:
/* Prepare the CONNECT request and make a first attempt to send. */
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);
/* FALLTHROUGH */
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;
tunnel_go_state(cf, ts, TUNNEL_RECEIVE, data);
/* FALLTHROUGH */
case TUNNEL_RECEIVE:
/* read what is there */
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;
}
/* error or not complete yet. return for more multi-multi */
if(result || !done)
goto out;
/* got it */
tunnel_go_state(cf, ts, TUNNEL_RESPONSE, data);
/* 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.
*/
if(ts->close_connection || conn->bits.close) {
/* Close this filter and the sub-chain, re-connect the
* sub-chain and continue. Closing this filter will
* 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_cf_close(cf, data);
connkeep(conn, "HTTP proxy CONNECT");
result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
goto out;
}
else {
/* staying on this connection, reset state */
tunnel_go_state(cf, ts, TUNNEL_INIT, data);
}
}
break;
default:
break;
}
} while(data->req.newurl);
DEBUGASSERT(ts->tunnel_state == TUNNEL_RESPONSE);
if(data->info.httpproxycode/100 != 2) {
/* a non-2xx response and we have no next url to try. */
free(data->req.newurl);
data->req.newurl = NULL;
/* failure, close this connection to avoid re-use */
streamclose(conn, "proxy CONNECT failure");
tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
return CURLE_RECV_ERROR;
}
/* 2xx response, SUCCESS! */
tunnel_go_state(cf, ts, TUNNEL_ESTABLISHED, data);
infof(data, "CONNECT tunnel established, response %d",
data->info.httpproxycode);
result = CURLE_OK;
out:
if(result)
tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
return result;
}
static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
{
struct cf_proxy_ctx *ctx = cf->ctx;
CURLcode result;
struct tunnel_state *ts = cf->ctx;
if(cf->connected) {
*done = TRUE;
@@ -1074,44 +70,74 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
}
DEBUGF(LOG_CF(data, cf, "connect"));
connect_sub:
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 */
/* for the secondary socket (FTP), use the "connect to host"
* but ignore the "connect to port" (use the secondary port)
*/
*done = FALSE;
if(!ts) {
result = tunnel_init(&ts, data, cf->conn, cf->sockindex);
if(result)
return result;
cf->ctx = ts;
if(!ctx->cf_protocol) {
struct Curl_cfilter *cf_protocol = NULL;
int alpn = Curl_conn_cf_is_ssl(cf->next)?
cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1;
/* First time call after the subchain connected */
switch(alpn) {
case CURL_HTTP_VERSION_NONE:
case CURL_HTTP_VERSION_1_0:
case CURL_HTTP_VERSION_1_1:
DEBUGF(LOG_CF(data, cf, "installing subfilter for HTTP/1.1"));
infof(data, "CONNECT tunnel: HTTP/1.%d negotiated",
(alpn == CURL_HTTP_VERSION_1_0)? 0 : 1);
result = Curl_cf_h1_proxy_insert_after(cf, data);
if(result)
goto out;
cf_protocol = cf->next;
break;
#ifdef USE_NGHTTP2
case CURL_HTTP_VERSION_2:
DEBUGF(LOG_CF(data, cf, "installing subfilter for HTTP/2"));
infof(data, "CONNECT tunnel: HTTP/2 negotiated");
result = Curl_cf_h2_proxy_insert_after(cf, data);
if(result)
goto out;
cf_protocol = cf->next;
break;
#endif
default:
DEBUGF(LOG_CF(data, cf, "installing subfilter for default HTTP/1.1"));
infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn);
result = CURLE_COULDNT_CONNECT;
goto out;
}
ctx->cf_protocol = cf_protocol;
/* after we installed the filter "below" us, we call connect
* on out sub-chain again.
*/
goto connect_sub;
}
else {
/* subchain connected and we had already installed the protocol filter.
* This means the protocol tunnel is established, we are done.
*/
DEBUGASSERT(ctx->cf_protocol);
result = CURLE_OK;
}
result = CONNECT(cf, data, ts);
if(result)
goto out;
Curl_safefree(data->state.aptr.proxyuserpwd);
out:
*done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
if (*done) {
if(!result) {
cf->connected = TRUE;
tunnel_free(cf, data);
*done = TRUE;
}
return result;
}
static void http_proxy_cf_get_host(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char **phost,
const char **pdisplay_host,
int *pport)
void Curl_cf_http_proxy_get_host(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char **phost,
const char **pdisplay_host,
int *pport)
{
(void)data;
if(!cf->connected) {
@@ -1124,50 +150,38 @@ static void http_proxy_cf_get_host(struct Curl_cfilter *cf,
}
}
static int http_proxy_cf_get_select_socks(struct Curl_cfilter *cf,
struct Curl_easy *data,
curl_socket_t *socks)
{
struct tunnel_state *ts = cf->ctx;
int fds;
fds = cf->next->cft->get_select_socks(cf->next, data, socks);
if(!fds && cf->next->connected && !cf->connected) {
/* If we are not connected, but the filter "below" is
* and not waiting on something, we are tunneling. */
socks[0] = Curl_conn_cf_get_socket(cf, data);
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->CONNECT.sending == HTTPSEND_REQUEST) {
return GETSOCK_WRITESOCK(0);
}
return GETSOCK_READSOCK(0);
}
return GETSOCK_WRITESOCK(0);
}
return fds;
}
static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_proxy_ctx *ctx = cf->ctx;
(void)data;
DEBUGF(LOG_CF(data, cf, "destroy"));
tunnel_free(cf, data);
free(ctx);
}
static void http_proxy_cf_close(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
DEBUGASSERT(cf->next);
struct cf_proxy_ctx *ctx = cf->ctx;
DEBUGF(LOG_CF(data, cf, "close"));
cf->connected = FALSE;
cf->next->cft->close(cf->next, data);
if(cf->ctx) {
tunnel_go_state(cf, cf->ctx, TUNNEL_INIT, data);
if(ctx->cf_protocol) {
struct Curl_cfilter *f;
/* if someone already removed it, we assume he also
* took care of destroying it. */
for(f = cf->next; f; f = f->next) {
if(f == ctx->cf_protocol) {
/* still in our sub-chain */
Curl_conn_cf_discard_sub(cf, ctx->cf_protocol, data, FALSE);
break;
}
}
ctx->cf_protocol = NULL;
}
if(cf->next)
cf->next->cft->close(cf->next, data);
}
@@ -1178,8 +192,8 @@ struct Curl_cftype Curl_cft_http_proxy = {
http_proxy_cf_destroy,
http_proxy_cf_connect,
http_proxy_cf_close,
http_proxy_cf_get_host,
http_proxy_cf_get_select_socks,
Curl_cf_http_proxy_get_host,
Curl_cf_def_get_select_socks,
Curl_cf_def_data_pending,
Curl_cf_def_send,
Curl_cf_def_recv,
@@ -1189,253 +203,28 @@ struct Curl_cftype Curl_cft_http_proxy = {
Curl_cf_def_query,
};
CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex)
{
struct Curl_cfilter *cf;
CURLcode result;
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;
struct cf_proxy_ctx *ctx = NULL;
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 */
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)
{
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;
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(&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(&ctx->data_out, "PROXY %s %s %s %i %i\r\n",
tcp_version,
data->info.conn_local_ip,
data->info.conn_primary_ip,
data->info.conn_local_port,
data->info.conn_primary_port);
#ifdef USE_UNIX_SOCKETS
}
#endif /* USE_UNIX_SOCKETS */
return result;
}
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;
}
result = cf->next->cft->connect(cf->next, data, blocking, done);
if(result || !*done)
return 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 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,
0,
cf_haproxy_destroy,
cf_haproxy_connect,
cf_haproxy_close,
Curl_cf_def_get_host,
cf_haproxy_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,
};
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);
ctx = calloc(1, sizeof(*ctx));
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);
result = Curl_cf_create(&cf, &Curl_cft_http_proxy, 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)
{
struct Curl_cfilter *cf;
CURLcode result;
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:
free(ctx);
return result;
}
#endif /* !CURL_DISABLE_PROXY */
#endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */
+11 -17
View File
@@ -25,34 +25,28 @@
***************************************************************************/
#include "curl_setup.h"
#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
#include "urldata.h"
#if !defined(CURL_DISABLE_PROXY)
#if !defined(CURL_DISABLE_HTTP)
/* Default proxy timeout in milliseconds */
#define PROXY_TIMEOUT (3600*1000)
CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
void Curl_cf_http_proxy_get_host(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char **phost,
const char **pdisplay_host,
int *pport);
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 */
#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex);
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 */
#define IS_HTTPS_PROXY(t) (((t) == CURLPROXY_HTTPS) || \
((t) == CURLPROXY_HTTPS2))
#endif /* HEADER_CURL_HTTP_PROXY_H */
+4 -4
View File
@@ -1511,11 +1511,11 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
result = status; /* use the already set error code */
}
else if(!data->set.connect_only && !imap->custom &&
(imap->uid || imap->mindex || data->set.upload ||
(imap->uid || imap->mindex || data->state.upload ||
data->set.mimepost.kind != MIMEKIND_NONE)) {
/* Handle responses after FETCH or APPEND transfer has finished */
if(!data->set.upload && data->set.mimepost.kind == MIMEKIND_NONE)
if(!data->state.upload && data->set.mimepost.kind == MIMEKIND_NONE)
state(data, IMAP_FETCH_FINAL);
else {
/* End the APPEND command first by sending an empty line */
@@ -1581,7 +1581,7 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected,
selected = TRUE;
/* Start the first command in the DO phase */
if(data->set.upload || data->set.mimepost.kind != MIMEKIND_NONE)
if(data->state.upload || data->set.mimepost.kind != MIMEKIND_NONE)
/* APPEND can be executed directly */
result = imap_perform_append(data);
else if(imap->custom && (selected || !imap->mailbox))
@@ -1931,7 +1931,7 @@ static CURLcode imap_parse_url_options(struct connectdata *conn)
const char *value;
while(*ptr && *ptr != '=')
ptr++;
ptr++;
value = ptr + 1;
+1 -1
View File
@@ -164,7 +164,7 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size)
/* Was it a trailing run of 0x00's?
*/
if(best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ))
*tp++ = ':';
*tp++ = ':';
*tp++ = '\0';
/* Check for overflow, copy, and we're done.
+2 -2
View File
@@ -731,7 +731,7 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done)
}
if(ber)
ber_free(ber, 0);
ber_free(ber, 0);
}
quit:
@@ -1069,7 +1069,7 @@ static int _ldap_url_parse(struct Curl_easy *data,
*ludpp = NULL;
if(!ludp)
return LDAP_NO_MEMORY;
return LDAP_NO_MEMORY;
rc = _ldap_url_parse2(data, conn, ludp);
if(rc != LDAP_SUCCESS) {
+7 -5
View File
@@ -24,7 +24,7 @@
#include "curl_setup.h"
#if !defined(CURL_DISABLE_CRYPTO_AUTH)
#if defined(USE_CURL_NTLM_CORE)
#include <string.h>
@@ -68,10 +68,12 @@
#include <openssl/md4.h>
#elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \
(__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) && \
defined(__MAC_OS_X_VERSION_MIN_ALLOWED) && \
(__MAC_OS_X_VERSION_MIN_ALLOWED < 101500)) || \
defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \
(__MAC_OS_X_VERSION_MIN_REQUIRED < 101500)) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \
(__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000))
(__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) && \
defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \
(__IPHONE_OS_VERSION_MIN_REQUIRED < 130000))
#define AN_APPLE_OS
#include <CommonCrypto/CommonDigest.h>
#elif defined(USE_WIN32_CRYPTO)
@@ -504,4 +506,4 @@ void Curl_md4it(unsigned char *output, const unsigned char *input,
MD4_Final(output, &ctx);
}
#endif /* CURL_DISABLE_CRYPTO_AUTH */
#endif /* USE_CURL_NTLM_CORE */
+5 -3
View File
@@ -66,10 +66,12 @@
#include <mbedtls/md5.h>
#elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \
(__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) && \
defined(__MAC_OS_X_VERSION_MIN_ALLOWED) && \
(__MAC_OS_X_VERSION_MIN_ALLOWED < 101500)) || \
defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \
(__MAC_OS_X_VERSION_MIN_REQUIRED < 101500)) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \
(__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000))
(__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) && \
defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \
(__IPHONE_OS_VERSION_MIN_REQUIRED < 130000))
#define AN_APPLE_OS
#include <CommonCrypto/CommonDigest.h>
#elif defined(USE_WIN32_CRYPTO)
+2 -6
View File
@@ -750,7 +750,6 @@ static void mime_file_free(void *ptr)
part->fp = NULL;
}
Curl_safefree(part->data);
part->data = NULL;
}
@@ -1108,7 +1107,7 @@ static int mime_subparts_seek(void *instream, curl_off_t offset, int whence)
return CURL_SEEKFUNC_CANTSEEK; /* Only support full rewind. */
if(mime->state.state == MIMESTATE_BEGIN)
return CURL_SEEKFUNC_OK; /* Already rewound. */
return CURL_SEEKFUNC_OK; /* Already rewound. */
for(part = mime->firstpart; part; part = part->nextpart) {
int res = mime_part_rewind(part);
@@ -1341,7 +1340,6 @@ CURLcode curl_mime_name(curl_mimepart *part, const char *name)
return CURLE_BAD_FUNCTION_ARGUMENT;
Curl_safefree(part->name);
part->name = NULL;
if(name) {
part->name = strdup(name);
@@ -1359,7 +1357,6 @@ CURLcode curl_mime_filename(curl_mimepart *part, const char *filename)
return CURLE_BAD_FUNCTION_ARGUMENT;
Curl_safefree(part->filename);
part->filename = NULL;
if(filename) {
part->filename = strdup(filename);
@@ -1459,7 +1456,6 @@ CURLcode curl_mime_type(curl_mimepart *part, const char *mimetype)
return CURLE_BAD_FUNCTION_ARGUMENT;
Curl_safefree(part->mimetype);
part->mimetype = NULL;
if(mimetype) {
part->mimetype = strdup(mimetype);
@@ -1738,7 +1734,7 @@ const char *Curl_mime_contenttype(const char *filename)
size_t len2 = strlen(ctts[i].extension);
if(len1 >= len2 && strcasecompare(nameend - len2, ctts[i].extension))
return ctts[i].type;
return ctts[i].type;
}
}
return NULL;
+3 -3
View File
@@ -400,7 +400,7 @@ static int dprintf_Pass1(const char *format, struct va_stack *vto,
/* out of allowed range */
return 1;
switch (*fmt) {
switch(*fmt) {
case 'S':
flags |= FLAGS_ALT;
/* FALLTHROUGH */
@@ -743,11 +743,11 @@ static int dprintf_formatf(
goto number;
unsigned_number:
unsigned_number:
/* Unsigned number of base BASE. */
is_neg = 0;
number:
number:
/* Number of base BASE. */
/* Supply a default precision if none was given. */
+2 -2
View File
@@ -605,7 +605,7 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done)
unsigned char packet;
switch(mqtt->state) {
MQTT_SUBACK_COMING:
MQTT_SUBACK_COMING:
case MQTT_SUBACK_COMING:
result = mqtt_verify_suback(data);
if(result)
@@ -688,7 +688,7 @@ static CURLcode mqtt_read_publish(struct Curl_easy *data, bool *done)
result = CURLE_WEIRD_SERVER_REPLY;
goto end;
}
end:
end:
return result;
}
+99 -53
View File
@@ -90,8 +90,17 @@
#define CURL_MULTI_HANDLE 0x000bab1e
#ifdef DEBUGBUILD
/* On a debug build, we want to fail hard on multi handles that
* are not NULL, but no longer have the MAGIC touch. This gives
* us early warning on things only discovered by valgrind otherwise. */
#define GOOD_MULTI_HANDLE(x) \
(((x) && (x)->magic == CURL_MULTI_HANDLE)? TRUE: \
(DEBUGASSERT(!(x)), FALSE))
#else
#define GOOD_MULTI_HANDLE(x) \
((x) && (x)->magic == CURL_MULTI_HANDLE)
#endif
static CURLMcode singlesocket(struct Curl_multi *multi,
struct Curl_easy *data);
@@ -383,12 +392,10 @@ static void sh_init(struct Curl_hash *hash, int hashsize)
* Called when a transfer is completed. Adds the given msg pointer to
* the list kept in the multi handle.
*/
static CURLMcode multi_addmsg(struct Curl_multi *multi,
struct Curl_message *msg)
static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg)
{
Curl_llist_insert_next(&multi->msglist, multi->msglist.tail, msg,
&msg->list);
return CURLM_OK;
}
struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
@@ -411,6 +418,7 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
Curl_llist_init(&multi->msglist, NULL);
Curl_llist_init(&multi->pending, NULL);
Curl_llist_init(&multi->msgsent, NULL);
multi->multiplexing = TRUE;
@@ -440,7 +448,7 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
return multi;
error:
error:
sockhash_destroy(&multi->sockhash);
Curl_hash_destroy(&multi->hostcache);
@@ -456,6 +464,14 @@ struct Curl_multi *curl_multi_init(void)
CURL_DNS_HASH_SIZE);
}
/* returns TRUE if the easy handle is supposed to be present in the main link
list */
static bool in_main_list(struct Curl_easy *data)
{
return ((data->mstate != MSTATE_PENDING) &&
(data->mstate != MSTATE_MSGSENT));
}
static void link_easy(struct Curl_multi *multi,
struct Curl_easy *data)
{
@@ -489,6 +505,8 @@ static void unlink_easy(struct Curl_multi *multi,
data->next->prev = data->prev;
else
multi->easylp = data->prev; /* point to last node */
data->prev = data->next = NULL;
}
@@ -681,6 +699,15 @@ static CURLcode multi_done(struct Curl_easy *data,
process_pending_handles(data->multi); /* connection / multiplex */
Curl_safefree(data->state.ulbuf);
/* if the transfer was completed in a paused state there can be buffered
data left to free */
for(i = 0; i < data->state.tempcount; i++) {
Curl_dyn_free(&data->state.tempwrite[i].b);
}
data->state.tempcount = 0;
CONNCACHE_LOCK(data);
Curl_detach_connection(data);
if(CONN_INUSE(conn)) {
@@ -699,14 +726,6 @@ static CURLcode multi_done(struct Curl_easy *data,
conn->dns_entry = NULL;
}
Curl_hostcache_prune(data);
Curl_safefree(data->state.ulbuf);
/* if the transfer was completed in a paused state there can be buffered
data left to free */
for(i = 0; i < data->state.tempcount; i++) {
Curl_dyn_free(&data->state.tempwrite[i].b);
}
data->state.tempcount = 0;
/* if data->set.reuse_forbid is TRUE, it means the libcurl client has
forced us to close this connection. This is ignored for requests taking
@@ -848,10 +867,16 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
called. Do it after multi_done() in case that sets another time! */
Curl_expire_clear(data);
if(data->connect_queue.ptr)
/* the handle was in the pending list waiting for an available connection,
so go ahead and remove it */
Curl_llist_remove(&multi->pending, &data->connect_queue, NULL);
if(data->connect_queue.ptr) {
/* the handle is in the pending or msgsent lists, so go ahead and remove
it */
if(data->mstate == MSTATE_PENDING)
Curl_llist_remove(&multi->pending, &data->connect_queue, NULL);
else
Curl_llist_remove(&multi->msgsent, &data->connect_queue, NULL);
}
if(in_main_list(data))
unlink_easy(multi, data);
if(data->dns.hostcachetype == HCACHE_MULTI) {
/* stop using the multi handle's DNS cache, *after* the possible
@@ -912,7 +937,6 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
/* make sure there's no pending message in the queue sent from this easy
handle */
for(e = multi->msglist.head; e; e = e->next) {
struct Curl_message *msg = e->ptr;
@@ -923,19 +947,6 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
}
}
/* Remove from the pending list if it is there. Otherwise this will
remain on the pending list forever due to the state change. */
for(e = multi->pending.head; e; e = e->next) {
struct Curl_easy *curr_data = e->ptr;
if(curr_data == data) {
Curl_llist_remove(&multi->pending, e, NULL);
break;
}
}
unlink_easy(multi, data);
/* NOTE NOTE NOTE
We do not touch the easy handle here! */
multi->num_easy--; /* one less to care about now */
@@ -1943,11 +1954,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
}
break;
case MSTATE_PENDING:
/* We will stay here until there is a connection available. Then
we try again in the MSTATE_CONNECT state. */
break;
case MSTATE_CONNECT:
/* Connect. We want to get a connection identifier filled in. */
/* init this transfer. */
@@ -1971,6 +1977,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* add this handle to the list of connect-pending handles */
Curl_llist_insert_next(&multi->pending, multi->pending.tail, data,
&data->connect_queue);
/* unlink from the main list */
unlink_easy(multi, data);
result = CURLE_OK;
break;
}
@@ -2013,7 +2021,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
else
#endif
if(conn->bits.conn_to_host)
hostname = conn->conn_to_host.name;
hostname = conn->conn_to_host.name;
else
hostname = conn->host.name;
@@ -2215,7 +2223,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* DO was not completed in one function call, we must continue
DOING... */
multistate(data, MSTATE_DOING);
rc = CURLM_OK;
}
/* after DO, go DO_DONE... or DO_MORE */
@@ -2223,7 +2230,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* we're supposed to do more, but we need to sit down, relax
and wait a little while first */
multistate(data, MSTATE_DOING_MORE);
rc = CURLM_OK;
}
else {
/* we're done with the DO, now DID */
@@ -2324,9 +2330,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
MSTATE_DID : MSTATE_DOING);
rc = CURLM_CALL_MULTI_PERFORM;
}
else
/* stay in DO_MORE */
rc = CURLM_OK;
/* else
stay in DO_MORE */
}
else {
/* failure detected */
@@ -2555,7 +2560,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
won't get stuck on this transfer at the expense of other concurrent
transfers */
Curl_expire(data, 0, EXPIRE_RUN_NOW);
rc = CURLM_OK;
}
break;
}
@@ -2597,9 +2601,11 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
case MSTATE_COMPLETED:
break;
case MSTATE_PENDING:
case MSTATE_MSGSENT:
data->result = result;
return CURLM_OK; /* do nothing */
/* handles in these states should NOT be in this list */
DEBUGASSERT(0);
break;
default:
return CURLM_INTERNAL_ERROR;
@@ -2619,7 +2625,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
multi_handle_timeout(data, nowp, &stream_error, &result, TRUE);
}
statemachine_end:
statemachine_end:
if(data->mstate < MSTATE_COMPLETED) {
if(result) {
@@ -2687,10 +2693,17 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
msg->extmsg.easy_handle = data;
msg->extmsg.data.result = result;
rc = multi_addmsg(multi, msg);
multi_addmsg(multi, msg);
DEBUGASSERT(!data->conn);
}
multistate(data, MSTATE_MSGSENT);
/* add this handle to the list of msgsent handles */
Curl_llist_insert_next(&multi->msgsent, multi->msgsent.tail, data,
&data->connect_queue);
/* unlink from the main list */
unlink_easy(multi, data);
return CURLM_OK;
}
} while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE));
@@ -2721,6 +2734,9 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
/* Do the loop and only alter the signal ignore state if the next handle
has a different NO_SIGNAL state than the previous */
do {
/* the current node might be unlinked in multi_runsingle(), get the next
pointer now */
struct Curl_easy *datanext = data->next;
if(data->set.no_signal != nosig) {
sigpipe_restore(&pipe_st);
sigpipe_ignore(data, &pipe_st);
@@ -2729,7 +2745,7 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
result = multi_runsingle(multi, &now, data);
if(result)
returncode = result;
data = data->next; /* operate on next handle */
data = datanext; /* operate on next handle */
} while(data);
sigpipe_restore(&pipe_st);
}
@@ -2760,6 +2776,18 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
return returncode;
}
/* unlink_all_msgsent_handles() detaches all those easy handles from this
multi handle */
static void unlink_all_msgsent_handles(struct Curl_multi *multi)
{
struct Curl_llist_element *e = multi->msgsent.head;
if(e) {
struct Curl_easy *data = e->ptr;
DEBUGASSERT(data->mstate == MSTATE_MSGSENT);
data->multi = NULL;
}
}
CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
{
struct Curl_easy *data;
@@ -2771,6 +2799,8 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
multi->magic = 0; /* not good anymore */
unlink_all_msgsent_handles(multi);
process_pending_handles(multi);
/* First remove all remaining easy handles */
data = multi->easyp;
while(data) {
@@ -3150,6 +3180,9 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
struct Curl_easy *data = NULL;
struct Curl_tree *t;
struct curltime now = Curl_now();
bool first = FALSE;
bool nosig = FALSE;
SIGPIPE_VARIABLE(pipe_st);
if(checkall) {
/* *perform() deals with running_handles on its own */
@@ -3192,7 +3225,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
if(data->conn && !(data->conn->handler->flags & PROTOPT_DIRLOCK))
/* set socket event bitmask if they're not locked */
data->conn->cselect_bits = ev_bitmask;
data->conn->cselect_bits = (unsigned char)ev_bitmask;
Curl_expire(data, 0, EXPIRE_RUN_NOW);
}
@@ -3224,18 +3257,24 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
do {
/* the first loop lap 'data' can be NULL */
if(data) {
SIGPIPE_VARIABLE(pipe_st);
sigpipe_ignore(data, &pipe_st);
if(!first) {
first = TRUE;
nosig = data->set.no_signal; /* initial state */
sigpipe_ignore(data, &pipe_st);
}
else if(data->set.no_signal != nosig) {
sigpipe_restore(&pipe_st);
sigpipe_ignore(data, &pipe_st);
nosig = data->set.no_signal; /* remember new state */
}
result = multi_runsingle(multi, &now, data);
sigpipe_restore(&pipe_st);
if(CURLM_OK >= result) {
/* get the socket(s) and check if the state has been changed since
last */
result = singlesocket(multi, data);
if(result)
return result;
break;
}
}
@@ -3249,6 +3288,8 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
}
} while(t);
if(first)
sigpipe_restore(&pipe_st);
*running_handles = multi->num_alive;
return result;
@@ -3702,6 +3743,8 @@ void Curl_multiuse_state(struct Curl_easy *data,
process_pending_handles(data->multi);
}
/* process_pending_handles() moves all handles from PENDING
back into the main list and change state to CONNECT */
static void process_pending_handles(struct Curl_multi *multi)
{
struct Curl_llist_element *e = multi->pending.head;
@@ -3710,6 +3753,9 @@ static void process_pending_handles(struct Curl_multi *multi)
DEBUGASSERT(data->mstate == MSTATE_PENDING);
/* put it back into the main list */
link_easy(multi, data);
multistate(data, MSTATE_CONNECT);
/* Remove this node from the list */
+2
View File
@@ -101,6 +101,8 @@ struct Curl_multi {
struct Curl_llist pending; /* Curl_easys that are in the
MSTATE_PENDING state */
struct Curl_llist msgsent; /* Curl_easys that are in the
MSTATE_MSGSENT state */
/* callback function and user data pointer for the *socket() API */
curl_socket_callback socket_cb;
+1 -1
View File
@@ -244,7 +244,7 @@ static int parsenetrc(const char *host,
}
} /* while Curl_get_line() */
out:
out:
if(!retcode) {
/* success */
if(login_alloc) {
+1 -1
View File
@@ -122,6 +122,7 @@ enum nametype {
bool Curl_check_noproxy(const char *name, const char *no_proxy,
bool *spacesep)
{
char hostip[128];
*spacesep = FALSE;
/*
* If we don't have a hostname at all, like for example with a FILE
@@ -139,7 +140,6 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy,
const char *p = no_proxy;
size_t namelen;
enum nametype type = TYPE_HOST;
char hostip[128];
if(!strcmp("*", no_proxy))
return TRUE;
+1 -1
View File
@@ -295,7 +295,7 @@ static CURLcode oldap_parse_login_options(struct connectdata *conn)
const char *value;
while(*ptr && *ptr != '=')
ptr++;
ptr++;
value = ptr + 1;
+1 -1
View File
@@ -332,7 +332,7 @@ static bool match_time(const char *date,
}
}
return FALSE; /* not a time string */
match:
match:
*h = hh;
*m = mm;
*s = ss;
+2 -2
View File
@@ -211,7 +211,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data,
#ifdef HAVE_GSSAPI
data_sec = conn->data_prot;
DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST);
conn->data_prot = data_sec;
conn->data_prot = (unsigned char)data_sec;
#endif
Curl_debug(data, CURLINFO_HEADER_OUT, s, (size_t)bytes_written);
@@ -316,7 +316,7 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data,
&gotbytes);
#ifdef HAVE_GSSAPI
DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST);
conn->data_prot = prot;
conn->data_prot = (unsigned char)prot;
#endif
if(result == CURLE_AGAIN)
return CURLE_OK; /* return */
+1 -1
View File
@@ -1376,7 +1376,7 @@ static CURLcode pop3_parse_url_options(struct connectdata *conn)
const char *value;
while(*ptr && *ptr != '=')
ptr++;
ptr++;
value = ptr + 1;
+2 -2
View File
@@ -183,8 +183,8 @@ static CURLcode randit(struct Curl_easy *data, unsigned int *rnd)
}
/*
* Curl_rand() stores 'num' number of random unsigned integers in the buffer
* 'rndptr' points to.
* Curl_rand() stores 'num' number of random unsigned characters in the buffer
* 'rnd' points to.
*
* If libcurl is built without TLS support or with a TLS backend that lacks a
* proper random API (rustls, Gskit or mbedTLS), this function will use "weak"
+158 -84
View File
@@ -45,8 +45,6 @@
#include "curl_memory.h"
#include "memdebug.h"
#define RTP_PKT_CHANNEL(p) ((int)((unsigned char)((p)[1])))
#define RTP_PKT_LENGTH(p) ((((int)((unsigned char)((p)[2]))) << 8) | \
((int)((unsigned char)((p)[3]))))
@@ -91,6 +89,8 @@ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn,
static
CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len);
static
CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport);
/*
@@ -119,6 +119,7 @@ const struct Curl_handler Curl_handler_rtsp = {
PROTOPT_NONE /* flags */
};
#define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */
static CURLcode rtsp_setup_connection(struct Curl_easy *data,
struct connectdata *conn)
@@ -130,6 +131,7 @@ static CURLcode rtsp_setup_connection(struct Curl_easy *data,
if(!rtsp)
return CURLE_OUT_OF_MEMORY;
Curl_dyn_init(&conn->proto.rtspc.buf, MAX_RTP_BUFFERSIZE);
return CURLE_OK;
}
@@ -176,7 +178,7 @@ static CURLcode rtsp_disconnect(struct Curl_easy *data,
{
(void) dead;
(void) data;
Curl_safefree(conn->proto.rtspc.rtp_buf);
Curl_dyn_free(&conn->proto.rtspc.buf);
return CURLE_OK;
}
@@ -204,7 +206,7 @@ static CURLcode rtsp_done(struct Curl_easy *data,
return CURLE_RTSP_CSEQ_ERROR;
}
if(data->set.rtspreq == RTSPREQ_RECEIVE &&
(data->conn->proto.rtspc.rtp_channel == -1)) {
(data->conn->proto.rtspc.rtp_channel == -1)) {
infof(data, "Got an RTP Receive with a CSeq of %ld", CSeq_recv);
}
}
@@ -374,7 +376,6 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
if(Curl_checkheaders(data, STRCONST("User-Agent")) &&
data->state.aptr.uagent) {
Curl_safefree(data->state.aptr.uagent);
data->state.aptr.uagent = NULL;
}
else if(!Curl_checkheaders(data, STRCONST("User-Agent")) &&
data->set.str[STRING_USERAGENT]) {
@@ -394,8 +395,6 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
Curl_safefree(data->state.aptr.ref);
if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer")))
data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer);
else
data->state.aptr.ref = NULL;
p_referrer = data->state.aptr.ref;
@@ -476,7 +475,6 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
* with basic and digest, it will be freed anyway by the next request
*/
Curl_safefree(data->state.aptr.userpwd);
data->state.aptr.userpwd = NULL;
if(result)
return result;
@@ -495,7 +493,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
rtspreq == RTSPREQ_SET_PARAMETER ||
rtspreq == RTSPREQ_GET_PARAMETER) {
if(data->set.upload) {
if(data->state.upload) {
putsize = data->state.infilesize;
data->state.httpreq = HTTPREQ_PUT;
@@ -514,7 +512,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
result =
Curl_dyn_addf(&req_buffer,
"Content-Length: %" CURL_FORMAT_CURL_OFF_T"\r\n",
(data->set.upload ? putsize : postsize));
(data->state.upload ? putsize : postsize));
if(result)
return result;
}
@@ -594,26 +592,20 @@ static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data,
bool *readmore) {
struct SingleRequest *k = &data->req;
struct rtsp_conn *rtspc = &(conn->proto.rtspc);
unsigned char *rtp_channel_mask = data->state.rtp_channel_mask;
char *rtp; /* moving pointer to rtp data */
ssize_t rtp_dataleft; /* how much data left to parse in this round */
char *scratch;
CURLcode result;
bool interleaved = false;
size_t skip_size = 0;
if(rtspc->rtp_buf) {
/* There was some leftover data the last time. Merge buffers */
char *newptr = Curl_saferealloc(rtspc->rtp_buf,
rtspc->rtp_bufsize + *nread);
if(!newptr) {
rtspc->rtp_buf = NULL;
rtspc->rtp_bufsize = 0;
if(Curl_dyn_len(&rtspc->buf)) {
/* There was some leftover data the last time. Append new buffers */
if(Curl_dyn_addn(&rtspc->buf, k->str, *nread))
return CURLE_OUT_OF_MEMORY;
}
rtspc->rtp_buf = newptr;
memcpy(rtspc->rtp_buf + rtspc->rtp_bufsize, k->str, *nread);
rtspc->rtp_bufsize += *nread;
rtp = rtspc->rtp_buf;
rtp_dataleft = rtspc->rtp_bufsize;
rtp = Curl_dyn_ptr(&rtspc->buf);
rtp_dataleft = Curl_dyn_len(&rtspc->buf);
}
else {
/* Just parse the request buffer directly */
@@ -621,71 +613,107 @@ static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data,
rtp_dataleft = *nread;
}
while((rtp_dataleft > 0) &&
(rtp[0] == '$')) {
if(rtp_dataleft > 4) {
int rtp_length;
while(rtp_dataleft > 0) {
if(rtp[0] == '$') {
if(rtp_dataleft > 4) {
unsigned char rtp_channel;
int rtp_length;
int idx;
int off;
/* Parse the header */
/* The channel identifier immediately follows and is 1 byte */
rtspc->rtp_channel = RTP_PKT_CHANNEL(rtp);
/* Parse the header */
/* The channel identifier immediately follows and is 1 byte */
rtp_channel = (unsigned char)rtp[1];
idx = rtp_channel / 8;
off = rtp_channel % 8;
if(!(rtp_channel_mask[idx] & (1 << off))) {
/* invalid channel number, maybe not an RTP packet */
rtp++;
rtp_dataleft--;
skip_size++;
continue;
}
if(skip_size > 0) {
DEBUGF(infof(data, "Skip the malformed interleaved data %lu "
"bytes", skip_size));
}
skip_size = 0;
rtspc->rtp_channel = rtp_channel;
/* The length is two bytes */
rtp_length = RTP_PKT_LENGTH(rtp);
/* The length is two bytes */
rtp_length = RTP_PKT_LENGTH(rtp);
if(rtp_dataleft < rtp_length + 4) {
/* Need more - incomplete payload */
if(rtp_dataleft < rtp_length + 4) {
/* Need more - incomplete payload */
*readmore = TRUE;
break;
}
interleaved = true;
/* We have the full RTP interleaved packet
* Write out the header including the leading '$' */
DEBUGF(infof(data, "RTP write channel %d rtp_length %d",
rtspc->rtp_channel, rtp_length));
result = rtp_client_write(data, &rtp[0], rtp_length + 4);
if(result) {
*readmore = FALSE;
return result;
}
/* Move forward in the buffer */
rtp_dataleft -= rtp_length + 4;
rtp += rtp_length + 4;
if(data->set.rtspreq == RTSPREQ_RECEIVE) {
/* If we are in a passive receive, give control back
* to the app as often as we can.
*/
k->keepon &= ~KEEP_RECV;
}
}
else {
/* Need more - incomplete header */
*readmore = TRUE;
break;
}
/* We have the full RTP interleaved packet
* Write out the header including the leading '$' */
DEBUGF(infof(data, "RTP write channel %d rtp_length %d",
rtspc->rtp_channel, rtp_length));
result = rtp_client_write(data, &rtp[0], rtp_length + 4);
if(result) {
failf(data, "Got an error writing an RTP packet");
*readmore = FALSE;
Curl_safefree(rtspc->rtp_buf);
rtspc->rtp_buf = NULL;
rtspc->rtp_bufsize = 0;
return result;
}
/* Move forward in the buffer */
rtp_dataleft -= rtp_length + 4;
rtp += rtp_length + 4;
if(data->set.rtspreq == RTSPREQ_RECEIVE) {
/* If we are in a passive receive, give control back
* to the app as often as we can.
*/
k->keepon &= ~KEEP_RECV;
}
}
else {
/* Need more - incomplete header */
*readmore = TRUE;
break;
/* If the following data begins with 'RTSP/', which might be an RTSP
message, we should stop skipping the data. */
/* If `k-> headerline> 0 && !interleaved` is true, we are maybe in the
middle of an RTSP message. It is difficult to determine this, so we
stop skipping. */
size_t prefix_len = (rtp_dataleft < 5) ? rtp_dataleft : 5;
if((k->headerline > 0 && !interleaved) ||
strncmp(rtp, "RTSP/", prefix_len) == 0) {
if(skip_size > 0) {
DEBUGF(infof(data, "Skip the malformed interleaved data %lu "
"bytes", skip_size));
}
break; /* maybe is an RTSP message */
}
/* Skip incorrect data util the next RTP packet or RTSP message */
do {
rtp++;
rtp_dataleft--;
skip_size++;
} while(rtp_dataleft > 0 && rtp[0] != '$' && rtp[0] != 'R');
}
}
if(rtp_dataleft && rtp[0] == '$') {
DEBUGF(infof(data, "RTP Rewinding %zd %s", rtp_dataleft,
*readmore ? "(READMORE)" : ""));
*readmore ? "(READMORE)" : ""));
/* Store the incomplete RTP packet for a "rewind" */
scratch = malloc(rtp_dataleft);
if(!scratch) {
Curl_safefree(rtspc->rtp_buf);
rtspc->rtp_buf = NULL;
rtspc->rtp_bufsize = 0;
return CURLE_OUT_OF_MEMORY;
if(!Curl_dyn_len(&rtspc->buf)) {
/* nothing was stored, add this data */
if(Curl_dyn_addn(&rtspc->buf, rtp, rtp_dataleft))
return CURLE_OUT_OF_MEMORY;
}
else {
/* keep the remainder */
Curl_dyn_tail(&rtspc->buf, rtp_dataleft);
}
memcpy(scratch, rtp, rtp_dataleft);
Curl_safefree(rtspc->rtp_buf);
rtspc->rtp_buf = scratch;
rtspc->rtp_bufsize = rtp_dataleft;
/* As far as the transfer is concerned, this data is consumed */
*nread = 0;
@@ -694,20 +722,10 @@ static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data,
/* Fix up k->str to point just after the last RTP packet */
k->str += *nread - rtp_dataleft;
/* either all of the data has been read or...
* rtp now points at the next byte to parse
*/
if(rtp_dataleft > 0)
DEBUGASSERT(k->str[0] == rtp[0]);
DEBUGASSERT(rtp_dataleft <= *nread); /* sanity check */
*nread = rtp_dataleft;
/* If we get here, we have finished with the leftover/merge buffer */
Curl_safefree(rtspc->rtp_buf);
rtspc->rtp_buf = NULL;
rtspc->rtp_bufsize = 0;
Curl_dyn_free(&rtspc->buf);
return CURLE_OK;
}
@@ -822,7 +840,63 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header)
(data->set.str[STRING_RTSP_SESSION_ID])[idlen] = '\0';
}
}
else if(checkprefix("Transport:", header)) {
CURLcode result;
result = rtsp_parse_transport(data, header + 10);
if(result)
return result;
}
return CURLE_OK;
}
static
CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport)
{
/* If we receive multiple Transport response-headers, the linterleaved
channels of each response header is recorded and used together for
subsequent data validity checks.*/
/* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */
char *start;
char *end;
start = transport;
while(start && *start) {
while(*start && ISBLANK(*start) )
start++;
end = strchr(start, ';');
if(checkprefix("interleaved=", start)) {
long chan1, chan2, chan;
char *endp;
char *p = start + 12;
chan1 = strtol(p, &endp, 10);
if(p != endp && chan1 >= 0 && chan1 <= 255) {
unsigned char *rtp_channel_mask = data->state.rtp_channel_mask;
chan2 = chan1;
if(*endp == '-') {
p = endp + 1;
chan2 = strtol(p, &endp, 10);
if(p == endp || chan2 < 0 || chan2 > 255) {
infof(data, "Unable to read the interleaved parameter from "
"Transport header: [%s]", transport);
chan2 = chan1;
}
}
for(chan = chan1; chan <= chan2; chan++) {
long idx = chan / 8;
long off = chan % 8;
rtp_channel_mask[idx] |= (unsigned char)(1 << off);
}
}
else {
infof(data, "Unable to read the interleaved parameter from "
"Transport header: [%s]", transport);
}
break;
}
/* skip to next parameter */
start = (!end) ? end : (end + 1);
}
return CURLE_OK;
}
#endif /* CURL_DISABLE_RTSP or using Hyper */
+1 -2
View File
@@ -45,8 +45,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header);
* Currently, only used for tracking incomplete RTP data reads
*/
struct rtsp_conn {
char *rtp_buf;
ssize_t rtp_bufsize;
struct dynbuf buf;
int rtp_channel;
};
+9 -4
View File
@@ -61,8 +61,8 @@
* for the intended use of this function in the library.
*
* Return values:
* -1 = system call error, invalid timeout value, or interrupted
* 0 = specified timeout has elapsed
* -1 = system call error, or invalid timeout value
* 0 = specified timeout has elapsed, or interrupted
*/
int Curl_wait_ms(timediff_t timeout_ms)
{
@@ -99,8 +99,13 @@ int Curl_wait_ms(timediff_t timeout_ms)
}
#endif /* HAVE_POLL_FINE */
#endif /* USE_WINSOCK */
if(r)
r = -1;
if(r) {
if((r == -1) && (SOCKERRNO == EINTR))
/* make EINTR from select or poll not a "lethal" error */
r = 0;
else
r = -1;
}
return r;
}
+1 -3
View File
@@ -271,10 +271,8 @@ static CURLcode chop_write(struct Curl_easy *data,
if(type & CLIENTWRITE_BODY) {
#ifdef USE_WEBSOCKETS
if(conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) {
struct HTTP *ws = data->req.p.http;
writebody = Curl_ws_writecb;
ws->ws.data = data;
writebody_ptr = ws;
writebody_ptr = data;
}
else
#endif
+8 -6
View File
@@ -115,7 +115,11 @@ static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp)
/* Parse the login details if specified. It not then we treat NULL as a hint
to clear the existing data */
if(option) {
result = Curl_parse_login_details(option, strlen(option),
size_t len = strlen(option);
if(len > CURL_MAX_INPUT_LENGTH)
return CURLE_BAD_FUNCTION_ARGUMENT;
result = Curl_parse_login_details(option, len,
(userp ? &user : NULL),
(passwdp ? &passwd : NULL),
NULL);
@@ -329,8 +333,8 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
* We want to sent data to the remote host. If this is HTTP, that equals
* using the PUT request.
*/
data->set.upload = (0 != va_arg(param, long)) ? TRUE : FALSE;
if(data->set.upload) {
arg = va_arg(param, long);
if(arg) {
/* If this is HTTP, PUT is what's needed to "upload" */
data->set.method = HTTPREQ_PUT;
data->set.opt_no_body = FALSE; /* this is implied */
@@ -660,7 +664,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
}
else
data->set.method = HTTPREQ_GET;
data->set.upload = FALSE;
break;
#ifndef CURL_DISABLE_MIME
@@ -884,7 +887,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
*/
if(va_arg(param, long)) {
data->set.method = HTTPREQ_GET;
data->set.upload = FALSE; /* switch off upload */
data->set.opt_no_body = FALSE; /* this is implied */
}
break;
@@ -1155,7 +1157,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
case CURLOPT_PROXYTYPE:
/*
* Set proxy type. HTTP/HTTP_1_0/SOCKS4/SOCKS4a/SOCKS5/SOCKS5_HOSTNAME
* Set proxy type.
*/
arg = va_arg(param, long);
if((arg < CURLPROXY_HTTP) || (arg > CURLPROXY_SOCKS5_HOSTNAME))
+1 -3
View File
@@ -59,9 +59,7 @@
#if defined(USE_OPENSSL_SHA256)
/* When OpenSSL or wolfSSL is available is available we use their
* SHA256-functions.
*/
/* When OpenSSL or wolfSSL is available we use their SHA256-functions. */
#if defined(USE_OPENSSL)
#include <openssl/evp.h>
#elif defined(USE_WOLFSSL)
+3 -4
View File
@@ -530,7 +530,7 @@ static CURLcode smb_send_open(struct Curl_easy *data)
byte_count = strlen(req->path);
msg.name_length = smb_swap16((unsigned short)byte_count);
msg.share_access = smb_swap32(SMB_FILE_SHARE_ALL);
if(data->set.upload) {
if(data->state.upload) {
msg.access = smb_swap32(SMB_GENERIC_READ | SMB_GENERIC_WRITE);
msg.create_disposition = smb_swap32(SMB_FILE_OVERWRITE_IF);
}
@@ -762,7 +762,7 @@ 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)) {
if(data->state.upload && (data->state.infilesize < 0)) {
failf(data, "SMB upload needs to know the size up front");
return CURLE_SEND_ERROR;
}
@@ -813,13 +813,12 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done)
smb_m = (const struct smb_nt_create_response*) msg;
req->fid = smb_swap16(smb_m->fid);
data->req.offset = 0;
if(data->set.upload) {
if(data->state.upload) {
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->req.size);
next_state = SMB_UPLOAD;
}
else {
smb_m = (const struct smb_nt_create_response*) msg;
data->req.size = smb_swap64(smb_m->end_of_file);
if(data->req.size < 0) {
req->result = CURLE_WEIRD_SERVER_REPLY;
+2 -2
View File
@@ -1419,7 +1419,7 @@ static CURLcode smtp_done(struct Curl_easy *data, CURLcode status,
result = status; /* use the already set error code */
}
else if(!data->set.connect_only && data->set.mail_rcpt &&
(data->set.upload || data->set.mimepost.kind)) {
(data->state.upload || data->set.mimepost.kind)) {
/* Calculate the EOB taking into account any terminating CRLF from the
previous line of the email or the CRLF of the DATA command when there
is "no mail data". RFC-5321, sect. 4.1.1.4.
@@ -1511,7 +1511,7 @@ static CURLcode smtp_perform(struct Curl_easy *data, bool *connected,
smtp->eob = 2;
/* Start the first command in the DO phase */
if((data->set.upload || data->set.mimepost.kind) && data->set.mail_rcpt)
if((data->state.upload || data->set.mimepost.kind) && data->set.mail_rcpt)
/* MAIL transfer */
result = smtp_perform_mail(data);
else
+11 -5
View File
@@ -24,6 +24,8 @@
#include "curl_setup.h"
#include "socketpair.h"
#include "urldata.h"
#include "rand.h"
#if !defined(HAVE_SOCKETPAIR) && !defined(CURL_DISABLE_SOCKETPAIR)
#ifdef WIN32
@@ -125,13 +127,17 @@ int Curl_socketpair(int domain, int type, int protocol,
if(socks[1] == CURL_SOCKET_BAD)
goto error;
else {
struct curltime check;
struct curltime start = Curl_now();
char *p = (char *)&check;
char rnd[9];
char check[sizeof(rnd)];
char *p = &check[0];
size_t s = sizeof(check);
if(Curl_rand(NULL, (unsigned char *)rnd, sizeof(rnd)))
goto error;
/* write data to the socket */
swrite(socks[0], &start, sizeof(start));
swrite(socks[0], rnd, sizeof(rnd));
/* verify that we read the correct data */
do {
ssize_t nread;
@@ -168,7 +174,7 @@ int Curl_socketpair(int domain, int type, int protocol,
p += nread;
continue;
}
if(memcmp(&start, &check, sizeof(check)))
if(memcmp(rnd, check, sizeof(check)))
goto error;
break;
} while(1);
@@ -177,7 +183,7 @@ int Curl_socketpair(int domain, int type, int protocol,
sclose(listener);
return 0;
error:
error:
sclose(listener);
sclose(socks[0]);
sclose(socks[1]);
+8 -21
View File
@@ -354,7 +354,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf,
}
}
/* FALLTHROUGH */
CONNECT_RESOLVED:
CONNECT_RESOLVED:
case CONNECT_RESOLVED: {
struct Curl_addrinfo *hp = NULL;
/*
@@ -394,7 +394,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf,
return CURLPX_RESOLVE_HOST;
}
/* FALLTHROUGH */
CONNECT_REQ_INIT:
CONNECT_REQ_INIT:
case CONNECT_REQ_INIT:
/*
* This is currently not supporting "Identification Protocol (RFC1413)".
@@ -638,7 +638,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
return CURLPX_OK;
}
/* FALLTHROUGH */
CONNECT_SOCKS_READ_INIT:
CONNECT_SOCKS_READ_INIT:
case CONNECT_SOCKS_READ_INIT:
sx->outstanding = 2; /* expect two bytes */
sx->outp = socksreq; /* store it here */
@@ -700,7 +700,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
default: /* do nothing! */
break;
CONNECT_AUTH_INIT:
CONNECT_AUTH_INIT:
case CONNECT_AUTH_INIT: {
/* Needs user name and password */
size_t proxy_user_len, proxy_password_len;
@@ -779,7 +779,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
/* Everything is good so far, user was authenticated! */
sxstate(sx, data, CONNECT_REQ_INIT);
/* FALLTHROUGH */
CONNECT_REQ_INIT:
CONNECT_REQ_INIT:
case CONNECT_REQ_INIT:
if(socks5_resolve_local) {
enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port,
@@ -818,7 +818,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
}
}
/* FALLTHROUGH */
CONNECT_RESOLVED:
CONNECT_RESOLVED:
case CONNECT_RESOLVED: {
struct Curl_addrinfo *hp = NULL;
size_t destlen;
@@ -873,7 +873,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
Curl_resolv_unlock(data, dns); /* not used anymore from now on */
goto CONNECT_REQ_SEND;
}
CONNECT_RESOLVE_REMOTE:
CONNECT_RESOLVE_REMOTE:
case CONNECT_RESOLVE_REMOTE:
/* Authentication is complete, now specify destination to the proxy */
len = 0;
@@ -913,7 +913,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
}
/* FALLTHROUGH */
CONNECT_REQ_SEND:
CONNECT_REQ_SEND:
case CONNECT_REQ_SEND:
/* PORT MSB */
socksreq[len++] = (unsigned char)((sx->remote_port >> 8) & 0xff);
@@ -1238,19 +1238,6 @@ struct Curl_cftype Curl_cft_socks_proxy = {
Curl_cf_def_query,
};
CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data,
struct connectdata *conn,
int sockindex)
{
struct Curl_cfilter *cf;
CURLcode result;
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)
{
-4
View File
@@ -51,10 +51,6 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf,
struct Curl_easy *data);
#endif
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);
+2 -2
View File
@@ -181,13 +181,13 @@ curl_easy_strerror(CURLcode error)
case CURLE_INTERFACE_FAILED:
return "Failed binding local connection end";
case CURLE_TOO_MANY_REDIRECTS :
case CURLE_TOO_MANY_REDIRECTS:
return "Number of redirects hit maximum amount";
case CURLE_UNKNOWN_OPTION:
return "An unknown option was passed in to libcurl";
case CURLE_SETOPT_OPTION_SYNTAX :
case CURLE_SETOPT_OPTION_SYNTAX:
return "Malformed option provided in a setopt";
case CURLE_GOT_NOTHING:
+13 -6
View File
@@ -770,16 +770,23 @@ static void printsub(struct Curl_easy *data,
}
}
#ifdef _MSC_VER
#pragma warning(push)
/* warning C4706: assignment within conditional expression */
#pragma warning(disable:4706)
#endif
static bool str_is_nonascii(const char *str)
{
size_t len = strlen(str);
while(len--) {
if(*str & 0x80)
char c;
while((c = *str++))
if(c & 0x80)
return TRUE;
str++;
}
return FALSE;
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
static CURLcode check_telnet_options(struct Curl_easy *data)
{
@@ -1103,7 +1110,7 @@ CURLcode telrcv(struct Curl_easy *data,
break;
case CURL_TS_IAC:
process_iac:
process_iac:
DEBUGASSERT(startwrite < 0);
switch(c) {
case CURL_WILL:
+4 -4
View File
@@ -370,7 +370,7 @@ static CURLcode tftp_parse_option_ack(struct tftp_state_data *state,
/* tsize should be ignored on upload: Who cares about the size of the
remote file? */
if(!data->set.upload) {
if(!data->state.upload) {
if(!tsize) {
failf(data, "invalid tsize -:%s:- value in OACK packet", value);
return CURLE_TFTP_ILLEGAL;
@@ -451,7 +451,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state,
return result;
}
if(data->set.upload) {
if(data->state.upload) {
/* If we are uploading, send an WRQ */
setpacketevent(&state->spacket, TFTP_EVENT_WRQ);
state->data->req.upload_fromhere =
@@ -486,7 +486,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state,
if(!data->set.tftp_no_options) {
char buf[64];
/* add tsize option */
if(data->set.upload && (data->state.infilesize != -1))
if(data->state.upload && (data->state.infilesize != -1))
msnprintf(buf, sizeof(buf), "%" CURL_FORMAT_CURL_OFF_T,
data->state.infilesize);
else
@@ -540,7 +540,7 @@ static CURLcode tftp_send_first(struct tftp_state_data *state,
break;
case TFTP_EVENT_OACK:
if(data->set.upload) {
if(data->state.upload) {
result = tftp_connect_for_tx(state, event);
}
else {
+43 -37
View File
@@ -753,7 +753,7 @@ static CURLcode readwrite_data(struct Curl_easy *data,
if(maxloops <= 0) {
/* we mark it as read-again-please */
conn->cselect_bits = CURL_CSELECT_IN;
data->state.dselect_bits = CURL_CSELECT_IN;
*comeback = TRUE;
}
@@ -1065,40 +1065,36 @@ CURLcode Curl_readwrite(struct connectdata *conn,
CURLcode result;
struct curltime now;
int didwhat = 0;
int select_bits;
curl_socket_t fd_read;
curl_socket_t fd_write;
int select_res = conn->cselect_bits;
conn->cselect_bits = 0;
/* only use the proper socket if the *_HOLD bit is not set simultaneously as
then we are in rate limiting state in that transfer direction */
if((k->keepon & KEEP_RECVBITS) == KEEP_RECV)
fd_read = conn->sockfd;
else
fd_read = CURL_SOCKET_BAD;
if((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
fd_write = conn->writesockfd;
else
fd_write = CURL_SOCKET_BAD;
#if defined(USE_HTTP2) || defined(USE_HTTP3)
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;
if(data->state.dselect_bits) {
select_bits = data->state.dselect_bits;
data->state.dselect_bits = 0;
}
#endif
else if(conn->cselect_bits) {
select_bits = conn->cselect_bits;
conn->cselect_bits = 0;
}
else {
curl_socket_t fd_read;
curl_socket_t fd_write;
/* only use the proper socket if the *_HOLD bit is not set simultaneously
as then we are in rate limiting state in that transfer direction */
if((k->keepon & KEEP_RECVBITS) == KEEP_RECV)
fd_read = conn->sockfd;
else
fd_read = CURL_SOCKET_BAD;
if(!select_res) /* Call for select()/poll() only, if read/write/error
status is not known. */
select_res = Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, 0);
if((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
fd_write = conn->writesockfd;
else
fd_write = CURL_SOCKET_BAD;
if(select_res == CURL_CSELECT_ERR) {
select_bits = Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, 0);
}
if(select_bits == CURL_CSELECT_ERR) {
failf(data, "select/poll returned error");
result = CURLE_SEND_ERROR;
goto out;
@@ -1106,7 +1102,7 @@ CURLcode Curl_readwrite(struct connectdata *conn,
#ifdef USE_HYPER
if(conn->datastream) {
result = conn->datastream(data, conn, &didwhat, done, select_res);
result = conn->datastream(data, conn, &didwhat, done, select_bits);
if(result || *done)
goto out;
}
@@ -1115,14 +1111,14 @@ CURLcode Curl_readwrite(struct connectdata *conn,
/* We go ahead and do a read if we have a readable socket or if
the stream was rewound (in which case we have data in a
buffer) */
if((k->keepon & KEEP_RECV) && (select_res & CURL_CSELECT_IN)) {
if((k->keepon & KEEP_RECV) && (select_bits & CURL_CSELECT_IN)) {
result = readwrite_data(data, conn, k, &didwhat, done, comeback);
if(result || *done)
goto out;
}
/* If we still have writing to do, we check if we have a writable socket. */
if((k->keepon & KEEP_SEND) && (select_res & CURL_CSELECT_OUT)) {
if((k->keepon & KEEP_SEND) && (select_bits & CURL_CSELECT_OUT)) {
/* write */
result = readwrite_upload(data, conn, &didwhat);
@@ -1235,7 +1231,6 @@ CURLcode Curl_readwrite(struct connectdata *conn,
/* Now update the "done" boolean we return */
*done = (0 == (k->keepon&(KEEP_RECVBITS|KEEP_SENDBITS))) ? TRUE : FALSE;
result = CURLE_OK;
out:
if(result)
DEBUGF(infof(data, DMSG(data, "Curl_readwrite() -> %d"), result));
@@ -1294,6 +1289,7 @@ void Curl_init_CONNECT(struct Curl_easy *data)
{
data->state.fread_func = data->set.fread_func_set;
data->state.in = data->set.in_set;
data->state.upload = (data->state.httpreq == HTTPREQ_PUT);
}
/*
@@ -1329,6 +1325,12 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
}
}
if(data->set.postfields && data->set.set_resume_from) {
/* we can't */
failf(data, "cannot mix POSTFIELDS with RESUME_FROM");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
data->state.prefer_ascii = data->set.prefer_ascii;
data->state.list_only = data->set.list_only;
data->state.httpreq = data->set.method;
@@ -1408,7 +1410,12 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
return CURLE_OUT_OF_MEMORY;
}
wc = data->wildcard;
if(wc->state < CURLWC_INIT) {
if((wc->state < CURLWC_INIT) ||
(wc->state >= CURLWC_CLEAN)) {
if(wc->ftpwc)
wc->dtor(wc->ftpwc);
Curl_safefree(wc->pattern);
Curl_safefree(wc->path);
result = Curl_wildcard_init(wc); /* init wildcard structures */
if(result)
return CURLE_OUT_OF_MEMORY;
@@ -1728,7 +1735,6 @@ CURLcode Curl_follow(struct Curl_easy *data,
data->state.httpreq != HTTPREQ_POST_MIME) ||
!(data->set.keep_post & CURL_REDIR_POST_303))) {
data->state.httpreq = HTTPREQ_GET;
data->set.upload = false;
infof(data, "Switch to %s",
data->req.no_body?"HEAD":"GET");
}
@@ -1766,7 +1772,7 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url)
/* if we're talking upload, we can't do the checks below, unless the protocol
is HTTP as when uploading over HTTP we will still get a response */
if(data->set.upload &&
if(data->state.upload &&
!(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)))
return CURLE_OK;
+64 -84
View File
@@ -129,7 +129,11 @@
#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
#endif
static void conn_free(struct Curl_easy *data, struct connectdata *conn);
#ifdef USE_NGHTTP2
static void data_priority_cleanup(struct Curl_easy *data);
#else
#define data_priority_cleanup(x)
#endif
/* Some parts of the code (e.g. chunked encoding) assume this buffer has at
* more than just a few bytes to play with. Don't let it become too small or
@@ -346,7 +350,6 @@ static void up_free(struct Curl_easy *data)
CURLcode Curl_close(struct Curl_easy **datap)
{
struct Curl_multi *m;
struct Curl_easy *data;
if(!datap || !*datap)
@@ -360,8 +363,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
/* Detach connection if any is left. This should not be normal, but can be
the case for example with CONNECT_ONLY + recv/send (test 556) */
Curl_detach_connection(data);
m = data->multi;
if(m)
if(data->multi)
/* This handle is still part of a multi handle, take care of this first
and detach this handle from there. */
curl_multi_remove_handle(data->multi, data);
@@ -373,11 +375,6 @@ CURLcode Curl_close(struct Curl_easy **datap)
data->multi_easy = NULL;
}
/* Destroy the timeout list that is held in the easy handle. It is
/normally/ done by curl_multi_remove_handle() but this is "just in
case" */
Curl_llist_destroy(&data->state.timeoutlist, NULL);
data->magic = 0; /* force a clear AFTER the possibly enforced removal from
the multi handle, since that function uses the magic
field! */
@@ -427,7 +424,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
Curl_resolver_cancel(data);
Curl_resolver_cleanup(data->state.async.resolver);
Curl_data_priority_cleanup(data);
data_priority_cleanup(data);
/* No longer a dirty share, if it exists */
if(data->share) {
@@ -1216,17 +1213,19 @@ ConnectionExists(struct Curl_easy *data,
if(needle->bits.tunnel_proxy != check->bits.tunnel_proxy)
continue;
if(needle->http_proxy.proxytype == CURLPROXY_HTTPS) {
if(IS_HTTPS_PROXY(needle->http_proxy.proxytype)) {
/* use https proxy */
if(needle->handler->flags&PROTOPT_SSL) {
if(needle->http_proxy.proxytype !=
check->http_proxy.proxytype)
continue;
else if(needle->handler->flags&PROTOPT_SSL) {
/* use double layer ssl */
if(!Curl_ssl_config_matches(&needle->proxy_ssl_config,
&check->proxy_ssl_config))
continue;
}
if(!Curl_ssl_config_matches(&needle->ssl_config,
&check->ssl_config))
else if(!Curl_ssl_config_matches(&needle->ssl_config,
&check->ssl_config))
continue;
}
}
@@ -1515,7 +1514,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
conn->created = Curl_now();
/* Store current time to give a baseline to keepalive connection times. */
conn->keepalive = Curl_now();
conn->keepalive = conn->created;
#ifndef CURL_DISABLE_PROXY
conn->http_proxy.proxytype = data->set.proxytype;
@@ -1528,8 +1527,8 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
conn->bits.httpproxy = (conn->bits.proxy &&
(conn->http_proxy.proxytype == CURLPROXY_HTTP ||
conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0 ||
conn->http_proxy.proxytype == CURLPROXY_HTTPS)) ?
TRUE : FALSE;
IS_HTTPS_PROXY(conn->http_proxy.proxytype))) ?
TRUE : FALSE;
conn->bits.socksproxy = (conn->bits.proxy &&
!conn->bits.httpproxy) ? TRUE : FALSE;
@@ -1588,11 +1587,11 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
it may live on without (this specific) Curl_easy */
conn->fclosesocket = data->set.fclosesocket;
conn->closesocket_client = data->set.closesocket_client;
conn->lastused = Curl_now(); /* used now */
conn->lastused = conn->created;
conn->gssapi_delegation = data->set.gssapi_delegation;
return conn;
error:
error:
free(conn->localdev);
free(conn);
@@ -1760,14 +1759,13 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data,
if(!use_set_uh) {
char *newurl;
uc = curl_url_set(uh, CURLUPART_URL, data->state.url,
CURLU_GUESS_SCHEME |
CURLU_NON_SUPPORT_SCHEME |
(data->set.disallow_username_in_url ?
CURLU_DISALLOW_USER : 0) |
(data->set.path_as_is ? CURLU_PATH_AS_IS : 0));
CURLU_GUESS_SCHEME |
CURLU_NON_SUPPORT_SCHEME |
(data->set.disallow_username_in_url ?
CURLU_DISALLOW_USER : 0) |
(data->set.path_as_is ? CURLU_PATH_AS_IS : 0));
if(uc) {
DEBUGF(infof(data, "curl_url_set rejected %s: %s", data->state.url,
curl_url_strerror(uc)));
failf(data, "URL rejected: %s", curl_url_strerror(uc));
return Curl_uc_to_curlcode(uc);
}
@@ -1821,11 +1819,6 @@ static CURLcode parseurlandfillconn(struct Curl_easy *data,
result = Curl_idnconvert_hostname(&conn->host);
if(result)
return result;
if(conn->bits.conn_to_host) {
result = Curl_idnconvert_hostname(&conn->conn_to_host);
if(result)
return result;
}
#ifndef CURL_DISABLE_HSTS
/* HSTS upgrade */
@@ -2161,8 +2154,12 @@ static CURLcode parse_proxy(struct Curl_easy *data,
goto error;
}
if(strcasecompare("https", scheme))
proxytype = CURLPROXY_HTTPS;
if(strcasecompare("https", scheme)) {
if(proxytype != CURLPROXY_HTTPS2)
proxytype = CURLPROXY_HTTPS;
else
proxytype = CURLPROXY_HTTPS2;
}
else if(strcasecompare("socks5h", scheme))
proxytype = CURLPROXY_SOCKS5_HOSTNAME;
else if(strcasecompare("socks5", scheme))
@@ -2182,7 +2179,8 @@ static CURLcode parse_proxy(struct Curl_easy *data,
}
}
else {
failf(data, "Unsupported proxy syntax in \'%s\'", proxy);
failf(data, "Unsupported proxy syntax in \'%s\': %s", proxy,
curl_url_strerror(uc));
result = CURLE_COULDNT_RESOLVE_PROXY;
goto error;
}
@@ -2190,9 +2188,9 @@ static CURLcode parse_proxy(struct Curl_easy *data,
#ifdef USE_SSL
if(!Curl_ssl_supports(data, SSLSUPP_HTTPS_PROXY))
#endif
if(proxytype == CURLPROXY_HTTPS) {
if(IS_HTTPS_PROXY(proxytype)) {
failf(data, "Unsupported proxy \'%s\', libcurl is built without the "
"HTTPS-proxy support.", proxy);
"HTTPS-proxy support.", proxy);
result = CURLE_NOT_BUILT_IN;
goto error;
}
@@ -2249,7 +2247,7 @@ static CURLcode parse_proxy(struct Curl_easy *data,
given */
port = (int)data->set.proxyport;
else {
if(proxytype == CURLPROXY_HTTPS)
if(IS_HTTPS_PROXY(proxytype))
port = CURL_DEFAULT_HTTPS_PROXY_PORT;
else
port = CURL_DEFAULT_PROXY_PORT;
@@ -2307,7 +2305,7 @@ static CURLcode parse_proxy(struct Curl_easy *data,
}
#endif
error:
error:
free(proxyuser);
free(proxypasswd);
free(host);
@@ -2329,22 +2327,17 @@ static CURLcode parse_proxy_auth(struct Curl_easy *data,
data->state.aptr.proxyuser : "";
const char *proxypasswd = data->state.aptr.proxypasswd ?
data->state.aptr.proxypasswd : "";
CURLcode result = CURLE_OK;
if(proxyuser) {
result = Curl_urldecode(proxyuser, 0, &conn->http_proxy.user, NULL,
REJECT_ZERO);
if(!result)
result = Curl_setstropt(&data->state.aptr.proxyuser,
conn->http_proxy.user);
}
if(!result && proxypasswd) {
CURLcode result = Curl_urldecode(proxyuser, 0, &conn->http_proxy.user, NULL,
REJECT_ZERO);
if(!result)
result = Curl_setstropt(&data->state.aptr.proxyuser,
conn->http_proxy.user);
if(!result)
result = Curl_urldecode(proxypasswd, 0, &conn->http_proxy.passwd,
NULL, REJECT_ZERO);
if(!result)
result = Curl_setstropt(&data->state.aptr.proxypasswd,
conn->http_proxy.passwd);
}
if(!result)
result = Curl_setstropt(&data->state.aptr.proxypasswd,
conn->http_proxy.passwd);
return result;
}
@@ -2569,29 +2562,13 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len,
size_t plen;
size_t olen;
/* the input length check is because this is called directly from setopt
and isn't going through the regular string length check */
size_t llen = strlen(login);
if(llen > CURL_MAX_INPUT_LENGTH)
return CURLE_BAD_FUNCTION_ARGUMENT;
/* Attempt to find the password separator */
if(passwdp) {
psep = strchr(login, ':');
/* Within the constraint of the login string */
if(psep >= login + len)
psep = NULL;
}
if(passwdp)
psep = memchr(login, ':', len);
/* Attempt to find the options separator */
if(optionsp) {
osep = strchr(login, ';');
/* Within the constraint of the login string */
if(osep >= login + len)
osep = NULL;
}
if(optionsp)
osep = memchr(login, ';', len);
/* Calculate the portion lengths */
ulen = (psep ?
@@ -2916,17 +2893,16 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data,
}
/* now, clone the cleaned host name */
if(hostptr) {
*hostname_result = strdup(hostptr);
if(!*hostname_result) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
DEBUGASSERT(hostptr);
*hostname_result = strdup(hostptr);
if(!*hostname_result) {
result = CURLE_OUT_OF_MEMORY;
goto error;
}
*port_result = port;
error:
error:
free(host_dup);
return result;
}
@@ -3503,6 +3479,11 @@ static CURLcode create_conn(struct Curl_easy *data,
return result;
}
#endif
if(conn->bits.conn_to_host) {
result = Curl_idnconvert_hostname(&conn->conn_to_host);
if(result)
return result;
}
/*************************************************************
* Check whether the host and the "connect to host" are equal.
@@ -4050,9 +4031,9 @@ CURLcode Curl_data_priority_add_child(struct Curl_easy *parent,
#endif /* USE_NGHTTP2 */
void Curl_data_priority_cleanup(struct Curl_easy *data)
{
#ifdef USE_NGHTTP2
static void data_priority_cleanup(struct Curl_easy *data)
{
while(data->set.priority.children) {
struct Curl_easy *tmp = data->set.priority.children->data;
priority_remove_child(data, tmp);
@@ -4062,9 +4043,8 @@ void Curl_data_priority_cleanup(struct Curl_easy *data)
if(data->set.priority.parent)
priority_remove_child(data->set.priority.parent, data);
#endif
(void)data;
}
#endif
void Curl_data_priority_clear_state(struct Curl_easy *data)
{
-2
View File
@@ -60,10 +60,8 @@ 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)) */
+3
View File
@@ -28,6 +28,9 @@
size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen,
bool guess_scheme);
CURLUcode Curl_url_set_authority(CURLU *u, const char *authority,
unsigned int flags);
#ifdef DEBUGBUILD
CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host,
bool has_scheme);
+340 -345
View File
@@ -34,6 +34,7 @@
#include "inet_ntop.h"
#include "strdup.h"
#include "idn.h"
#include "curl_memrchr.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -375,27 +376,30 @@ static char *concat_url(char *base, const char *relurl)
return Curl_dyn_ptr(&newest);
}
/* scan for byte values < 31 or 127 */
static bool junkscan(const char *part, unsigned int flags)
/* scan for byte values <= 31, 127 and sometimes space */
static CURLUcode junkscan(const char *url, size_t *urllen, unsigned int flags)
{
if(part) {
static const char badbytes[]={
/* */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x7f, 0x00 /* null-terminate */
};
size_t n = strlen(part);
size_t nfine = strcspn(part, badbytes);
if(nfine != n)
/* since we don't know which part is scanned, return a generic error
code */
return TRUE;
if(!(flags & CURLU_ALLOW_SPACE) && strchr(part, ' '))
return TRUE;
}
return FALSE;
static const char badbytes[]={
/* */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x7f, 0x00 /* null-terminate */
};
size_t n = strlen(url);
size_t nfine;
if(n > CURL_MAX_INPUT_LENGTH)
/* excessive input length */
return CURLUE_MALFORMED_INPUT;
nfine = strcspn(url, badbytes);
if((nfine != n) ||
(!(flags & CURLU_ALLOW_SPACE) && strchr(url, ' ')))
return CURLUE_MALFORMED_INPUT;
*urllen = n;
return CURLUE_OK;
}
/*
@@ -406,8 +410,10 @@ static bool junkscan(const char *part, unsigned int flags)
*
*/
static CURLUcode parse_hostname_login(struct Curl_URL *u,
struct dynbuf *host,
unsigned int flags)
const char *login,
size_t len,
unsigned int flags,
size_t *offset) /* to the host name */
{
CURLUcode result = CURLUE_OK;
CURLcode ccode;
@@ -423,13 +429,12 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u,
*
* We need somewhere to put the embedded details, so do that first.
*/
char *login = Curl_dyn_ptr(host);
char *ptr;
DEBUGASSERT(login);
ptr = strchr(login, '@');
*offset = 0;
ptr = memchr(login, '@', len);
if(!ptr)
goto out;
@@ -459,35 +464,25 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u,
result = CURLUE_USER_NOT_ALLOWED;
goto out;
}
if(junkscan(userp, flags)) {
result = CURLUE_BAD_USER;
goto out;
}
free(u->user);
u->user = userp;
}
if(passwdp) {
if(junkscan(passwdp, flags)) {
result = CURLUE_BAD_PASSWORD;
goto out;
}
free(u->password);
u->password = passwdp;
}
if(optionsp) {
if(junkscan(optionsp, flags)) {
result = CURLUE_BAD_LOGIN;
goto out;
}
free(u->options);
u->options = optionsp;
}
/* move the name to the start of the host buffer */
if(Curl_dyn_tail(host, strlen(ptr)))
return CURLUE_OUT_OF_MEMORY;
/* the host name starts at this offset */
*offset = ptr - login;
return CURLUE_OK;
out:
out:
free(userp);
free(passwdp);
@@ -505,8 +500,7 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host,
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.
* Find the end of an IPv6 address on the ']' ending bracket.
*/
if(hostname[0] == '[') {
portptr = strchr(hostname, ']');
@@ -527,7 +521,6 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host,
if(portptr) {
char *rest;
long port;
char portbuf[7];
size_t keep = portptr - hostname;
/* Browser behavior adaptation. If there's a colon with no digits after,
@@ -553,11 +546,10 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host,
if(rest[0])
return CURLUE_BAD_PORT_NUMBER;
*rest = 0;
/* generate a new port number string to get rid of leading zeroes etc */
msnprintf(portbuf, sizeof(portbuf), "%ld", port);
u->portnum = port;
u->port = strdup(portbuf);
/* generate a new port number string to get rid of leading zeroes etc */
free(u->port);
u->port = aprintf("%ld", port);
if(!u->port)
return CURLUE_OUT_OF_MEMORY;
}
@@ -565,68 +557,76 @@ UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host,
return CURLUE_OK;
}
/* this assumes 'hostname' now starts with [ */
static CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname,
size_t hlen) /* length of hostname */
{
size_t len;
DEBUGASSERT(*hostname == '[');
if(hlen < 4) /* '[::]' is the shortest possible valid string */
return CURLUE_BAD_IPV6;
hostname++;
hlen -= 2;
/* only valid IPv6 letters are ok */
len = strspn(hostname, "0123456789abcdefABCDEF:.");
if(hlen != len) {
hlen = len;
if(hostname[len] == '%') {
/* this could now be '%[zone id]' */
char zoneid[16];
int i = 0;
char *h = &hostname[len + 1];
/* pass '25' if present and is a url encoded percent sign */
if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
h += 2;
while(*h && (*h != ']') && (i < 15))
zoneid[i++] = *h++;
if(!i || (']' != *h))
return CURLUE_BAD_IPV6;
zoneid[i] = 0;
u->zoneid = strdup(zoneid);
if(!u->zoneid)
return CURLUE_OUT_OF_MEMORY;
hostname[len] = ']'; /* insert end bracket */
hostname[len + 1] = 0; /* terminate the hostname */
}
else
return CURLUE_BAD_IPV6;
/* hostname is fine */
}
/* Check the IPv6 address. */
{
char dest[16]; /* fits a binary IPv6 address */
char norm[MAX_IPADR_LEN];
hostname[hlen] = 0; /* end the address there */
if(1 != Curl_inet_pton(AF_INET6, hostname, dest))
return CURLUE_BAD_IPV6;
/* check if it can be done shorter */
if(Curl_inet_ntop(AF_INET6, dest, norm, sizeof(norm)) &&
(strlen(norm) < hlen)) {
strcpy(hostname, norm);
hlen = strlen(norm);
hostname[hlen + 1] = 0;
}
hostname[hlen] = ']'; /* restore ending bracket */
}
return CURLUE_OK;
}
static CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
size_t hlen) /* length of hostname */
{
size_t len;
DEBUGASSERT(hostname);
if(!hostname[0])
if(!hlen)
return CURLUE_NO_HOST;
else if(hostname[0] == '[') {
const char *l = "0123456789abcdefABCDEF:.";
if(hlen < 4) /* '[::]' is the shortest possible valid string */
return CURLUE_BAD_IPV6;
hostname++;
hlen -= 2;
/* only valid IPv6 letters are ok */
len = strspn(hostname, l);
if(hlen != len) {
hlen = len;
if(hostname[len] == '%') {
/* this could now be '%[zone id]' */
char zoneid[16];
int i = 0;
char *h = &hostname[len + 1];
/* pass '25' if present and is a url encoded percent sign */
if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
h += 2;
while(*h && (*h != ']') && (i < 15))
zoneid[i++] = *h++;
if(!i || (']' != *h))
return CURLUE_BAD_IPV6;
zoneid[i] = 0;
u->zoneid = strdup(zoneid);
if(!u->zoneid)
return CURLUE_OUT_OF_MEMORY;
hostname[len] = ']'; /* insert end bracket */
hostname[len + 1] = 0; /* terminate the hostname */
}
else
return CURLUE_BAD_IPV6;
/* hostname is fine */
}
/* Check the IPv6 address. */
{
char dest[16]; /* fits a binary IPv6 address */
char norm[MAX_IPADR_LEN];
hostname[hlen] = 0; /* end the address there */
if(1 != Curl_inet_pton(AF_INET6, hostname, dest))
return CURLUE_BAD_IPV6;
/* check if it can be done shorter */
if(Curl_inet_ntop(AF_INET6, dest, norm, sizeof(norm)) &&
(strlen(norm) < hlen)) {
strcpy(hostname, norm);
hlen = strlen(norm);
hostname[hlen + 1] = 0;
}
hostname[hlen] = ']'; /* restore ending bracket */
}
}
else if(hostname[0] == '[')
return ipv6_parse(u, hostname, hlen);
else {
/* letters from the second string are not ok */
len = strcspn(hostname, " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%");
@@ -637,50 +637,52 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
return CURLUE_OK;
}
#define HOSTNAME_END(x) (((x) == '/') || ((x) == '?') || ((x) == '#'))
/*
* Handle partial IPv4 numerical addresses and different bases, like
* '16843009', '0x7f', '0x7f.1' '0177.1.1.1' etc.
*
* If the given input string is syntactically wrong or any part for example is
* too big, this function returns FALSE and doesn't create any output.
* If the given input string is syntactically wrong IPv4 or any part for
* example is too big, this function returns HOST_NAME.
*
* Output the "normalized" version of that input string in plain quad decimal
* integers and return TRUE.
* integers.
*
* Returns the host type.
*/
static bool ipv4_normalize(const char *hostname, char *outp, size_t olen)
#define HOST_ERROR -1 /* out of memory */
#define HOST_BAD -2 /* bad IPv4 address */
#define HOST_NAME 1
#define HOST_IPV4 2
#define HOST_IPV6 3
static int ipv4_normalize(struct dynbuf *host)
{
bool done = FALSE;
int n = 0;
const char *c = hostname;
const char *c = Curl_dyn_ptr(host);
unsigned long parts[4] = {0, 0, 0, 0};
CURLcode result = CURLE_OK;
if(*c == '[')
return HOST_IPV6;
while(!done) {
char *endp;
unsigned long l;
if((*c < '0') || (*c > '9'))
if(!ISDIGIT(*c))
/* most importantly this doesn't allow a leading plus or minus */
return FALSE;
return HOST_NAME;
l = strtoul(c, &endp, 0);
/* overflow or nothing parsed at all */
if(((l == ULONG_MAX) && (errno == ERANGE)) || (endp == c))
return FALSE;
#if SIZEOF_LONG > 4
/* a value larger than 32 bits */
if(l > UINT_MAX)
return FALSE;
#endif
parts[n] = l;
c = endp;
switch (*c) {
case '.' :
switch(*c) {
case '.':
if(n == 3)
return FALSE;
return HOST_NAME;
n++;
c++;
break;
@@ -690,51 +692,63 @@ static bool ipv4_normalize(const char *hostname, char *outp, size_t olen)
break;
default:
return FALSE;
return HOST_NAME;
}
}
/* this is deemed a valid IPv4 numerical address */
/* overflow */
if((l == ULONG_MAX) && (errno == ERANGE))
return HOST_NAME;
#if SIZEOF_LONG > 4
/* a value larger than 32 bits */
if(l > UINT_MAX)
return HOST_NAME;
#endif
}
switch(n) {
case 0: /* a -- 32 bits */
msnprintf(outp, olen, "%u.%u.%u.%u",
parts[0] >> 24, (parts[0] >> 16) & 0xff,
(parts[0] >> 8) & 0xff, parts[0] & 0xff);
Curl_dyn_reset(host);
result = Curl_dyn_addf(host, "%u.%u.%u.%u",
parts[0] >> 24, (parts[0] >> 16) & 0xff,
(parts[0] >> 8) & 0xff, parts[0] & 0xff);
break;
case 1: /* a.b -- 8.24 bits */
if((parts[0] > 0xff) || (parts[1] > 0xffffff))
return FALSE;
msnprintf(outp, olen, "%u.%u.%u.%u",
parts[0], (parts[1] >> 16) & 0xff,
(parts[1] >> 8) & 0xff, parts[1] & 0xff);
return HOST_NAME;
Curl_dyn_reset(host);
result = Curl_dyn_addf(host, "%u.%u.%u.%u",
parts[0], (parts[1] >> 16) & 0xff,
(parts[1] >> 8) & 0xff, parts[1] & 0xff);
break;
case 2: /* a.b.c -- 8.8.16 bits */
if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
return FALSE;
msnprintf(outp, olen, "%u.%u.%u.%u",
parts[0], parts[1], (parts[2] >> 8) & 0xff,
parts[2] & 0xff);
return HOST_NAME;
Curl_dyn_reset(host);
result = Curl_dyn_addf(host, "%u.%u.%u.%u",
parts[0], parts[1], (parts[2] >> 8) & 0xff,
parts[2] & 0xff);
break;
case 3: /* a.b.c.d -- 8.8.8.8 bits */
if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
(parts[3] > 0xff))
return FALSE;
msnprintf(outp, olen, "%u.%u.%u.%u",
parts[0], parts[1], parts[2], parts[3]);
return HOST_NAME;
Curl_dyn_reset(host);
result = Curl_dyn_addf(host, "%u.%u.%u.%u",
parts[0], parts[1], parts[2], parts[3]);
break;
}
return TRUE;
if(result)
return HOST_ERROR;
return HOST_IPV4;
}
/* if necessary, replace the host content with a URL decoded version */
static CURLUcode decode_host(struct dynbuf *host)
static CURLUcode urldecode_host(struct dynbuf *host)
{
char *per = NULL;
const char *hostname = Curl_dyn_ptr(host);
if(hostname[0] == '[')
/* only decode if not an ipv6 numerical */
return CURLUE_OK;
per = strchr(hostname, '%');
if(!per)
/* nothing to decode */
@@ -757,6 +771,78 @@ static CURLUcode decode_host(struct dynbuf *host)
return CURLUE_OK;
}
static CURLUcode parse_authority(struct Curl_URL *u,
const char *auth, size_t authlen,
unsigned int flags,
struct dynbuf *host,
bool has_scheme)
{
size_t offset;
CURLUcode result;
/*
* Parse the login details and strip them out of the host name.
*/
result = parse_hostname_login(u, auth, authlen, flags, &offset);
if(result)
goto out;
if(Curl_dyn_addn(host, auth + offset, authlen - offset)) {
result = CURLUE_OUT_OF_MEMORY;
goto out;
}
result = Curl_parse_port(u, host, has_scheme);
if(result)
goto out;
if(!Curl_dyn_len(host))
return CURLUE_NO_HOST;
switch(ipv4_normalize(host)) {
case HOST_IPV4:
break;
case HOST_IPV6:
result = ipv6_parse(u, Curl_dyn_ptr(host), Curl_dyn_len(host));
break;
case HOST_NAME:
result = urldecode_host(host);
if(!result)
result = hostname_check(u, Curl_dyn_ptr(host), Curl_dyn_len(host));
break;
case HOST_ERROR:
result = CURLUE_OUT_OF_MEMORY;
break;
case HOST_BAD:
default:
result = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */
break;
}
out:
return result;
}
CURLUcode Curl_url_set_authority(CURLU *u, const char *authority,
unsigned int flags)
{
CURLUcode result;
struct dynbuf host;
DEBUGASSERT(authority);
Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
result = parse_authority(u, authority, strlen(authority), flags,
&host, !!u->scheme);
if(result)
Curl_dyn_free(&host);
else {
free(u->host);
u->host = Curl_dyn_ptr(&host);
}
return result;
}
/*
* "Remove Dot Segments"
* https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
@@ -781,8 +867,7 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp);
UNITTEST int dedotdotify(const char *input, size_t clen, char **outp)
{
char *outptr;
const char *orginput = input;
char *queryp;
const char *endp = &input[clen];
char *out;
*outp = NULL;
@@ -797,13 +882,6 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp)
*out = 0; /* null-terminates, for inputs like "./" */
outptr = 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
* string.
*/
queryp = strchr(input, '?');
do {
bool dotdot = TRUE;
if(*input == '.') {
@@ -889,17 +967,8 @@ UNITTEST int dedotdotify(const char *input, size_t clen, char **outp)
*outptr = 0;
}
/* continue until end of input string OR, if there is a terminating
query part, stop there */
} while(*input && (!queryp || (input < queryp)));
if(queryp) {
size_t qlen;
/* There was a query part, append that to the output. */
size_t oindex = queryp - orginput;
qlen = strlen(&orginput[oindex]);
memcpy(outptr, &orginput[oindex], qlen + 1); /* include zero byte */
}
/* continue until end of path */
} while(input < endp);
*outp = out;
return 0; /* success */
@@ -909,11 +978,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
{
const char *path;
size_t pathlen;
bool uncpath = FALSE;
char *query = NULL;
char *fragment = NULL;
char schemebuf[MAX_SCHEME_LEN + 1];
const char *schemep = NULL;
size_t schemelen = 0;
size_t urllen;
CURLUcode result = CURLUE_OK;
@@ -924,16 +991,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
/*************************************************************
* Parse the URL.
************************************************************/
/* allocate scratch area */
urllen = strlen(url);
if(urllen > CURL_MAX_INPUT_LENGTH) {
/* excessive input length */
result = CURLUE_MALFORMED_INPUT;
result = junkscan(url, &urllen, flags);
if(result)
goto fail;
}
schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf),
flags & (CURLU_GUESS_SCHEME|
@@ -941,6 +1001,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
/* handle the file: scheme */
if(schemelen && !strcmp(schemebuf, "file")) {
bool uncpath = FALSE;
if(urllen <= 6) {
/* file:/ is not enough to actually be a complete file: URL */
result = CURLUE_BAD_FILE_URL;
@@ -949,8 +1010,9 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
/* path has been allocated large enough to hold this */
path = (char *)&url[5];
pathlen = urllen - 5;
schemep = u->scheme = strdup("file");
u->scheme = strdup("file");
if(!u->scheme) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
@@ -1025,6 +1087,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
}
path = ptr;
pathlen = urllen - (ptr - url);
}
if(!uncpath)
@@ -1051,14 +1114,14 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
}
else {
/* clear path */
const char *p;
const char *schemep = NULL;
const char *hostp;
size_t len;
size_t hostlen;
if(schemelen) {
int i = 0;
p = &url[schemelen + 1];
while(p && (*p == '/') && (i < 4)) {
const char *p = &url[schemelen + 1];
while((*p == '/') && (i < 4)) {
p++;
i++;
}
@@ -1070,15 +1133,12 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
goto fail;
}
if((i < 1) || (i>3)) {
if((i < 1) || (i > 3)) {
/* less than one or more than three slashes */
result = CURLUE_BAD_SLASHES;
goto fail;
}
if(junkscan(schemep, flags)) {
result = CURLUE_BAD_SCHEME;
goto fail;
}
hostp = p; /* host name starts here */
}
else {
/* no scheme! */
@@ -1093,29 +1153,8 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
/*
* The URL was badly formatted, let's try without scheme specified.
*/
p = url;
hostp = url;
}
hostp = p; /* host name starts here */
/* find the end of the host name + port number */
while(*p && !HOSTNAME_END(*p))
p++;
len = p - hostp;
if(len) {
if(Curl_dyn_addn(&host, hostp, len)) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
}
}
else {
if(!(flags & CURLU_NO_AUTHORITY)) {
result = CURLUE_NO_HOST;
goto fail;
}
}
path = (char *)p;
if(schemep) {
u->scheme = strdup(schemep);
@@ -1124,30 +1163,89 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
goto fail;
}
}
/* find the end of the host name + port number */
hostlen = strcspn(hostp, "/?#");
path = &hostp[hostlen];
/* this pathlen also contains the query and the fragment */
pathlen = urllen - (path - url);
if(hostlen) {
result = parse_authority(u, hostp, hostlen, flags, &host, schemelen);
if(result)
goto fail;
if((flags & CURLU_GUESS_SCHEME) && !schemep) {
const char *hostname = Curl_dyn_ptr(&host);
/* legacy curl-style guess based on host name */
if(checkprefix("ftp.", hostname))
schemep = "ftp";
else if(checkprefix("dict.", hostname))
schemep = "dict";
else if(checkprefix("ldap.", hostname))
schemep = "ldap";
else if(checkprefix("imap.", hostname))
schemep = "imap";
else if(checkprefix("smtp.", hostname))
schemep = "smtp";
else if(checkprefix("pop3.", hostname))
schemep = "pop3";
else
schemep = "http";
u->scheme = strdup(schemep);
if(!u->scheme) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
}
}
}
else if(flags & CURLU_NO_AUTHORITY) {
/* allowed to be empty. */
if(Curl_dyn_add(&host, "")) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
}
}
else {
result = CURLUE_NO_HOST;
goto fail;
}
}
fragment = strchr(path, '#');
if(fragment) {
fraglen = strlen(fragment);
fraglen = pathlen - (fragment - path);
if(fraglen > 1) {
/* skip the leading '#' in the copy but include the terminating null */
u->fragment = Curl_memdup(fragment + 1, fraglen);
if(!u->fragment) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
if(flags & CURLU_URLENCODE) {
struct dynbuf enc;
Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
if(urlencode_str(&enc, fragment + 1, fraglen, TRUE, FALSE)) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
}
u->fragment = Curl_dyn_ptr(&enc);
}
if(junkscan(u->fragment, flags)) {
result = CURLUE_BAD_FRAGMENT;
goto fail;
else {
u->fragment = Curl_memdup(fragment + 1, fraglen);
if(!u->fragment) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
}
}
}
/* after this, pathlen still contains the query */
pathlen -= fraglen;
}
query = strchr(path, '?');
if(query && (!fragment || (query < fragment))) {
size_t qlen = strlen(query) - fraglen; /* includes '?' */
pathlen = strlen(path) - qlen - fraglen;
DEBUGASSERT(pathlen < urllen);
query = memchr(path, '?', pathlen);
if(query) {
size_t qlen = fragment ? (size_t)(fragment - query) :
pathlen - (query - path);
pathlen -= qlen;
if(qlen > 1) {
if(flags & CURLU_URLENCODE) {
struct dynbuf enc;
@@ -1167,11 +1265,6 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
}
u->query[qlen - 1] = 0;
}
if(junkscan(u->query, flags)) {
result = CURLUE_BAD_QUERY;
goto fail;
}
}
else {
/* single byte query */
@@ -1182,8 +1275,6 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
}
}
}
else
pathlen = strlen(path) - fraglen;
if(pathlen && (flags & CURLU_URLENCODE)) {
struct dynbuf enc;
@@ -1214,11 +1305,6 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
/* it might have encoded more than just the path so cut it */
u->path[pathlen] = 0;
if(junkscan(u->path, flags)) {
result = CURLUE_BAD_PATH;
goto fail;
}
if(!(flags & CURLU_PATH_AS_IS)) {
/* remove ../ and ./ sequences according to RFC3986 */
char *dedot;
@@ -1234,76 +1320,10 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
}
}
if(Curl_dyn_len(&host)) {
char normalized_ipv4[sizeof("255.255.255.255") + 1];
/*
* Parse the login details and strip them out of the host name.
*/
result = parse_hostname_login(u, &host, flags);
if(!result)
result = Curl_parse_port(u, &host, schemelen);
if(result)
goto fail;
if(junkscan(Curl_dyn_ptr(&host), flags)) {
result = CURLUE_BAD_HOSTNAME;
goto fail;
}
if(ipv4_normalize(Curl_dyn_ptr(&host),
normalized_ipv4, sizeof(normalized_ipv4))) {
Curl_dyn_reset(&host);
if(Curl_dyn_add(&host, normalized_ipv4)) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
}
}
else {
result = decode_host(&host);
if(!result)
result = hostname_check(u, Curl_dyn_ptr(&host), Curl_dyn_len(&host));
if(result)
goto fail;
}
if((flags & CURLU_GUESS_SCHEME) && !schemep) {
const char *hostname = Curl_dyn_ptr(&host);
/* legacy curl-style guess based on host name */
if(checkprefix("ftp.", hostname))
schemep = "ftp";
else if(checkprefix("dict.", hostname))
schemep = "dict";
else if(checkprefix("ldap.", hostname))
schemep = "ldap";
else if(checkprefix("imap.", hostname))
schemep = "imap";
else if(checkprefix("smtp.", hostname))
schemep = "smtp";
else if(checkprefix("pop3.", hostname))
schemep = "pop3";
else
schemep = "http";
u->scheme = strdup(schemep);
if(!u->scheme) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
}
}
}
else if(flags & CURLU_NO_AUTHORITY) {
/* allowed to be empty. */
if(Curl_dyn_add(&host, "")) {
result = CURLUE_OUT_OF_MEMORY;
goto fail;
}
}
u->host = Curl_dyn_ptr(&host);
return result;
fail:
fail:
Curl_dyn_free(&host);
free_urlhandle(u);
return result;
@@ -1366,7 +1386,7 @@ CURLU *curl_url_dup(const CURLU *in)
u->portnum = in->portnum;
}
return u;
fail:
fail:
curl_url_cleanup(u);
return NULL;
}
@@ -1525,36 +1545,6 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
#endif
}
}
else {
/* only encode '%' in output host name */
char *host = u->host;
bool percent = FALSE;
/* first, count number of percents present in the name */
while(*host) {
if(*host == '%') {
percent = TRUE;
break;
}
host++;
}
/* if there were percent(s), encode the host name */
if(percent) {
struct dynbuf enc;
CURLcode result;
Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
host = u->host;
while(*host) {
if(*host == '%')
result = Curl_dyn_addn(&enc, "%25", 3);
else
result = Curl_dyn_addn(&enc, host, 1);
if(result)
return CURLUE_OUT_OF_MEMORY;
host++;
}
allochost = Curl_dyn_ptr(&enc);
}
}
url = aprintf("%s://%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
scheme,
@@ -1704,9 +1694,11 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what,
}
switch(what) {
case CURLUPART_SCHEME:
if(strlen(part) > MAX_SCHEME_LEN)
/* too long */
case CURLUPART_SCHEME: {
size_t plen = strlen(part);
const char *s = part;
if((plen > MAX_SCHEME_LEN) || (plen < 1))
/* too long or too short */
return CURLUE_BAD_SCHEME;
if(!(flags & CURLU_NON_SUPPORT_SCHEME) &&
/* verify that it is a fine scheme */
@@ -1714,7 +1706,15 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what,
return CURLUE_UNSUPPORTED_SCHEME;
storep = &u->scheme;
urlencode = FALSE; /* never */
/* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */
while(plen--) {
if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.'))
s++; /* fine */
else
return CURLUE_BAD_SCHEME;
}
break;
}
case CURLUPART_USER:
storep = &u->user;
break;
@@ -1724,15 +1724,10 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what,
case CURLUPART_OPTIONS:
storep = &u->options;
break;
case CURLUPART_HOST: {
size_t len = strcspn(part, " \r\n");
if(strlen(part) != len)
/* hostname with bad content */
return CURLUE_BAD_HOSTNAME;
case CURLUPART_HOST:
storep = &u->host;
Curl_safefree(u->zoneid);
break;
}
case CURLUPART_ZONEID:
storep = &u->zoneid;
break;
@@ -1882,7 +1877,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what,
free(*storep);
*storep = Curl_dyn_ptr(&enc);
return CURLUE_OK;
nomem:
nomem:
free((char *)newp);
return CURLUE_OUT_OF_MEMORY;
}
@@ -1894,7 +1889,7 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what,
/* Skip hostname check, it's allowed to be empty. */
}
else {
if(hostname_check(u, (char *)newp, n)) {
if(!n || hostname_check(u, (char *)newp, n)) {
free((char *)newp);
return CURLUE_BAD_HOSTNAME;
}

Some files were not shown because too many files have changed in this diff Show More