mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-09-25 04:09:36 +03:00
Merge branch 'upstream-curl' into update-curl
* upstream-curl: curl 2023-05-30 (7ab9d437)
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ...);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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) */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -312,7 +312,7 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done)
|
||||
}
|
||||
}
|
||||
|
||||
error:
|
||||
error:
|
||||
free(eword);
|
||||
free(path);
|
||||
return result;
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -244,7 +244,7 @@ static int parsenetrc(const char *host,
|
||||
}
|
||||
} /* while Curl_get_line() */
|
||||
|
||||
out:
|
||||
out:
|
||||
if(!retcode) {
|
||||
/* success */
|
||||
if(login_alloc) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@ static CURLcode oldap_parse_login_options(struct connectdata *conn)
|
||||
const char *value;
|
||||
|
||||
while(*ptr && *ptr != '=')
|
||||
ptr++;
|
||||
ptr++;
|
||||
|
||||
value = ptr + 1;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -1376,7 +1376,7 @@ static CURLcode pop3_parse_url_options(struct connectdata *conn)
|
||||
const char *value;
|
||||
|
||||
while(*ptr && *ptr != '=')
|
||||
ptr++;
|
||||
ptr++;
|
||||
|
||||
value = ptr + 1;
|
||||
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 *)✓
|
||||
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]);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)) */
|
||||
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user