Merge topic 'presets-file-arg'

95edd93948 presets: Support loading from arbitrary file
81c22a67b6 presets: Factor out command line arguments
e75b62fd73 presets: Convert allowNoFiles to enum class
8476469b72 Help: Add cmake-workflow-option role
5e76987626 Help/presets: Use preset role for configurePreset references
2d8e8213d4 Help/presets: Improve include field formatting
43f5fc8ac8 Help, Tests: Consolidate presets terminology and references
c49ebb67da Help: Use POSIX slashes for 'generated by' in presets files

Acked-by: Kitware Robot <kwrobot@kitware.com>
Tested-by: buildbot <buildbot@kitware.com>
Merge-request: !11886
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fd56c98..7345496 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -138,6 +138,18 @@
         - .cmake_cdash_artifacts
         - .rules
 
+l:pvs-studio-fedora43:
+    extends:
+        - .fedora43_pvs_studio
+        - .cmake_build_linux
+        - .linux_x86_64_tags
+        - .cmake_cdash_artifacts
+        - .rules
+    variables:
+        CMAKE_CI_JOB_CONTINUOUS: "true"
+        CMAKE_CI_JOB_NIGHTLY: "true"
+        CMAKE_CI_JOB_NO_MR: "true"
+
 l:sphinx-fedora43:
     extends:
         - .fedora43_sphinx
diff --git a/.gitlab/.gitignore b/.gitlab/.gitignore
index 4d33c62..c0e73eb 100644
--- a/.gitlab/.gitignore
+++ b/.gitlab/.gitignore
@@ -20,6 +20,7 @@
 /open-watcom*
 /orangec
 /pellesc
+/pvs-studio
 /python*
 /qt*
 /sccache*
diff --git a/.gitlab/ci/cmake.ps1 b/.gitlab/ci/cmake.ps1
index 94f6694..ee0f4db 100644
--- a/.gitlab/ci/cmake.ps1
+++ b/.gitlab/ci/cmake.ps1
@@ -1,12 +1,12 @@
 $erroractionpreference = "stop"
 
-$version = "4.2.0"
+$version = "4.3.2"
 
 if ("$env:PROCESSOR_ARCHITECTURE" -eq "AMD64") {
-    $sha256sum = "CF35A516C4F5F4646B301E51C8E24B168CC012C3B1453B8F675303B54EB0EF45"
+    $sha256sum = "83D20C23F5C5F64B3B328785E35B23C532E33057A97ED6294ACACA3781B78A01"
     $platform = "windows-x86_64"
 } elseif ("$env:PROCESSOR_ARCHITECTURE" -eq "ARM64") {
-    $sha256sum = "9EB3E88083DDA569A4086C48517F5D49BD7B505DADA79075EA7CB6E2AC6E0A1E"
+    $sha256sum = "42A2A88FC57E70D04A92F6EAAAC2094241995F53584BC2AFDAE7E6EF982B3781"
     $platform = "windows-arm64"
 } else {
     throw ('unknown PROCESSOR_ARCHITECTURE: ' + "$env:PROCESSOR_ARCHITECTURE")
diff --git a/.gitlab/ci/cmake.sh b/.gitlab/ci/cmake.sh
index a0e2a5a..bc41ddd 100755
--- a/.gitlab/ci/cmake.sh
+++ b/.gitlab/ci/cmake.sh
@@ -2,22 +2,22 @@
 
 set -e
 
-readonly version="4.2.0"
+readonly version="4.3.2"
 
 case "$(uname -s)-$(uname -m)" in
     Linux-x86_64)
         shatool="sha256sum"
-        sha256sum="bbcebd4c433eab3af03a8c80bb5d84e8dfc3ff8a4ab9d01547b21240c23f7c2c"
+        sha256sum="791ae3604841ca03cb3889a3ad89165346e4b180ae3448efd4b0caa9ef46d245"
         platform="linux-x86_64"
         ;;
     Linux-aarch64)
         shatool="sha256sum"
-        sha256sum="86f52e9769cd7745e5227931e689e122dc1afc5648cc9b04db601b165f3ac993"
+        sha256sum="377079ab739f5765176f427609d9a2015b756ea20d5cba908d279c3731a2f481"
         platform="linux-aarch64"
         ;;
     Darwin-*)
         shatool="shasum -a 256"
-        sha256sum="b8b040a06343b2b6bc090b03a9c2bb4e98037518846989fb7c40ebbf30655c5d"
+        sha256sum="808ab43a0db04c8eec9ed7db12b90d7be1c8e2e75f4a060724d604a2043ccaf7"
         platform="macos-universal"
         ;;
     *)
diff --git a/.gitlab/ci/configure_fedora43_pvs_studio.cmake b/.gitlab/ci/configure_fedora43_pvs_studio.cmake
new file mode 100644
index 0000000..ff6e7f0
--- /dev/null
+++ b/.gitlab/ci/configure_fedora43_pvs_studio.cmake
@@ -0,0 +1,3 @@
+set(configure_no_sccache 1)
+set(CMake_RUN_PVS_STUDIO ON CACHE BOOL "")
+include("${CMAKE_CURRENT_LIST_DIR}/configure_fedora43_common.cmake")
diff --git a/.gitlab/ci/docker/debian13-x86_64/install_rvm.sh b/.gitlab/ci/docker/debian13-x86_64/install_rvm.sh
index 1ad42c4..3e76005 100755
--- a/.gitlab/ci/docker/debian13-x86_64/install_rvm.sh
+++ b/.gitlab/ci/docker/debian13-x86_64/install_rvm.sh
@@ -8,7 +8,10 @@
      --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 \
                  7D2BAF1CF37B13E2069D6956105BD0E739499BDB
 
-curl -sSL https://get.rvm.io | bash -s stable
+curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer     -o rvm-installer &&
+curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer.asc -o rvm-installer.asc &&
+gpg2 --verify rvm-installer.asc rvm-installer &&
+bash rvm-installer stable
 
 # keep version in sync with `env_debian*_ninja.sh`
 /usr/local/rvm/bin/rvm install ruby-3.2.2
diff --git a/.gitlab/ci/docker/fedora43/build_rvm.sh b/.gitlab/ci/docker/fedora43/build_rvm.sh
index 22c4333..9da34fe 100755
--- a/.gitlab/ci/docker/fedora43/build_rvm.sh
+++ b/.gitlab/ci/docker/fedora43/build_rvm.sh
@@ -8,7 +8,10 @@
      --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 \
                  7D2BAF1CF37B13E2069D6956105BD0E739499BDB
 
-curl -sSL https://get.rvm.io | bash -s stable --ignore-dotfiles
+curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer     -o rvm-installer &&
+curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer.asc -o rvm-installer.asc &&
+gpg2 --verify rvm-installer.asc rvm-installer &&
+bash rvm-installer stable --ignore-dotfiles
 
 export rvm_silence_banner=1
 
diff --git a/.gitlab/ci/env_fedora43_pvs_studio.sh b/.gitlab/ci/env_fedora43_pvs_studio.sh
new file mode 100644
index 0000000..36dba85
--- /dev/null
+++ b/.gitlab/ci/env_fedora43_pvs_studio.sh
@@ -0,0 +1 @@
+. .gitlab/ci/pvs-studio-env.sh
diff --git a/.gitlab/ci/pvs-studio-env.sh b/.gitlab/ci/pvs-studio-env.sh
new file mode 100644
index 0000000..c179b30
--- /dev/null
+++ b/.gitlab/ci/pvs-studio-env.sh
@@ -0,0 +1,13 @@
+.gitlab/ci/pvs-studio.sh
+export PATH=$PWD/.gitlab/pvs-studio/bin:$PATH
+pvs-studio --version
+
+if test -f "$CMAKE_CI_PVS_STUDIO_LICENSE_USER" -a -f "$CMAKE_CI_PVS_STUDIO_LICENSE_KEY"; then
+  pvs-studio-analyzer credentials "$(<"$CMAKE_CI_PVS_STUDIO_LICENSE_USER")" "$(<"$CMAKE_CI_PVS_STUDIO_LICENSE_KEY")"
+else
+ echo "The CMAKE_CI_PVS_STUDIO_LICENSE_USER and CMAKE_CI_PVS_STUDIO_LICENSE_KEY files were not both provided!"
+ exit 1
+fi
+rm -f "$CMAKE_CI_PVS_STUDIO_LICENSE_USER" "$CMAKE_CI_PVS_STUDIO_LICENSE_KEY"
+unset CMAKE_CI_PVS_STUDIO_LICENSE_KEY
+unset CMAKE_CI_PVS_STUDIO_LICENSE_USER
diff --git a/.gitlab/ci/pvs-studio.sh b/.gitlab/ci/pvs-studio.sh
new file mode 100755
index 0000000..b0eddf3
--- /dev/null
+++ b/.gitlab/ci/pvs-studio.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+set -e
+
+readonly version="7.42.105112.761"
+
+case "$(uname -s)-$(uname -m)" in
+    Linux-x86_64)
+        # https://files.pvs-studio.com/pvs-studio-7.42.105112.761-x86_64.tgz
+        shatool="sha256sum"
+        sha256sum="0ced11d17855cc172a860f8cc19a89f10adb6ab7185992fb57474118e9bdabd7"
+        filename="pvs-studio-$version-x86_64"
+        ;;
+    *)
+        echo "Unrecognized platform $(uname -s)-$(uname -m)"
+        exit 1
+        ;;
+esac
+readonly shatool
+readonly sha256sum
+
+cd .gitlab
+
+# This URL is only visible inside of Kitware's network.  See above filename table.
+baseurl="https://cmake.org/files/dependencies/internal"
+
+tarball="$filename.tgz"
+echo "$sha256sum  $tarball" > pvs-studio.sha256sum
+curl -OL "$baseurl/$tarball"
+$shatool --check pvs-studio.sha256sum
+tar xzf "$tarball"
+rm "$tarball" pvs-studio.sha256sum
+mv "$filename" "pvs-studio"
diff --git a/.gitlab/os-linux.yml b/.gitlab/os-linux.yml
index 5e6f1ee..4d9aff3 100644
--- a/.gitlab/os-linux.yml
+++ b/.gitlab/os-linux.yml
@@ -58,7 +58,7 @@
 ### Debian
 
 .debian13:
-    image: "kitware/cmake:ci-debian13-x86_64-2026-02-08"
+    image: "kitware/cmake:ci-debian13-x86_64-2026-04-28"
 
     variables:
         GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci"
@@ -82,7 +82,7 @@
 ### Fedora
 
 .fedora43:
-    image: "kitware/cmake:ci-fedora43-x86_64-2026-02-08"
+    image: "kitware/cmake:ci-fedora43-x86_64-2026-04-28"
 
     variables:
         GIT_CLONE_PATH: "$CI_BUILDS_DIR/cmake ci/long file name for testing purposes"
@@ -132,6 +132,14 @@
         CMAKE_CONFIGURATION: fedora43_tidy
         CMAKE_CI_NO_INSTALL: 1
 
+.fedora43_pvs_studio:
+    extends: .fedora43
+    environment:
+        name: pvs-studio
+    variables:
+        CMAKE_CONFIGURATION: fedora43_pvs_studio
+        CMAKE_CI_NO_INSTALL: 1
+
 .fedora43_clang_analyzer:
     extends: .fedora43
 
diff --git a/.pvsconfig b/.pvsconfig
new file mode 100644
index 0000000..4d7413f
--- /dev/null
+++ b/.pvsconfig
@@ -0,0 +1,100 @@
+# Identical sub-expressions to the left and to the right of 'foo' operator.
+//-V::501
+# Pointer to local variable 'X' is stored outside the scope of this variable. Such a pointer will become invalid.
+//-V::506
+# The 'new type(n)' pattern was detected. Probably meant: 'new type[n]'.
+//-V::508
+# The 'x' variable is assigned values twice successively. Perhaps this is a mistake.
+//-V::519
+# Possible null pointer dereference.
+//-V::522
+# Constant value is represented by an octal form.
+//-V::536
+# Iterators are passed as arguments to 'Foo' function. Consider inspecting the expression.
+//-V::539
+# Expression is always true/false.
+//-V::547
+# Expression of the 'A - B > 0' kind will work as 'A != B'.
+//-V::555
+# Possible array overrun.
+//-V::557
+# Part of conditional expression is always true/false.
+//-V::560
+# It is suspicious that the argument of sizeof() operator is the expression.
+//-V::568
+# Variable is assigned to itself.
+//-V::570
+# Recurring check. This condition was already verified in previous line.
+//-V::571
+# Function receives suspicious argument.
+//-V::575
+# Conditional expressions of 'if' statements located next to each other are identical.
+//-V::581
+# The 'Foo' function is called twice to deallocate the same resource.
+//-V::586
+# Expression is enclosed by parentheses twice: ((expression)). One pair of parentheses is unnecessary or typo is present.
+//-V::592
+# Pointer was used before its check for nullptr. Check lines: N1, N2.
+//-V::595
+# Possible division or mod by zero.
+//-V::609
+# Condition of a loop is always true/false.
+//-V::654
+# Possible meaningless check for null, as memory was allocated using 'new' operator. Memory allocation will lead to an exception.
+//-V::668
+# An object is used as an argument to its own method.
+//-V::678
+# Pattern A || (A && ...) was detected. The expression is excessive or contains a logical error.
+//-V::686
+# The class implements a copy constructor/operator=, but lacks the operator=/copy constructor.
+//-V::690
+# Excessive check can be simplified. The '||' operator is surrounded by opposite expressions 'x' and '!x'.
+//-V::728
+# Not all members of a class are initialized inside the constructor.
+//-V::730
+# Suspicious access to element of '...' array by a constant index inside a loop.
+//-V::767
+# The pointer in the expression equals nullptr. The resulting value is meaningless and should not be used.
+//-V::769
+# Two similar code fragments were found.
+//-V::778
+# Value of a variable is checked after it is used. Possible error in program's logic. Check lines: N1, N2.
+//-V::781
+# Variable is assigned but not used by the end of the function.
+//-V::1001
+# Pointer was used unsafely after its check for nullptr.
+//-V::1004
+# Value from the uninitialized optional is used. It may be an error.
+//-V::1007
+# The value is out of range of enum values. This causes unspecified or undefined behavior.
+//-V::1016
+# A pointer without owner is added to the container by the 'emplace_back' method. A memory leak will occur in case of an exception.
+//-V::1023
+# Possible overflow. Consider casting operands, not the result.
+//-V::1028
+# Variable is used after it is moved.
+//-V::1030
+# Two or more case-branches perform the same actions.
+//-V::1037
+# This file is marked with copyleft license, which requires you to open the derived source code.
+//-V::1042
+# Loop break conditions do not depend on the number of iterations.
+//-V::1044
+# Variable 'foo' was assigned the same value.
+//-V::1048
+# It is possible that an assigned variable should be checked in the next condition. Consider checking for typos.
+//-V::1051
+# Calling the 'foo' virtual function in the constructor/destructor may lead to unexpected result at runtime.
+//-V::1053
+# Return value is not always used. Consider inspecting the 'foo' function.
+//-V::1071
+# Conditional initialization inside the constructor may leave some members uninitialized.
+//-V::1077
+# Call of the 'Foo' function will lead to buffer underflow.
+//-V::1086
+# Waiting on condition variable without predicate. A thread can wait indefinitely or experience a spurious wake-up.
+//-V::1089
+# The 'emplace' / 'insert' function call contains potentially dangerous move operation. Moved object can be destroyed even if there is no insertion.
+//-V::1098
+# Preprocessing directive is present within a macro argument. This leads to undefined behavior.
+//-V::1119
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 06778c7..b391315 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -391,6 +391,22 @@
   list(APPEND CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${CMake_IWYU_OPTIONS})
 endif()
 
+option(CMake_RUN_PVS_STUDIO "Run pvs-studio-analyzer with the compiler." OFF)
+if(CMake_RUN_PVS_STUDIO)
+  if(CMake_SOURCE_DIR STREQUAL CMake_BINARY_DIR)
+    message(FATAL_ERROR "CMake_RUN_PVS_STUDIO requires an out-of-source build!")
+  endif()
+  find_program(PVS_STUDIO_COMMAND NAMES pvs-studio-analyzer)
+  if(NOT PVS_STUDIO_COMMAND)
+    message(FATAL_ERROR "CMake_RUN_PVS_STUDIO is ON but pvs-studio-analyzer is not found!")
+  endif()
+  set(CMAKE_CXX_PVS_STUDIO
+    "${PVS_STUDIO_COMMAND}" analyze
+      -a GA
+      --apply-pvs-configs
+      --project-root "${CMAKE_SOURCE_DIR}"
+  )
+endif()
 
 #-----------------------------------------------------------------------
 # a macro that only sets the FOLDER target property if it's
diff --git a/Help/command/install.rst b/Help/command/install.rst
index 7ed27e7..0d6ccb6 100644
--- a/Help/command/install.rst
+++ b/Help/command/install.rst
@@ -60,6 +60,11 @@
   ``<dir>`` should be a relative path.  An absolute path is allowed,
   but not recommended.
 
+  .. versionadded:: 4.4
+    The :ref:`CMD_INSTALL_ABSOLUTE_DESTINATION <CMD_INSTALL_ABSOLUTE_DESTINATION>`
+    diagnostic can be enabled to warn or error out when an absolute destination
+    is provided.
+
   When a relative path is given, it is interpreted relative to the value
   of the :variable:`CMAKE_INSTALL_PREFIX` variable.
   The prefix can be relocated at install time using the ``DESTDIR``
diff --git a/Help/manual/cmake-diagnostics.7.rst b/Help/manual/cmake-diagnostics.7.rst
index 66a08d5..5612299 100644
--- a/Help/manual/cmake-diagnostics.7.rst
+++ b/Help/manual/cmake-diagnostics.7.rst
@@ -88,3 +88,15 @@
 
 Although the action of this warning category can be queried as usual, changes
 made using the :command:`cmake_diagnostic` command have no effect.
+
+.. _CMD_INSTALL_ABSOLUTE_DESTINATION:
+
+``CMD_INSTALL_ABSOLUTE_DESTINATION`` (``-Winstall-absolute-destination``)
+-------------------------------------------------------------------------
+
+:Default: Ignore
+
+Warn when an :command:`install` command specifies an absolute
+``DESTINATION`` path.  Absolute destinations are typically undesirable
+because they prevent the installation prefix from being overridden at
+install time.
diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index 85391cd..2ab7290 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -100,6 +100,7 @@
 .. toctree::
    :maxdepth: 1
 
+   CMP0217: The MACROS directory property does not exist anymore. </policy/CMP0217>
    CMP0216: Swift targets have a default project name. </policy/CMP0216>
    CMP0215: Ninja generators emit Swift modules separately from compilation. </policy/CMP0215>
    CMP0214: Honor CMAKE_EXE_LINKER_FLAGS for Swift executable targets. </policy/CMP0214>
diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst
index 8985502..d5f7c98 100644
--- a/Help/manual/cmake-presets.7.rst
+++ b/Help/manual/cmake-presets.7.rst
@@ -486,6 +486,9 @@
       * The ``uninitialized`` and ``unusedCli`` fields were added to
         :preset:`configurePresets.errors`.
 
+      * The ``installAbsoluteDestination`` field was added to
+        :preset:`configurePresets.warnings` and :preset:`configurePresets.errors`.
+
     * Changes to `Macro Expansion`_
 
       * The `${fileDir} <CMakePresets fileDir_>`_ macro now always expands to
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 3638cf6..571585d 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -84,7 +84,6 @@
    /prop_dir/LINK_DIRECTORIES
    /prop_dir/LINK_OPTIONS
    /prop_dir/LISTFILE_STACK
-   /prop_dir/MACROS
    /prop_dir/PARENT_DIRECTORY
    /prop_dir/RULE_LAUNCH_COMPILE
    /prop_dir/RULE_LAUNCH_CUSTOM
@@ -702,6 +701,7 @@
    /prop_dir/COMPILE_DEFINITIONS_CONFIG
    /prop_dir/INTERPROCEDURAL_OPTIMIZATION
    /prop_dir/INTERPROCEDURAL_OPTIMIZATION_CONFIG
+   /prop_dir/MACROS
    /prop_dir/TEST_INCLUDE_FILE
 
 
diff --git a/Help/manual/presets/errors-properties.rst b/Help/manual/presets/errors-properties.rst
index 3f1216f..7bf5dac 100644
--- a/Help/manual/presets/errors-properties.rst
+++ b/Help/manual/presets/errors-properties.rst
@@ -32,6 +32,15 @@
   This may not be set to ``true``
   if ``warnings.dev`` is set to ``false``.
 
+.. _`CMakePresets.configurePresets.errors.installAbsoluteDestination`:
+
+``installAbsoluteDestination``
+  .. presets-versionadded:: 12
+
+  An optional boolean. Equivalent to passing :cmake-option:`-Werror=install-absolute-destination` or
+  :cmake-option:`-Wno-error=install-absolute-destination` on the command line.
+  This may not be set to ``true`` if ``warnings.installAbsoluteDestination`` is set to ``false``.
+
 .. _`CMakePresets.configurePresets.errors.uninitialized`:
 
 ``uninitialized``
diff --git a/Help/manual/presets/schema.json b/Help/manual/presets/schema.json
index 70f5024..cd52447 100644
--- a/Help/manual/presets/schema.json
+++ b/Help/manual/presets/schema.json
@@ -1286,6 +1286,9 @@
         "deprecated": {
           "$ref": "#/definitions/configurePresets.warnings.deprecated@v1.."
         },
+        "installAbsoluteDestination": {
+          "$ref": "#/definitions/configurePresets.warnings.installAbsoluteDestination@v12"
+        },
         "uninitialized": {
           "$ref": "#/definitions/configurePresets.warnings.uninitialized@v1.."
         },
@@ -1312,6 +1315,10 @@
       "type": "boolean",
       "description": "An optional boolean. Equivalent to passing -Wdev or -Wno-dev on the command line. This may not be set to false if errors.dev is set to true."
     },
+    "configurePresets.warnings.installAbsoluteDestination@v12": {
+      "type": "boolean",
+      "description": "An optional boolean. Equivalent to passing -Winstall-absolute-destination or -Wno-install-absolute-destination on the command line. This may not be set to false if errors.installAbsoluteDestination is set to true."
+    },
     "configurePresets.warnings.uninitialized@v1..": {
       "type": "boolean",
       "description": "An optional boolean. Equivalent to passing -Wuninitialized or -Wno-uninitialized on the command line. This may not be set to false if errors.uninitialized is set to true."
@@ -1364,6 +1371,9 @@
         "deprecated": {
           "$ref": "#/definitions/configurePresets.errors.deprecated@v1.."
         },
+        "installAbsoluteDestination": {
+          "$ref": "#/definitions/configurePresets.errors.installAbsoluteDestination@v12"
+        },
         "uninitialized": {
           "$ref": "#/definitions/configurePresets.errors.uninitialized@v12"
         },
@@ -1387,6 +1397,10 @@
       "type": "boolean",
       "description": "An optional boolean. Equivalent to passing -Werror=dev or -Wno-error=dev on the command line. This may not be set to true if warnings.dev is set to false."
     },
+    "configurePresets.errors.installAbsoluteDestination@v12": {
+      "type": "boolean",
+      "description": "An optional boolean. Equivalent to passing -Werror=install-absolute-destination or -Wno-error=install-absolute-destination on the command line. This may not be set to true if warnings.installAbsoluteDestination is set to false."
+    },
     "configurePresets.errors.uninitialized@v12": {
       "type": "boolean",
       "description": "An optional boolean. Equivalent to passing -Werror=uninitialized or -Wno-error=uninitialized on the command line. This may not be set to true if warnings.uninitialized is set to false."
diff --git a/Help/manual/presets/warnings-properties.rst b/Help/manual/presets/warnings-properties.rst
index acfce8f..0a31190 100644
--- a/Help/manual/presets/warnings-properties.rst
+++ b/Help/manual/presets/warnings-properties.rst
@@ -32,6 +32,15 @@
   This may not be set to ``false``
   if ``errors.dev`` is set to ``true``.
 
+.. _`CMakePresets.configurePresets.warnings.installAbsoluteDestination`:
+
+``installAbsoluteDestination``
+  .. presets-versionadded:: 12
+
+  An optional boolean. Equivalent to passing :option:`-Winstall-absolute-destination <cmake -W>` or
+  :option:`-Wno-install-absolute-destination <cmake -Wno->` on the command line.
+  This may not be set to ``false`` if ``errors.installAbsoluteDestination`` is set to ``true``.
+
 .. _`CMakePresets.configurePresets.warnings.uninitialized`:
 
 ``uninitialized``
diff --git a/Help/policy/CMP0217.rst b/Help/policy/CMP0217.rst
new file mode 100644
index 0000000..052b82c6
--- /dev/null
+++ b/Help/policy/CMP0217.rst
@@ -0,0 +1,24 @@
+CMP0217
+-------
+
+.. versionadded:: 4.4
+
+The :prop_dir:`MACROS` directory property does not exist anymore.
+
+In CMake 4.3 and below, the macros defined in a directory are registered in the
+:prop_dir:`MACROS` directory property.  However, the property does not have the
+same scope as macros, so it does not represent the actual list of macros.
+In CMake 4.4 and above, this property is not longer filled with the defined
+macros.  One may still use :command:`if(COMMAND myMacro) <if(COMMAND)>` to test
+whether one exists.
+
+The ``OLD`` behavior for this policy is to store macros definition in the
+:prop_dir:`MACROS` directory property.
+The ``NEW`` behavior for this policy is to no longer manage the
+:prop_dir:`MACROS` directory property.
+
+.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.4
+.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn
+.. include:: include/STANDARD_ADVICE.rst
+
+.. include:: include/DEPRECATED.rst
diff --git a/Help/prop_dir/MACROS.rst b/Help/prop_dir/MACROS.rst
index 245cc1b..7f1a7c5 100644
--- a/Help/prop_dir/MACROS.rst
+++ b/Help/prop_dir/MACROS.rst
@@ -1,8 +1,4 @@
 MACROS
 ------
 
-List of macro commands available in the current directory.
-
-This read-only property specifies the list of CMake macros currently
-defined.  It is intended for debugging purposes.  See the :command:`macro`
-command.
+This directory property does not exist anymore. See policy :policy:`CMP0217`.
diff --git a/Help/release/dev/MACROS-directory-property.rst b/Help/release/dev/MACROS-directory-property.rst
new file mode 100644
index 0000000..314310b
--- /dev/null
+++ b/Help/release/dev/MACROS-directory-property.rst
@@ -0,0 +1,5 @@
+MACROS-directory-property
+-------------------------
+
+* The :prop_dir:`MACROS` directory property does not exist anymore. See policy
+  :policy:`CMP0217`.
diff --git a/Help/release/dev/install-absolute-dest-configure-time.rst b/Help/release/dev/install-absolute-dest-configure-time.rst
new file mode 100644
index 0000000..25a9c0a
--- /dev/null
+++ b/Help/release/dev/install-absolute-dest-configure-time.rst
@@ -0,0 +1,10 @@
+install-absolute-dest-configure-time
+-------------------------------------
+
+* CMake gained the ability to diagnose :command:`install` commands that
+  specify an absolute ``DESTINATION`` path via the
+  :ref:`CMD_INSTALL_ABSOLUTE_DESTINATION <CMD_INSTALL_ABSOLUTE_DESTINATION>`
+  diagnostic category. This diagnostic may be controlled with the
+  :option:`-Winstall-absolute-destination <cmake -W>` command-line option, the
+  ``installAbsoluteDestination`` field in a :manual:`cmake-presets(7)`
+  ``warnings`` object, or the :command:`cmake_diagnostic` command.
diff --git a/Help/variable/CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION.rst b/Help/variable/CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION.rst
index 38e9b7b..84a27e5 100644
--- a/Help/variable/CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION.rst
+++ b/Help/variable/CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION.rst
@@ -8,3 +8,11 @@
 file takes place.  This variable is used by CMake-generated
 ``cmake_install.cmake`` scripts.  If one sets this variable to ``ON`` while
 running the script, it may get fatal error messages from the script.
+
+.. versionadded:: 4.4
+
+  The :ref:`CMD_INSTALL_ABSOLUTE_DESTINATION <CMD_INSTALL_ABSOLUTE_DESTINATION>`
+  diagnostic can be used to to generate errors for absolute install destinations
+  at generate time.
+
+See also :variable:`CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION`.
diff --git a/Help/variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION.rst b/Help/variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION.rst
index 81c1158..fac680e 100644
--- a/Help/variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION.rst
+++ b/Help/variable/CMAKE_WARN_ON_ABSOLUTE_INSTALL_DESTINATION.rst
@@ -7,3 +7,11 @@
 This variable is used by CMake-generated ``cmake_install.cmake`` scripts.
 If one sets this variable to ``ON`` while running the script, it may get
 warning messages from the script.
+
+.. versionadded:: 4.4
+
+  The :ref:`CMD_INSTALL_ABSOLUTE_DESTINATION <CMD_INSTALL_ABSOLUTE_DESTINATION>`
+  diagnostic can be used to to generate warnings for absolute install destinations
+  at generate time.
+
+See also :variable:`CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION`.
diff --git a/Modules/FindCUDAToolkit.cmake b/Modules/FindCUDAToolkit.cmake
index 8a6e1fb..73e30d2 100644
--- a/Modules/FindCUDAToolkit.cmake
+++ b/Modules/FindCUDAToolkit.cmake
@@ -1539,10 +1539,13 @@
   _CUDAToolkit_find_and_add_import_lib(
     sanitizer
     ONLY_SEARCH_FOR sanitizer-public
-    LIBRARY_SEARCH_DIRS
-      "${CUDAToolkit_LIBRARY_ROOT}/compute-sanitizer"
-      "${CUDAToolkit_LIBRARY_ROOT}/Sanitizer"
-      "${CUDAToolkit_LIBRARY_ROOT}/extras/Sanitizer"
+    EXTRA_PATH_SUFFIXES
+      "../compute-sanitizer"
+      "../../../compute-sanitizer"
+      "../Sanitizer"
+      "../../../Sanitizer"
+      "../extras/Sanitizer"
+      "../../../extras/Sanitizer"
     EXTRA_INCLUDE_DIRS "${CUDAToolkit_CUPTI_INCLUDE_DIR}"
   )
   if(TARGET CUDA::sanitizer)
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index df39ab1..ecb5c31 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,7 +1,7 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 4)
 set(CMake_VERSION_MINOR 3)
-set(CMake_VERSION_PATCH 20260430)
+set(CMake_VERSION_PATCH 20260501)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/cmDiagnostics.h b/Source/cmDiagnostics.h
index 3ab2239..bad732b 100644
--- a/Source/cmDiagnostics.h
+++ b/Source/cmDiagnostics.h
@@ -28,6 +28,7 @@
 #define CM_FOR_EACH_DIAGNOSTIC_TABLE(ACTION, SELECT)                          \
   SELECT(ACTION, Warn, CMD_NONE, CMD_AUTHOR, 12)                              \
   SELECT(ACTION, Warn, CMD_AUTHOR, CMD_DEPRECATED, 1)                         \
+  SELECT(ACTION, Ignore, CMD_AUTHOR, CMD_INSTALL_ABSOLUTE_DESTINATION, 12)    \
   SELECT(ACTION, Ignore, CMD_NONE, CMD_UNINITIALIZED, 1)                      \
   SELECT(ACTION, Warn, CMD_NONE, CMD_UNUSED_CLI, 1)
 
diff --git a/Source/cmExportInstallCMakeConfigGenerator.cxx b/Source/cmExportInstallCMakeConfigGenerator.cxx
index eae42b6..2f87099 100644
--- a/Source/cmExportInstallCMakeConfigGenerator.cxx
+++ b/Source/cmExportInstallCMakeConfigGenerator.cxx
@@ -284,22 +284,21 @@
   auto configs =
     gte->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
 
-  cmGeneratorExpression ge(*gte->Makefile->GetCMakeInstance());
-  auto cge =
-    ge.Parse(te->FileSetGenerators.at(fileSet->GetName())->GetDestination());
-
   for (auto const& config : configs) {
-    auto unescapedDest = cge->Evaluate(gte->LocalGenerator, config, gte);
-    auto dest = cmOutputConverter::EscapeForCMake(
-      unescapedDest, cmOutputConverter::WrapQuotes::NoWrap);
-    if (!cmSystemTools::FileIsFullPath(unescapedDest)) {
+    cmInstallFileSetGenerator::DestinationContext result =
+      te->FileSetGenerators.at(fileSet->GetName())
+        ->GetDestination(gte, config);
+
+    std::string dest = cmOutputConverter::EscapeForCMake(
+      result.UnescapedDestination, cmOutputConverter::WrapQuotes::NoWrap);
+    if (!cmSystemTools::FileIsFullPath(result.UnescapedDestination)) {
       dest = cmStrCat("${_IMPORT_PREFIX}/", dest);
     }
 
     auto const& type = fileSet->GetType();
     // C++ modules do not support interface file sets which are dependent upon
     // the configuration.
-    if (cge->GetHadContextSensitiveCondition() &&
+    if (result.HadContextSensitiveCondition &&
         type == cm::FileSetMetadata::CXX_MODULES) {
       auto* mf = this->IEGen->GetLocalGenerator()->GetMakefile();
       std::ostringstream e;
@@ -311,7 +310,7 @@
       return std::string{};
     }
 
-    if (cge->GetHadContextSensitiveCondition() && configs.size() != 1) {
+    if (result.HadContextSensitiveCondition && configs.size() != 1) {
       resultVector.push_back(
         cmStrCat("\"$<$<CONFIG:", config, ">:", dest, ">\""));
     } else {
diff --git a/Source/cmExportInstallPackageInfoGenerator.cxx b/Source/cmExportInstallPackageInfoGenerator.cxx
index 78eb146..202594a 100644
--- a/Source/cmExportInstallPackageInfoGenerator.cxx
+++ b/Source/cmExportInstallPackageInfoGenerator.cxx
@@ -224,18 +224,14 @@
   cmGeneratorTarget* gte, cmTargetExport const* te,
   cmGeneratorFileSet const* fileSet, cm::optional<std::string> const& config)
 {
-  cmGeneratorExpression ge(*gte->Makefile->GetCMakeInstance());
-  auto cge =
-    ge.Parse(te->FileSetGenerators.at(fileSet->GetName())->GetDestination());
+  cmInstallFileSetGenerator::DestinationContext result =
+    te->FileSetGenerators.at(fileSet->GetName())
+      ->GetDestination(gte, config.value_or(""));
 
-  std::string const unescapedDest =
-    cge->Evaluate(gte->LocalGenerator, config.value_or(""), gte);
-  bool const isConfigDependent = cge->GetHadContextSensitiveCondition();
-
-  if (config && !isConfigDependent) {
+  if (config && !result.HadContextSensitiveCondition) {
     return {};
   }
-  if (!config && isConfigDependent) {
+  if (!config && result.HadContextSensitiveCondition) {
     this->RequiresConfigFiles = true;
     return {};
   }
@@ -255,9 +251,9 @@
   }
 
   cm::optional<std::string> dest = cmOutputConverter::EscapeForCMake(
-    unescapedDest, cmOutputConverter::WrapQuotes::NoWrap);
+    result.UnescapedDestination, cmOutputConverter::WrapQuotes::NoWrap);
 
-  if (!cmSystemTools::FileIsFullPath(unescapedDest)) {
+  if (!cmSystemTools::FileIsFullPath(result.UnescapedDestination)) {
     dest = cmStrCat("@prefix@/"_s, *dest);
   }
 
diff --git a/Source/cmExportInstallSbomGenerator.cxx b/Source/cmExportInstallSbomGenerator.cxx
index d944383..f9641a1 100644
--- a/Source/cmExportInstallSbomGenerator.cxx
+++ b/Source/cmExportInstallSbomGenerator.cxx
@@ -216,15 +216,11 @@
   cmGeneratorTarget* gte, cmTargetExport const* te,
   cmGeneratorFileSet const* fileSet, cm::optional<std::string> const& config)
 {
-  cmGeneratorExpression ge(*gte->Makefile->GetCMakeInstance());
-  auto cge =
-    ge.Parse(te->FileSetGenerators.at(fileSet->GetName())->GetDestination());
+  cmInstallFileSetGenerator::DestinationContext result =
+    te->FileSetGenerators.at(fileSet->GetName())
+      ->GetDestination(gte, config.value_or(""));
 
-  std::string const unescapedDest =
-    cge->Evaluate(gte->LocalGenerator, config.value_or(""), gte);
-  bool const isConfigDependent = cge->GetHadContextSensitiveCondition();
-
-  if (config && !isConfigDependent) {
+  if (config && !result.HadContextSensitiveCondition) {
     return {};
   }
 
@@ -241,9 +237,9 @@
   }
 
   cm::optional<std::string> dest = cmOutputConverter::EscapeForCMake(
-    unescapedDest, cmOutputConverter::WrapQuotes::NoWrap);
+    result.UnescapedDestination, cmOutputConverter::WrapQuotes::NoWrap);
 
-  if (!cmSystemTools::FileIsFullPath(unescapedDest)) {
+  if (!cmSystemTools::FileIsFullPath(result.UnescapedDestination)) {
     dest = cmStrCat("@prefix@/"_s, *dest);
   }
 
diff --git a/Source/cmFindBase.cxx b/Source/cmFindBase.cxx
index 3e98e3f..e9e8dfd 100644
--- a/Source/cmFindBase.cxx
+++ b/Source/cmFindBase.cxx
@@ -184,18 +184,16 @@
       }
       // ensure a macro is not specified as validator
       auto const& validatorName = args[j];
-      cmList macros{ this->Makefile->GetProperty("MACROS") };
-      if (std::find_if(macros.begin(), macros.end(),
-                       [&validatorName](std::string const& item) {
-                         return cmSystemTools::Strucmp(validatorName.c_str(),
-                                                       item.c_str()) == 0;
-                       }) != macros.end()) {
+      if (this->Makefile->GetState()
+            ->GetCommandType(validatorName)
+            .value_or(cmStateEnums::CommandType::Macro) !=
+          cmStateEnums::CommandType::Function) {
         this->SetError(cmStrCat(
           "command specified for VALIDATOR is not a function: ", args[j],
           '.'));
         return false;
       }
-      this->ValidatorName = args[j];
+      this->ValidatorName = validatorName;
     } else if (this->CheckCommonArgument(args[j])) {
       doing = DoingNone;
     } else {
diff --git a/Source/cmFunctionCommand.cxx b/Source/cmFunctionCommand.cxx
index 60c3b90..456cb72 100644
--- a/Source/cmFunctionCommand.cxx
+++ b/Source/cmFunctionCommand.cxx
@@ -19,6 +19,7 @@
 #include "cmPolicies.h"
 #include "cmRange.h"
 #include "cmState.h"
+#include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
@@ -175,7 +176,7 @@
   mf.RecordPolicies(f.Policies);
   mf.RecordDiagnostics(f.Diagnostics);
   return mf.GetState()->AddScriptedCommand(
-    this->Args.front(),
+    this->Args.front(), cmStateEnums::CommandType::Function,
     BT<cmState::Command>(std::move(f),
                          mf.GetBacktrace().Push(this->GetStartingContext())),
     mf);
diff --git a/Source/cmGetCMakePropertyCommand.cxx b/Source/cmGetCMakePropertyCommand.cxx
index 7497afa..db8f8ec 100644
--- a/Source/cmGetCMakePropertyCommand.cxx
+++ b/Source/cmGetCMakePropertyCommand.cxx
@@ -8,6 +8,7 @@
 #include "cmGlobalGenerator.h"
 #include "cmList.h"
 #include "cmMakefile.h"
+#include "cmPolicies.h"
 #include "cmState.h"
 #include "cmValue.h"
 
@@ -27,7 +28,9 @@
     if (cmValue varsProp = status.GetMakefile().GetProperty("VARIABLES")) {
       output = *varsProp;
     }
-  } else if (args[1] == "MACROS") {
+  } else if (args[1] == "MACROS" &&
+             status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0217) !=
+               cmPolicies::NEW) {
     output.clear();
     if (cmValue macrosProp = status.GetMakefile().GetProperty("MACROS")) {
       output = *macrosProp;
diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx
index 1fb9d4b..8137d69 100644
--- a/Source/cmInstallCommand.cxx
+++ b/Source/cmInstallCommand.cxx
@@ -1912,6 +1912,15 @@
   cmInstallGenerator::MessageLevel message =
     cmInstallGenerator::SelectMessageLevel(helper.Makefile, message_never);
 
+  // Check for an absolute destination.
+  if (cmGeneratorExpression::Find(*destination) == std::string::npos &&
+      cmSystemTools::FileIsFullPath(*destination)) {
+    helper.Makefile->IssueDiagnostic(
+      cmDiagnostics::CMD_INSTALL_ABSOLUTE_DESTINATION,
+      cmStrCat("INSTALL command given absolute DESTINATION path (",
+               *destination, ").\n"));
+  }
+
   // Create the directory install generator.
   helper.Makefile->AddInstallGenerator(
     cm::make_unique<cmInstallDirectoryGenerator>(
diff --git a/Source/cmInstallCommandArguments.h b/Source/cmInstallCommandArguments.h
index 7bf861e..c8b1ad9 100644
--- a/Source/cmInstallCommandArguments.h
+++ b/Source/cmInstallCommandArguments.h
@@ -22,7 +22,7 @@
     this->GenericArguments = args;
   }
 
-  // Compute destination path.and check permissions
+  // Compute destination path and check permissions.
   bool Finalize();
 
   std::string const& GetDestination() const;
diff --git a/Source/cmInstallDirectoryGenerator.cxx b/Source/cmInstallDirectoryGenerator.cxx
index cf1fac9..bd62dd3 100644
--- a/Source/cmInstallDirectoryGenerator.cxx
+++ b/Source/cmInstallDirectoryGenerator.cxx
@@ -118,6 +118,9 @@
 std::string cmInstallDirectoryGenerator::GetDestination(
   std::string const& config) const
 {
-  return cmGeneratorExpression::Evaluate(this->Destination,
-                                         this->LocalGenerator, config);
+  std::string dest = cmGeneratorExpression::Evaluate(
+    this->Destination, this->LocalGenerator, config);
+  cmInstallGenerator::CheckAbsoluteDestination(dest, this->LocalGenerator,
+                                               this->Backtrace);
+  return dest;
 }
diff --git a/Source/cmInstallFileSetGenerator.cxx b/Source/cmInstallFileSetGenerator.cxx
index 85b8275..e6e9f03 100644
--- a/Source/cmInstallFileSetGenerator.cxx
+++ b/Source/cmInstallFileSetGenerator.cxx
@@ -4,6 +4,7 @@
 
 #include <algorithm>
 #include <map>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -21,6 +22,7 @@
 #include "cmList.h"
 #include "cmListFileCache.h"
 #include "cmLocalGenerator.h"
+#include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmStringAlgorithms.h"
 #include "cmTarget.h"
@@ -91,11 +93,32 @@
   return true;
 }
 
+std::string cmInstallFileSetGenerator::GetDestination() const
+{
+  cmInstallGenerator::CheckAbsoluteDestination(
+    this->Destination, this->LocalGenerator, this->Backtrace);
+  return this->Destination;
+}
+
 std::string cmInstallFileSetGenerator::GetDestination(
   std::string const& config) const
 {
-  return cmGeneratorExpression::Evaluate(this->Destination,
-                                         this->LocalGenerator, config);
+  return this->GetDestination(this->Target, config).UnescapedDestination;
+}
+
+cmInstallFileSetGenerator::DestinationContext
+cmInstallFileSetGenerator::GetDestination(cmGeneratorTarget* gte,
+                                          std::string const& config) const
+{
+  cmGeneratorExpression ge(*gte->Makefile->GetCMakeInstance());
+  std::unique_ptr<cmCompiledGeneratorExpression> cge =
+    ge.Parse(this->Destination);
+
+  std::string const dest = cge->Evaluate(gte->LocalGenerator, config, gte);
+  cmInstallGenerator::CheckAbsoluteDestination(dest, gte->LocalGenerator,
+                                               this->Backtrace);
+
+  return { dest, cge->GetHadContextSensitiveCondition() };
 }
 
 void cmInstallFileSetGenerator::GenerateScriptForConfig(
diff --git a/Source/cmInstallFileSetGenerator.h b/Source/cmInstallFileSetGenerator.h
index ff56201..b0488fb 100644
--- a/Source/cmInstallFileSetGenerator.h
+++ b/Source/cmInstallFileSetGenerator.h
@@ -9,8 +9,9 @@
 
 #include "cmInstallGenerator.h"
 
-class cmGeneratorTarget;
+class cmExportInstallCMakeConfigGenerator;
 class cmGeneratorFileSet;
+class cmGeneratorTarget;
 class cmListFileBacktrace;
 class cmLocalGenerator;
 
@@ -28,14 +29,23 @@
 
   bool Compute(cmLocalGenerator* lg) override;
 
+  struct DestinationContext
+  {
+    std::string UnescapedDestination;
+    bool HadContextSensitiveCondition;
+  };
   std::string GetDestination(std::string const& config) const;
-  std::string GetDestination() const { return this->Destination; }
+  DestinationContext GetDestination(cmGeneratorTarget* gt,
+                                    std::string const& config) const;
   bool GetOptional() const { return this->Optional; }
   std::string GetFileSetName() const { return this->FileSetName; }
   cmGeneratorFileSet const* GetFileSet() const { return this->FileSet; };
   cmGeneratorTarget* GetTarget() const { return this->Target; }
 
 protected:
+  friend cmExportInstallCMakeConfigGenerator;
+  std::string GetDestination() const;
+
   void GenerateScriptForConfig(std::ostream& os, std::string const& config,
                                Indent indent) override;
 
diff --git a/Source/cmInstallFilesGenerator.cxx b/Source/cmInstallFilesGenerator.cxx
index 6da1862..f5613fa 100644
--- a/Source/cmInstallFilesGenerator.cxx
+++ b/Source/cmInstallFilesGenerator.cxx
@@ -54,8 +54,11 @@
 std::string cmInstallFilesGenerator::GetDestination(
   std::string const& config) const
 {
-  return cmGeneratorExpression::Evaluate(this->Destination,
-                                         this->LocalGenerator, config);
+  std::string dest = cmGeneratorExpression::Evaluate(
+    this->Destination, this->LocalGenerator, config);
+  cmInstallGenerator::CheckAbsoluteDestination(dest, this->LocalGenerator,
+                                               this->Backtrace);
+  return dest;
 }
 
 std::string cmInstallFilesGenerator::GetRename(std::string const& config) const
diff --git a/Source/cmInstallGenerator.cxx b/Source/cmInstallGenerator.cxx
index dd9ade7..9317f94 100644
--- a/Source/cmInstallGenerator.cxx
+++ b/Source/cmInstallGenerator.cxx
@@ -7,6 +7,9 @@
 
 #include <cm/string_view>
 
+#include "cmDiagnostics.h"
+#include "cmListFileCache.h"
+#include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -229,6 +232,19 @@
   return result;
 }
 
+void cmInstallGenerator::CheckAbsoluteDestination(
+  std::string const& dest, cmLocalGenerator* lg, cmListFileBacktrace const& bt)
+{
+  if (!cmSystemTools::FileIsFullPath(dest)) {
+    return;
+  }
+  lg->IssueDiagnostic(
+    cmDiagnostics::CMD_INSTALL_ABSOLUTE_DESTINATION,
+    cmStrCat("INSTALL command given absolute DESTINATION path (", dest,
+             ").\n"),
+    bt);
+}
+
 cmInstallGenerator::MessageLevel cmInstallGenerator::SelectMessageLevel(
   cmMakefile* mf, bool never)
 {
diff --git a/Source/cmInstallGenerator.h b/Source/cmInstallGenerator.h
index 572d192..d35a4b6 100644
--- a/Source/cmInstallGenerator.h
+++ b/Source/cmInstallGenerator.h
@@ -56,6 +56,9 @@
   /** Get the install destination as it should appear in the
       installation script.  */
   static std::string ConvertToAbsoluteDestination(std::string const& dest);
+  static void CheckAbsoluteDestination(std::string const& dest,
+                                       cmLocalGenerator* lg,
+                                       cmListFileBacktrace const& bt);
 
   /** Test if this generator installs something for a given configuration.  */
   bool InstallsForConfig(std::string const& config);
diff --git a/Source/cmInstallTargetGenerator.cxx b/Source/cmInstallTargetGenerator.cxx
index 6b89f86..e2e7924 100644
--- a/Source/cmInstallTargetGenerator.cxx
+++ b/Source/cmInstallTargetGenerator.cxx
@@ -479,8 +479,11 @@
 std::string cmInstallTargetGenerator::GetDestination(
   std::string const& config) const
 {
-  return cmGeneratorExpression::Evaluate(
-    this->Destination, this->Target->GetLocalGenerator(), config);
+  cmLocalGenerator* lg = this->Target->GetLocalGenerator();
+  std::string dest =
+    cmGeneratorExpression::Evaluate(this->Destination, lg, config);
+  cmInstallGenerator::CheckAbsoluteDestination(dest, lg, this->Backtrace);
+  return dest;
 }
 
 std::string cmInstallTargetGenerator::GetInstallFilename(
diff --git a/Source/cmMacroCommand.cxx b/Source/cmMacroCommand.cxx
index f193bce..ae825ef 100644
--- a/Source/cmMacroCommand.cxx
+++ b/Source/cmMacroCommand.cxx
@@ -19,6 +19,7 @@
 #include "cmPolicies.h"
 #include "cmRange.h"
 #include "cmState.h"
+#include "cmStateTypes.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
@@ -170,7 +171,11 @@
                                     cmExecutionStatus& status)
 {
   cmMakefile& mf = status.GetMakefile();
-  mf.AppendProperty("MACROS", this->Args[0]);
+  if (status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0217) !=
+      cmPolicies::NEW) {
+
+    mf.AppendProperty("MACROS", this->Args[0]);
+  }
   // create a new command and add it to cmake
   cmMacroHelperCommand f;
   f.Args = this->Args;
@@ -179,7 +184,7 @@
   mf.RecordPolicies(f.Policies);
   mf.RecordDiagnostics(f.Diagnostics);
   return mf.GetState()->AddScriptedCommand(
-    this->Args[0],
+    this->Args[0], cmStateEnums::CommandType::Macro,
     BT<cmState::Command>(std::move(f),
                          mf.GetBacktrace().Push(this->GetStartingContext())),
     mf);
diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx
index c9ce1df..c962997 100644
--- a/Source/cmNinjaNormalTargetGenerator.cxx
+++ b/Source/cmNinjaNormalTargetGenerator.cxx
@@ -1652,6 +1652,14 @@
     }
   }
 
+  // For split Swift builds, ensure the link edge depends on the target's own
+  // .swiftmodule so the emit-module edge runs even when no other target in
+  // the build depends on it (e.g. install-only targets).
+  std::string swiftModuleOutput = this->GetSwiftModuleOutput(config);
+  if (!swiftModuleOutput.empty()) {
+    linkBuild.ImplicitDeps.emplace_back(std::move(swiftModuleOutput));
+  }
+
   // Ninja should restat after linking if and only if there are byproducts.
   vars["RESTAT"] = byproducts.ExplicitOuts.empty() ? "" : "1";
 
diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx
index 12ecbee..762da6c 100644
--- a/Source/cmNinjaTargetGenerator.cxx
+++ b/Source/cmNinjaTargetGenerator.cxx
@@ -1438,6 +1438,7 @@
   this->Configs[config].SwiftOutputMap[""] = deps;
 
   cmGeneratedFileStream output(mapFilePath);
+  output.SetCopyIfDifferent(true);
   output << this->Configs[config].SwiftOutputMap;
 
   // Add flag
@@ -2202,6 +2203,9 @@
   // Importable targets keep -emit-module on compile so swiftc still emits
   // .swiftdoc.  When splitting module emission, both the .swiftmodule output
   // and -emit-module flags move entirely to the separate emit-module edge.
+  if (targetIsImportable) {
+    this->Configs[config].SwiftModuleOutput = moduleFilepath;
+  }
   if (targetIsImportable && !emitModuleSeparately) {
     objBuild.Outputs.push_back(moduleFilepath);
   }
@@ -2276,11 +2280,6 @@
 
   objBuild.OrderOnlyDeps.push_back(this->OrderDependsTargetForTarget(config));
 
-  // Write object build
-  this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
-                                         objBuild,
-                                         this->ForceResponseFile() ? -1 : 0);
-
   // Write a separate emit-module build edge that produces .swiftmodule
   // without compile outputs. This allows downstream Swift targets to start
   // compiling as soon as the module interface is ready, overlapping with
@@ -2311,7 +2310,16 @@
     this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
                                            modBuild,
                                            this->ForceResponseFile() ? -1 : 0);
+
+    // Both edges share the same -output-file-map; serialize the compile
+    // edge after emit-module so they do not race on the module .swiftdeps.
+    objBuild.OrderOnlyDeps.push_back(moduleFilepath);
   }
+
+  // Write object build
+  this->GetGlobalGenerator()->WriteBuild(this->GetImplFileStream(fileConfig),
+                                         objBuild,
+                                         this->ForceResponseFile() ? -1 : 0);
 }
 
 void cmNinjaTargetGenerator::WriteTargetDependInfo(std::string const& lang,
@@ -2673,6 +2681,16 @@
   return {};
 }
 
+std::string cmNinjaTargetGenerator::GetSwiftModuleOutput(
+  std::string const& config) const
+{
+  auto const it = this->Configs.find(config);
+  if (it != this->Configs.end()) {
+    return it->second.SwiftModuleOutput;
+  }
+  return {};
+}
+
 void cmNinjaTargetGenerator::EnsureDirectoryExists(
   std::string const& path) const
 {
diff --git a/Source/cmNinjaTargetGenerator.h b/Source/cmNinjaTargetGenerator.h
index c706e75..41f3f7c 100644
--- a/Source/cmNinjaTargetGenerator.h
+++ b/Source/cmNinjaTargetGenerator.h
@@ -216,6 +216,7 @@
   void AdditionalCleanFiles(std::string const& config);
 
   cmNinjaDeps GetObjects(std::string const& config) const;
+  std::string GetSwiftModuleOutput(std::string const& config) const;
 
   void EnsureDirectoryExists(std::string const& dir) const;
   void EnsureParentDirectoryExists(std::string const& path) const;
@@ -278,6 +279,8 @@
     Json::Value SwiftOutputMap;
     cmNinjaDeps ExtraFiles;
     std::unique_ptr<MacOSXContentGeneratorType> MacOSXContentGenerator;
+    // Path declared as the .swiftmodule output (compile or emit-module edge).
+    std::string SwiftModuleOutput;
   };
 
   std::map<std::string, ByConfig> Configs;
diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h
index 57d2597..1ef7f10 100644
--- a/Source/cmPolicies.h
+++ b/Source/cmPolicies.h
@@ -647,7 +647,10 @@
          "Ninja generators emit Swift modules separately from compilation.",  \
          4, 4, 0, WARN)                                                       \
   SELECT(POLICY, CMP0216, "Swift targets have a default project name.", 4, 4, \
-         0, WARN)
+         0, WARN)                                                             \
+  SELECT(POLICY, CMP0217,                                                     \
+         "The MACROS directory property does not exist anymore.", 4, 4, 0,    \
+         WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
@@ -738,14 +741,15 @@
 
     /** \brief Always the last entry.
      *
-     * Useful mostly to avoid adding a comma the last policy when adding a new
-     * one.
+     * Useful mostly to avoid adding a comma the last policy when adding
+     * a new one.
      */
     CMPCOUNT
   };
 
   //! convert a string policy ID into a number
-  static bool GetPolicyID(char const* id, /* out */ cmPolicies::PolicyID& pid);
+  static bool GetPolicyID(char const* id,
+                          /* out */ cmPolicies::PolicyID& pid);
 
   //! Return whether a policy has been removed.
   static bool IsRemoved(cmPolicies::PolicyID id);
diff --git a/Source/cmSetDirectoryPropertiesCommand.cxx b/Source/cmSetDirectoryPropertiesCommand.cxx
index cdfd068..024c76b 100644
--- a/Source/cmSetDirectoryPropertiesCommand.cxx
+++ b/Source/cmSetDirectoryPropertiesCommand.cxx
@@ -4,6 +4,7 @@
 
 #include "cmExecutionStatus.h"
 #include "cmMakefile.h"
+#include "cmPolicies.h"
 
 // cmSetDirectoryPropertiesCommand
 bool cmSetDirectoryPropertiesCommand(std::vector<std::string> const& args,
@@ -27,7 +28,9 @@
         "Variables and cache variables should be set using SET command");
       return false;
     }
-    if (prop == "MACROS") {
+    if (prop == "MACROS" &&
+        status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0217) !=
+          cmPolicies::NEW) {
       status.SetError(
         "Commands and macros cannot be set using SET_CMAKE_PROPERTIES");
       return false;
diff --git a/Source/cmState.cxx b/Source/cmState.cxx
index e458b4c..ecfc0a2 100644
--- a/Source/cmState.cxx
+++ b/Source/cmState.cxx
@@ -414,7 +414,10 @@
 {
   assert(name == cmSystemTools::LowerCase(name));
   assert(this->BuiltinCommands.find(name) == this->BuiltinCommands.end());
-  this->BuiltinCommands.emplace(name, std::move(command));
+  this->BuiltinCommands.emplace(
+    name,
+    CommandDescriptor{ cmStateEnums::CommandType::Function,
+                       std::move(command) });
 }
 
 static bool InvokeBuiltinCommand(cmState::BuiltinCommand command,
@@ -522,8 +525,31 @@
   this->AddUnexpectedCommand(name, error);
 }
 
-bool cmState::AddScriptedCommand(std::string const& name, BT<Command> command,
-                                 cmMakefile& mf)
+cmState::CommandDescriptor::CommandDescriptor(CommandType type,
+                                              Command command)
+  : Type(type)
+  , Script(std::move(command))
+{
+}
+cmState::CommandDescriptor::CommandDescriptor(
+  CommandDescriptor&& descriptor) noexcept
+  : Type(descriptor.Type)
+  , Script(std::move(descriptor.Script))
+{
+}
+
+cmState::CommandDescriptor& cmState::CommandDescriptor::operator=(
+  CommandDescriptor&& descriptor) noexcept
+{
+  this->Type = descriptor.Type;
+  this->Script = std::move(descriptor.Script);
+
+  return *this;
+}
+
+bool cmState::AddScriptedCommand(std::string const& name,
+                                 cmStateEnums::CommandType type,
+                                 BT<Command> command, cmMakefile& mf)
 {
   std::string sName = cmSystemTools::LowerCase(name);
 
@@ -538,30 +564,60 @@
   }
 
   // if the command already exists, give a new name to the old command.
-  if (Command oldCmd = this->GetCommandByExactName(sName)) {
-    this->ScriptedCommands["_" + sName] = oldCmd;
+  if (CommandDescriptor const* oldCmd =
+        this->GetCommandDescriptorByExactName(sName)) {
+    this->ScriptedCommands["_" + sName] = *oldCmd;
   }
 
-  this->ScriptedCommands[sName] = std::move(command.Value);
+  this->ScriptedCommands[sName] =
+    CommandDescriptor{ type, std::move(command.Value) };
   return true;
 }
 
+cmState::CommandDescriptor const* cmState::GetCommandDescriptorByExactName(
+  std::string const& name) const
+{
+  auto pos = this->ScriptedCommands.find(name);
+  if (pos != this->ScriptedCommands.end()) {
+    return &pos->second;
+  }
+  pos = this->BuiltinCommands.find(name);
+  if (pos != this->BuiltinCommands.end()) {
+    return &pos->second;
+  }
+  return nullptr;
+}
+
 cmState::Command cmState::GetCommand(std::string const& name) const
 {
   return this->GetCommandByExactName(cmSystemTools::LowerCase(name));
 }
+cm::optional<cmStateEnums::CommandType> cmState::GetCommandType(
+  std::string const& name) const
+{
+  return this->GetCommandTypeByExactName(cmSystemTools::LowerCase(name));
+}
 
 cmState::Command cmState::GetCommandByExactName(std::string const& name) const
 {
-  auto pos = this->ScriptedCommands.find(name);
-  if (pos != this->ScriptedCommands.end()) {
-    return pos->second;
+  CommandDescriptor const* descriptor =
+    this->GetCommandDescriptorByExactName(name);
+  if (!descriptor) {
+    return nullptr;
   }
-  pos = this->BuiltinCommands.find(name);
-  if (pos != this->BuiltinCommands.end()) {
-    return pos->second;
+
+  return descriptor->Script;
+}
+cm::optional<cmStateEnums::CommandType> cmState::GetCommandTypeByExactName(
+  std::string const& name) const
+{
+  CommandDescriptor const* descriptor =
+    this->GetCommandDescriptorByExactName(name);
+  if (!descriptor) {
+    return cm::nullopt;
   }
-  return nullptr;
+
+  return descriptor->Type;
 }
 
 std::vector<std::string> cmState::GetCommandNames() const
diff --git a/Source/cmState.h b/Source/cmState.h
index a25342f..5d8a1e7 100644
--- a/Source/cmState.h
+++ b/Source/cmState.h
@@ -172,8 +172,14 @@
 
   // Returns a command from its name, case insensitive, or nullptr
   Command GetCommand(std::string const& name) const;
+  // Returns a command type from its name, case insensitive, or cm::nullopt
+  cm::optional<cmStateEnums::CommandType> GetCommandType(
+    std::string const& name) const;
   // Returns a command from its name, or nullptr
   Command GetCommandByExactName(std::string const& name) const;
+  // Returns a command type from its name, or cm::nullopt
+  cm::optional<cmStateEnums::CommandType> GetCommandTypeByExactName(
+    std::string const& name) const;
 
   void AddBuiltinCommand(std::string const& name, Command command);
   void AddBuiltinCommand(std::string const& name, BuiltinCommand command);
@@ -186,7 +192,8 @@
   void AddUnexpectedCommand(std::string const& name, char const* error);
   void AddUnexpectedFlowControlCommand(std::string const& name,
                                        char const* error);
-  bool AddScriptedCommand(std::string const& name, BT<Command> command,
+  bool AddScriptedCommand(std::string const& name,
+                          cmStateEnums::CommandType type, BT<Command> command,
                           cmMakefile& mf);
   void RemoveBuiltinCommand(std::string const& name);
   void RemoveUserDefinedCommands();
@@ -277,8 +284,32 @@
 
   cmPropertyDefinitionMap PropertyDefinitions;
   std::vector<std::string> EnabledLanguages;
-  std::unordered_map<std::string, Command> BuiltinCommands;
-  std::unordered_map<std::string, Command> ScriptedCommands;
+
+  class CommandDescriptor
+  {
+  public:
+    using CommandType = cmStateEnums::CommandType;
+
+    CommandDescriptor()
+      : Type(CommandType::Macro)
+      , Script(nullptr)
+    {
+    }
+    CommandDescriptor(CommandType type, Command command);
+    CommandDescriptor(CommandDescriptor&& descriptor) noexcept;
+
+    CommandDescriptor& operator=(CommandDescriptor&& descriptor) noexcept;
+    CommandDescriptor& operator=(CommandDescriptor const& descriptor) =
+      default;
+
+    CommandType Type;
+    Command Script;
+  };
+  CommandDescriptor const* GetCommandDescriptorByExactName(
+    std::string const& name) const;
+
+  std::unordered_map<std::string, CommandDescriptor> BuiltinCommands;
+  std::unordered_map<std::string, CommandDescriptor> ScriptedCommands;
   std::unordered_set<std::string> FlowControlCommands;
   cmPropertyMap GlobalProperties;
   std::unique_ptr<cmCacheManager> CacheManager;
diff --git a/Source/cmStateTypes.h b/Source/cmStateTypes.h
index 6e2d456..1b7043f 100644
--- a/Source/cmStateTypes.h
+++ b/Source/cmStateTypes.h
@@ -41,6 +41,12 @@
   UNWINDING
 };
 
+enum class CommandType
+{
+  Macro,
+  Function
+};
+
 // There are multiple overlapping ranges represented here. Be aware that adding
 // a value to this enumeration may cause failures in numerous places which
 // assume details about the ordering.
diff --git a/Tests/RunCMake/CMP0217/CMP0217-NEW.cmake b/Tests/RunCMake/CMP0217/CMP0217-NEW.cmake
new file mode 100644
index 0000000..881d941
--- /dev/null
+++ b/Tests/RunCMake/CMP0217/CMP0217-NEW.cmake
@@ -0,0 +1,10 @@
+
+cmake_policy(SET CMP0217 NEW)
+
+macro(FOO)
+endmacro()
+
+get_property(macros DIRECTORY PROPERTY MACROS)
+if (macros)
+  set(RunCMake_TEST_FAILED "unexpected content for MACROS directory property.")
+endif()
diff --git a/Tests/RunCMake/CMP0217/CMP0217-OLD.cmake b/Tests/RunCMake/CMP0217/CMP0217-OLD.cmake
new file mode 100644
index 0000000..dee0cc3
--- /dev/null
+++ b/Tests/RunCMake/CMP0217/CMP0217-OLD.cmake
@@ -0,0 +1,10 @@
+
+cmake_policy(SET CMP0217 OLD)
+
+macro(FOO)
+endmacro()
+
+get_property(macros DIRECTORY PROPERTY MACROS)
+if (NOT "FOO" IN_LIST macros)
+  set(RunCMake_TEST_FAILED "unexpected content for MACROS directory property.")
+endif()
diff --git a/Tests/RunCMake/CMP0217/CMakeLists.txt b/Tests/RunCMake/CMP0217/CMakeLists.txt
new file mode 100644
index 0000000..955802c
--- /dev/null
+++ b/Tests/RunCMake/CMP0217/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 4.0)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CMP0217/RunCMakeTest.cmake b/Tests/RunCMake/CMP0217/RunCMakeTest.cmake
new file mode 100644
index 0000000..8b6cffa
--- /dev/null
+++ b/Tests/RunCMake/CMP0217/RunCMakeTest.cmake
@@ -0,0 +1,4 @@
+include(RunCMake)
+
+run_cmake(CMP0217-OLD)
+run_cmake(CMP0217-NEW)
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index b763eb7..6261947 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -184,6 +184,7 @@
 if(WIN32)
   add_RunCMake_test(CMP0212)
 endif()
+add_RunCMake_test(CMP0217)
 
 if(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
   add_RunCMake_test(CMP0194 -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION})
diff --git a/Tests/RunCMake/CMakePresets/ErrorAbsInstallPath-stderr.txt b/Tests/RunCMake/CMakePresets/ErrorAbsInstallPath-stderr.txt
new file mode 100644
index 0000000..6d9315e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/ErrorAbsInstallPath-stderr.txt
@@ -0,0 +1,5 @@
+CMake Warning \(unused-cli\):
+  Manually-specified variables were not used by the project:
+
+    RunCMake_GENERATOR
+    UNUSED_VARIABLE$
diff --git a/Tests/RunCMake/CMakePresets/ErrorAbsInstallPath.cmake b/Tests/RunCMake/CMakePresets/ErrorAbsInstallPath.cmake
new file mode 100644
index 0000000..d6fe2ff
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/ErrorAbsInstallPath.cmake
@@ -0,0 +1,6 @@
+cmake_diagnostic(GET CMD_INSTALL_ABSOLUTE_DESTINATION action)
+if(NOT "${action}" STREQUAL SEND_ERROR)
+   message(SEND_ERROR
+     "wrong action for diagnostic CMD_INSTALL_ABSOLUTE_DESTINATION"
+     " (expected 'SEND_ERROR', actual '${action}')")
+endif()
diff --git a/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake b/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
index d7a0eb6..8ca58a2 100644
--- a/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
@@ -416,7 +416,9 @@
 set(CMakePresets_FILE "${RunCMake_SOURCE_DIR}/Warnings12.json.in")
 run_cmake_presets(NoWarningFlags)
 run_cmake_presets(WarningFlags)
+run_cmake_presets(WarningAbsInstallPath)
 run_cmake_presets(DisableWarningFlags)
+run_cmake_presets(ErrorAbsInstallPath)
 run_cmake_presets(ErrorDev)
 run_cmake_presets(ErrorUninitialized)
 run_cmake_presets(ErrorUnusedCli)
diff --git a/Tests/RunCMake/CMakePresets/WarningAbsInstallPath-stderr.txt b/Tests/RunCMake/CMakePresets/WarningAbsInstallPath-stderr.txt
new file mode 100644
index 0000000..6d9315e
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/WarningAbsInstallPath-stderr.txt
@@ -0,0 +1,5 @@
+CMake Warning \(unused-cli\):
+  Manually-specified variables were not used by the project:
+
+    RunCMake_GENERATOR
+    UNUSED_VARIABLE$
diff --git a/Tests/RunCMake/CMakePresets/WarningAbsInstallPath.cmake b/Tests/RunCMake/CMakePresets/WarningAbsInstallPath.cmake
new file mode 100644
index 0000000..fc59b1c
--- /dev/null
+++ b/Tests/RunCMake/CMakePresets/WarningAbsInstallPath.cmake
@@ -0,0 +1,6 @@
+cmake_diagnostic(GET CMD_INSTALL_ABSOLUTE_DESTINATION action)
+if(NOT "${action}" STREQUAL WARN)
+   message(SEND_ERROR
+     "wrong action for diagnostic CMD_INSTALL_ABSOLUTE_DESTINATION"
+     " (expected 'WARN', actual '${action}')")
+endif()
diff --git a/Tests/RunCMake/CMakePresets/Warnings12.json.in b/Tests/RunCMake/CMakePresets/Warnings12.json.in
index 650f111..701e5f4 100644
--- a/Tests/RunCMake/CMakePresets/Warnings12.json.in
+++ b/Tests/RunCMake/CMakePresets/Warnings12.json.in
@@ -30,6 +30,20 @@
       }
     },
     {
+      "name": "WarningAbsInstallPath",
+      "inherits": "NoWarningFlags",
+      "warnings": {
+        "installAbsoluteDestination": true
+      }
+    },
+    {
+      "name": "ErrorAbsInstallPath",
+      "inherits": "NoWarningFlags",
+      "errors": {
+        "installAbsoluteDestination": true
+      }
+    },
+    {
       "name": "ErrorDev",
       "inherits": "NoWarningFlags",
       "errors": {
diff --git a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-cmake-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-cmake-ctest-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-cmake-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-cmake-ctest-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-ctest-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-ctest-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-env-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-env-ctest-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-env-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-OFF-env-ctest-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-cmake-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-cmake-ctest-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-cmake-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-cmake-ctest-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-ctest-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-ctest-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-env-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-env-ctest-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-env-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVerify-ON-env-ctest-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-cmake-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-cmake-ctest-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-cmake-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-cmake-ctest-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-ctest-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-ctest-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-env-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-env-ctest-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-env-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/FailDrop-TLSVersion-1.1-env-ctest-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/Diagnostics/CacheInit.cmake b/Tests/RunCMake/Diagnostics/CacheInit.cmake
index 1af1495..24baac2 100644
--- a/Tests/RunCMake/Diagnostics/CacheInit.cmake
+++ b/Tests/RunCMake/Diagnostics/CacheInit.cmake
@@ -1,4 +1,5 @@
 include(Assertions.cmake)
 
 expect(CMD_AUTHOR IGNORE)
+expect(CMD_INSTALL_ABSOLUTE_DESTINATION IGNORE)
 expect(CMD_DEPRECATED SEND_ERROR)
diff --git a/Tests/RunCMake/Diagnostics/CommandLine3.cmake b/Tests/RunCMake/Diagnostics/CommandLine3.cmake
index e11e17d..96f8e1a 100644
--- a/Tests/RunCMake/Diagnostics/CommandLine3.cmake
+++ b/Tests/RunCMake/Diagnostics/CommandLine3.cmake
@@ -1,3 +1,5 @@
 include(Assertions.cmake)
 
 expect_cached(CMD_UNINITIALIZED IGNORE)
+
+expect_cached(CMD_INSTALL_ABSOLUTE_DESTINATION WARN)
diff --git a/Tests/RunCMake/Diagnostics/RunCMakeTest.cmake b/Tests/RunCMake/Diagnostics/RunCMakeTest.cmake
index 5ffb6fa..5f77f8e 100644
--- a/Tests/RunCMake/Diagnostics/RunCMakeTest.cmake
+++ b/Tests/RunCMake/Diagnostics/RunCMakeTest.cmake
@@ -12,4 +12,4 @@
 run_cmake_with_options(CommandLine1 -Werror=author -Wdeprecated)
 run_cmake_with_options(CommandLine1 -Wno-deprecated -Werror=author)
 run_cmake_with_options(CommandLine2 -Werror=author -Wno-deprecated)
-run_cmake_with_options(CommandLine3 -Wno-error=uninitialized)
+run_cmake_with_options(CommandLine3 -Wno-error=uninitialized -Winstall-absolute-destination)
diff --git a/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDep-check.cmake b/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDep-check.cmake
new file mode 100644
index 0000000..90038ea
--- /dev/null
+++ b/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDep-check.cmake
@@ -0,0 +1,14 @@
+# The link edge for a Swift library must implicitly depend on the
+# .swiftmodule so the emit-module edge runs even when no other target
+# in the build depends on it.
+if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+  set(path "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/impl-Debug.ninja")
+else()
+  set(path "${RunCMake_TEST_BINARY_DIR}/build.ninja")
+endif()
+file(READ "${path}" build_ninja)
+
+if(NOT build_ninja MATCHES "build [^\n]*(libL\\.a|L\\.lib)[^\n]*:.*\\|[^\n]*L\\.swiftmodule")
+  string(APPEND RunCMake_TEST_FAILED
+    "Link edge for L does not depend on L.swiftmodule.\n")
+endif()
diff --git a/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDep.cmake b/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDep.cmake
new file mode 100644
index 0000000..7ef69cf
--- /dev/null
+++ b/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDep.cmake
@@ -0,0 +1,12 @@
+cmake_policy(SET CMP0157 NEW)
+cmake_policy(SET CMP0215 NEW)
+
+if(NOT CMAKE_GENERATOR MATCHES "Ninja")
+  message(SEND_ERROR "this test must use a Ninja generator, found ${CMAKE_GENERATOR}")
+endif()
+
+enable_language(Swift)
+
+# A standalone library with no dependent Swift targets.  The link edge
+# must still depend on the .swiftmodule so the emit-module edge runs.
+add_library(L STATIC L.swift)
diff --git a/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDepCustomPath-check.cmake b/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDepCustomPath-check.cmake
new file mode 100644
index 0000000..2aca41d
--- /dev/null
+++ b/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDepCustomPath-check.cmake
@@ -0,0 +1,14 @@
+# Same as EmitModuleSeparatelyLinkDep but with a custom module directory.
+# The link edge must depend on the actual emit-module output path, not a
+# default that doesn't match the target's configuration.
+if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+  set(path "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/impl-Debug.ninja")
+else()
+  set(path "${RunCMake_TEST_BINARY_DIR}/build.ninja")
+endif()
+file(READ "${path}" build_ninja)
+
+if(NOT build_ninja MATCHES "build [^\n]*(libL\\.a|L\\.lib)[^\n]*:.*\\|[^\n]*custom(/|\\\\)[^\n]*L\\.swiftmodule")
+  string(APPEND RunCMake_TEST_FAILED
+    "Link edge for L does not depend on custom/.../L.swiftmodule.\n")
+endif()
diff --git a/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDepCustomPath.cmake b/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDepCustomPath.cmake
new file mode 100644
index 0000000..fbb3ac8
--- /dev/null
+++ b/Tests/RunCMake/Swift/EmitModuleSeparatelyLinkDepCustomPath.cmake
@@ -0,0 +1,15 @@
+cmake_policy(SET CMP0157 NEW)
+cmake_policy(SET CMP0215 NEW)
+
+if(NOT CMAKE_GENERATOR MATCHES "Ninja")
+  message(SEND_ERROR "this test must use a Ninja generator, found ${CMAKE_GENERATOR}")
+endif()
+
+enable_language(Swift)
+
+# A standalone library with a custom module output path (like swiftCore
+# in the Swift stdlib).  The link edge must depend on the actual
+# emit-module output, not GetSwiftModulePath().
+add_library(L STATIC L.swift)
+set_target_properties(L PROPERTIES
+  Swift_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/custom)
diff --git a/Tests/RunCMake/Swift/EmitModuleSeparatelyOrdering-check.cmake b/Tests/RunCMake/Swift/EmitModuleSeparatelyOrdering-check.cmake
new file mode 100644
index 0000000..b42cfd1
--- /dev/null
+++ b/Tests/RunCMake/Swift/EmitModuleSeparatelyOrdering-check.cmake
@@ -0,0 +1,28 @@
+# The Swift object compile edge must order-only depend on the .swiftmodule
+# produced by the separate emit-module edge.  Both edges share the same
+# -output-file-map; running them concurrently corrupts the module-level
+# swift-dependencies file referenced by that map.
+if(RunCMake_GENERATOR_IS_MULTI_CONFIG)
+  set(path "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/impl-Debug.ninja")
+else()
+  set(path "${RunCMake_TEST_BINARY_DIR}/build.ninja")
+endif()
+file(READ "${path}" build_ninja)
+
+# Find the object compile edge for L and confirm L.swiftmodule appears
+# after the order-only `||` separator.  The edge ends at the next `build `
+# at the start of a line (or end of file).
+string(REGEX MATCH
+  "build [^\n]*L\\.swift\\.o[^\n]*:[^\n]*(\n [^\n]+)*"
+  obj_edge "${build_ninja}")
+
+if(NOT obj_edge)
+  string(APPEND RunCMake_TEST_FAILED
+    "Could not find object compile edge for L.swift.\n")
+  return()
+endif()
+
+if(NOT obj_edge MATCHES "\\|\\|[^\n]*L\\.swiftmodule")
+  string(APPEND RunCMake_TEST_FAILED
+    "Object compile edge for L.swift is missing an order-only dependency on L.swiftmodule.\nEdge:\n${obj_edge}\n")
+endif()
diff --git a/Tests/RunCMake/Swift/EmitModuleSeparatelyOrdering.cmake b/Tests/RunCMake/Swift/EmitModuleSeparatelyOrdering.cmake
new file mode 100644
index 0000000..6264453
--- /dev/null
+++ b/Tests/RunCMake/Swift/EmitModuleSeparatelyOrdering.cmake
@@ -0,0 +1,14 @@
+cmake_policy(SET CMP0157 NEW)
+cmake_policy(SET CMP0215 NEW)
+
+if(NOT CMAKE_GENERATOR MATCHES "Ninja")
+  message(SEND_ERROR "this test must use a Ninja generator, found ${CMAKE_GENERATOR}")
+endif()
+
+enable_language(Swift)
+
+# The compile and emit-module edges share the same -output-file-map.
+# Within the target, the object compile edge must order-only depend on the
+# .swiftmodule so the two edges do not run concurrently and corrupt the
+# shared module-level swift-dependencies entry.
+add_library(L STATIC L.swift)
diff --git a/Tests/RunCMake/Swift/RunCMakeTest.cmake b/Tests/RunCMake/Swift/RunCMakeTest.cmake
index 5b63f43..a56d6bf 100644
--- a/Tests/RunCMake/Swift/RunCMakeTest.cmake
+++ b/Tests/RunCMake/Swift/RunCMakeTest.cmake
@@ -171,6 +171,14 @@
   endblock()
 
   block()
+    run_cmake(EmitModuleSeparatelyLinkDep)
+  endblock()
+
+  block()
+    run_cmake(EmitModuleSeparatelyLinkDepCustomPath)
+  endblock()
+
+  block()
     set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/EmitModuleSeparatelyExistingModulePath-build)
     run_cmake(EmitModuleSeparatelyExistingModulePath)
     set(RunCMake_TEST_NO_CLEAN 1)
@@ -179,6 +187,10 @@
   endblock()
 
   block()
+    run_cmake(EmitModuleSeparatelyOrdering)
+  endblock()
+
+  block()
     if(CMAKE_SYSTEM_NAME MATCHES Windows)
       set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/ImportLibraryFlags-build)
       run_cmake(ImportLibraryFlags)
diff --git a/Tests/RunCMake/ctest_submit/CDashSubmitHeaders-stderr.txt b/Tests/RunCMake/ctest_submit/CDashSubmitHeaders-stderr.txt
index a42e3f4..f6dcbc8 100644
--- a/Tests/RunCMake/ctest_submit/CDashSubmitHeaders-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/CDashSubmitHeaders-stderr.txt
@@ -1 +1 @@
- *Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
+ *Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
diff --git a/Tests/RunCMake/ctest_submit/CDashSubmitQuiet-stderr.txt b/Tests/RunCMake/ctest_submit/CDashSubmitQuiet-stderr.txt
index 093eefb..cb14372 100644
--- a/Tests/RunCMake/ctest_submit/CDashSubmitQuiet-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/CDashSubmitQuiet-stderr.txt
@@ -1,3 +1,3 @@
  *Error when uploading file: .*/Configure\.xml
- *Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
+ *Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
  *Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/CDashSubmitVerbose-stderr.txt b/Tests/RunCMake/ctest_submit/CDashSubmitVerbose-stderr.txt
index a42e3f4..f6dcbc8 100644
--- a/Tests/RunCMake/ctest_submit/CDashSubmitVerbose-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/CDashSubmitVerbose-stderr.txt
@@ -1 +1 @@
- *Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
+ *Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
diff --git a/Tests/RunCMake/ctest_submit/FILESNoBuildId-stderr.txt b/Tests/RunCMake/ctest_submit/FILESNoBuildId-stderr.txt
index a42e3f4..f6dcbc8 100644
--- a/Tests/RunCMake/ctest_submit/FILESNoBuildId-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FILESNoBuildId-stderr.txt
@@ -1 +1 @@
- *Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
+ *Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-cmake-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-cmake-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-cmake-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-cmake-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-env-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-env-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-env-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-env-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-OFF-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-cmake-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-cmake-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-cmake-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-cmake-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-env-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-env-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-env-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-env-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-TLSVerify-ON-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-cmake-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-cmake-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-cmake-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-cmake-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-env-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-env-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-env-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-env-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-TLSVersion-1.1-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-http-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-http-stderr.txt
index 9c3402e..d1a06c4 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-http-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-http-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/FailDrop-https-stderr.txt b/Tests/RunCMake/ctest_submit/FailDrop-https-stderr.txt
index 24d412d..085e90a 100644
--- a/Tests/RunCMake/ctest_submit/FailDrop-https-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/FailDrop-https-stderr.txt
@@ -1,2 +1,2 @@
-Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
+Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?|The requested URL returned error:|Protocol "https" (not supported or disabled|not supported|disabled)|.* was built with SSL disabled).*
    Problems when submitting via HTTP
diff --git a/Tests/RunCMake/ctest_submit/PARTSDone-stderr.txt b/Tests/RunCMake/ctest_submit/PARTSDone-stderr.txt
index 2c5f900..8210ee3 100644
--- a/Tests/RunCMake/ctest_submit/PARTSDone-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/PARTSDone-stderr.txt
@@ -1,3 +1,3 @@
  *Error when uploading file: .*/Done\.xml
- *Error message was: ([Cc]ould *n.t resolve host:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
+ *Error message was: ([Cc]ould *n.t resolve( host)?:? '?badhostname\.invalid'?.*|The requested URL returned error:.*)
  *Problems when submitting via HTTP
diff --git a/Tests/RunCMake/find_file/VALIDATOR-specify-macro-stderr.txt b/Tests/RunCMake/find_file/VALIDATOR-specify-macro-stderr.txt
index f6391ad..1f4da93 100644
--- a/Tests/RunCMake/find_file/VALIDATOR-specify-macro-stderr.txt
+++ b/Tests/RunCMake/find_file/VALIDATOR-specify-macro-stderr.txt
@@ -2,3 +2,9 @@
   find_file command specified for VALIDATOR is not a function: check\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at VALIDATOR-specify-macro\.cmake:[0-9]+ \(find_file\):
+  find_file command specified for VALIDATOR is not a function: check2\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/find_file/VALIDATOR-specify-macro.cmake b/Tests/RunCMake/find_file/VALIDATOR-specify-macro.cmake
index ec0ce9a..861fbf5 100644
--- a/Tests/RunCMake/find_file/VALIDATOR-specify-macro.cmake
+++ b/Tests/RunCMake/find_file/VALIDATOR-specify-macro.cmake
@@ -3,3 +3,6 @@
 endmacro()
 
 find_file(result NAMES input.txt VALIDATOR check)
+
+add_subdirectory(subdir)
+find_file(result NAMES input.txt VALIDATOR check2)
diff --git a/Tests/RunCMake/find_file/subdir/CMakeLists.txt b/Tests/RunCMake/find_file/subdir/CMakeLists.txt
new file mode 100644
index 0000000..1edb776
--- /dev/null
+++ b/Tests/RunCMake/find_file/subdir/CMakeLists.txt
@@ -0,0 +1,3 @@
+
+macro(CHECK2 result path)
+endmacro()
diff --git a/Tests/RunCMake/find_library/VALIDATOR-specify-macro-stderr.txt b/Tests/RunCMake/find_library/VALIDATOR-specify-macro-stderr.txt
index 8f7f8c4..c220085 100644
--- a/Tests/RunCMake/find_library/VALIDATOR-specify-macro-stderr.txt
+++ b/Tests/RunCMake/find_library/VALIDATOR-specify-macro-stderr.txt
@@ -2,3 +2,9 @@
   find_library command specified for VALIDATOR is not a function: check\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at VALIDATOR-specify-macro\.cmake:[0-9]+ \(find_library\):
+  find_library command specified for VALIDATOR is not a function: check2\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/find_library/VALIDATOR-specify-macro.cmake b/Tests/RunCMake/find_library/VALIDATOR-specify-macro.cmake
index fa90d72..f652828 100644
--- a/Tests/RunCMake/find_library/VALIDATOR-specify-macro.cmake
+++ b/Tests/RunCMake/find_library/VALIDATOR-specify-macro.cmake
@@ -3,3 +3,6 @@
 endmacro()
 
 find_library(result NAMES input.txt VALIDATOR check)
+
+add_subdirectory(subdir)
+find_library(result NAMES input.txt VALIDATOR check2)
diff --git a/Tests/RunCMake/find_library/subdir/CMakeLists.txt b/Tests/RunCMake/find_library/subdir/CMakeLists.txt
new file mode 100644
index 0000000..1edb776
--- /dev/null
+++ b/Tests/RunCMake/find_library/subdir/CMakeLists.txt
@@ -0,0 +1,3 @@
+
+macro(CHECK2 result path)
+endmacro()
diff --git a/Tests/RunCMake/find_path/VALIDATOR-specify-macro-stderr.txt b/Tests/RunCMake/find_path/VALIDATOR-specify-macro-stderr.txt
index 9f0e3e8..bff9f35 100644
--- a/Tests/RunCMake/find_path/VALIDATOR-specify-macro-stderr.txt
+++ b/Tests/RunCMake/find_path/VALIDATOR-specify-macro-stderr.txt
@@ -2,3 +2,9 @@
   find_path command specified for VALIDATOR is not a function: check\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at VALIDATOR-specify-macro\.cmake:[0-9]+ \(find_path\):
+  find_path command specified for VALIDATOR is not a function: check2\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/find_path/VALIDATOR-specify-macro.cmake b/Tests/RunCMake/find_path/VALIDATOR-specify-macro.cmake
index 62b44ed..f1faded 100644
--- a/Tests/RunCMake/find_path/VALIDATOR-specify-macro.cmake
+++ b/Tests/RunCMake/find_path/VALIDATOR-specify-macro.cmake
@@ -3,3 +3,6 @@
 endmacro()
 
 find_path(result NAMES input.txt VALIDATOR check)
+
+add_subdirectory(subdir)
+find_path(result NAMES input.txt VALIDATOR check2)
diff --git a/Tests/RunCMake/find_path/subdir/CMakeLists.txt b/Tests/RunCMake/find_path/subdir/CMakeLists.txt
new file mode 100644
index 0000000..1edb776
--- /dev/null
+++ b/Tests/RunCMake/find_path/subdir/CMakeLists.txt
@@ -0,0 +1,3 @@
+
+macro(CHECK2 result path)
+endmacro()
diff --git a/Tests/RunCMake/find_program/VALIDATOR-specify-macro-stderr.txt b/Tests/RunCMake/find_program/VALIDATOR-specify-macro-stderr.txt
index 949f29d..567e577 100644
--- a/Tests/RunCMake/find_program/VALIDATOR-specify-macro-stderr.txt
+++ b/Tests/RunCMake/find_program/VALIDATOR-specify-macro-stderr.txt
@@ -2,3 +2,9 @@
   find_program command specified for VALIDATOR is not a function: check\.
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)
+
+
+CMake Error at VALIDATOR-specify-macro\.cmake:[0-9]+ \(find_program\):
+  find_program command specified for VALIDATOR is not a function: check2\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/find_program/VALIDATOR-specify-macro.cmake b/Tests/RunCMake/find_program/VALIDATOR-specify-macro.cmake
index 43bb9ec..3e5e579 100644
--- a/Tests/RunCMake/find_program/VALIDATOR-specify-macro.cmake
+++ b/Tests/RunCMake/find_program/VALIDATOR-specify-macro.cmake
@@ -3,3 +3,6 @@
 endmacro()
 
 find_program(result NAMES input.txt VALIDATOR check)
+
+add_subdirectory(subdir)
+find_program(result NAMES input.txt VALIDATOR check2)
diff --git a/Tests/RunCMake/find_program/subdir/CMakeLists.txt b/Tests/RunCMake/find_program/subdir/CMakeLists.txt
new file mode 100644
index 0000000..1edb776
--- /dev/null
+++ b/Tests/RunCMake/find_program/subdir/CMakeLists.txt
@@ -0,0 +1,3 @@
+
+macro(CHECK2 result path)
+endmacro()
diff --git a/Tests/RunCMake/install/DIRECTORY-AbsoluteDest-error-result.txt b/Tests/RunCMake/install/DIRECTORY-AbsoluteDest-error-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/install/DIRECTORY-AbsoluteDest-error-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/DIRECTORY-AbsoluteDest-error-stderr.txt b/Tests/RunCMake/install/DIRECTORY-AbsoluteDest-error-stderr.txt
new file mode 100644
index 0000000..d742940
--- /dev/null
+++ b/Tests/RunCMake/install/DIRECTORY-AbsoluteDest-error-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error \(install-absolute-destination\) at DIRECTORY-AbsoluteDest-error\.cmake:[0-9]+ \(install\):
+  INSTALL command given absolute DESTINATION path \(/absolute/path\)\.
diff --git a/Tests/RunCMake/install/DIRECTORY-AbsoluteDest-error.cmake b/Tests/RunCMake/install/DIRECTORY-AbsoluteDest-error.cmake
new file mode 100644
index 0000000..a76ddb4
--- /dev/null
+++ b/Tests/RunCMake/install/DIRECTORY-AbsoluteDest-error.cmake
@@ -0,0 +1 @@
+install(DIRECTORY DESTINATION /absolute/path)
diff --git a/Tests/RunCMake/install/FILES-AbsoluteDest-error-result.txt b/Tests/RunCMake/install/FILES-AbsoluteDest-error-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/install/FILES-AbsoluteDest-error-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/FILES-AbsoluteDest-error-stderr.txt b/Tests/RunCMake/install/FILES-AbsoluteDest-error-stderr.txt
new file mode 100644
index 0000000..689d48b
--- /dev/null
+++ b/Tests/RunCMake/install/FILES-AbsoluteDest-error-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error \(install-absolute-destination\) at FILES-AbsoluteDest-error\.cmake:[0-9]+ \(install\):
+  INSTALL command given absolute DESTINATION path \(/absolute/path\)\.
diff --git a/Tests/RunCMake/install/FILES-AbsoluteDest-error.cmake b/Tests/RunCMake/install/FILES-AbsoluteDest-error.cmake
new file mode 100644
index 0000000..28cb56b
--- /dev/null
+++ b/Tests/RunCMake/install/FILES-AbsoluteDest-error.cmake
@@ -0,0 +1 @@
+install(FILES empty.c DESTINATION /absolute/path)
diff --git a/Tests/RunCMake/install/FILES-AbsoluteDest-warn-stderr.txt b/Tests/RunCMake/install/FILES-AbsoluteDest-warn-stderr.txt
new file mode 100644
index 0000000..f320131
--- /dev/null
+++ b/Tests/RunCMake/install/FILES-AbsoluteDest-warn-stderr.txt
@@ -0,0 +1,2 @@
+CMake Warning \(install-absolute-destination\) at FILES-AbsoluteDest-warn\.cmake:[0-9]+ \(install\):
+  INSTALL command given absolute DESTINATION path \(/absolute/path\)\.
diff --git a/Tests/RunCMake/install/FILES-AbsoluteDest-warn.cmake b/Tests/RunCMake/install/FILES-AbsoluteDest-warn.cmake
new file mode 100644
index 0000000..28cb56b
--- /dev/null
+++ b/Tests/RunCMake/install/FILES-AbsoluteDest-warn.cmake
@@ -0,0 +1 @@
+install(FILES empty.c DESTINATION /absolute/path)
diff --git a/Tests/RunCMake/install/RunCMakeTest.cmake b/Tests/RunCMake/install/RunCMakeTest.cmake
index ff8e812..5e40e88 100644
--- a/Tests/RunCMake/install/RunCMakeTest.cmake
+++ b/Tests/RunCMake/install/RunCMakeTest.cmake
@@ -82,6 +82,14 @@
 run_cmake(FILES-DESTINATION-bad)
 run_cmake(FILES-RENAME-bad)
 run_cmake(TARGETS-DESTINATION-bad)
+run_cmake_with_options(DIRECTORY-AbsoluteDest-error -Werror=install-absolute-destination)
+run_cmake_with_options(FILES-AbsoluteDest-error -Werror=install-absolute-destination)
+run_cmake_with_options(FILES-AbsoluteDest-warn -Winstall-absolute-destination)
+run_cmake_with_options(TARGETS-AbsoluteDest-error -Werror=install-absolute-destination)
+run_cmake_with_options(TARGETS-AbsoluteDest-warn -Winstall-absolute-destination)
+run_cmake_with_options(TARGETS-AbsoluteDest-archive-error -Werror=install-absolute-destination)
+run_cmake_with_options(TARGETS-AbsoluteDest-library-error -Werror=install-absolute-destination)
+run_cmake_with_options(TARGETS-AbsoluteDest-runtime-error -Werror=install-absolute-destination)
 run_cmake(EXPORT-Component)
 run_cmake(EXPORT-FindDependencyExportGate)
 run_cmake(EXPORT-OldIFace)
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-archive-error-result.txt b/Tests/RunCMake/install/TARGETS-AbsoluteDest-archive-error-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-archive-error-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-archive-error-stderr.txt b/Tests/RunCMake/install/TARGETS-AbsoluteDest-archive-error-stderr.txt
new file mode 100644
index 0000000..e091ab0
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-archive-error-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error \(install-absolute-destination\) at TARGETS-AbsoluteDest-archive-error\.cmake:[0-9]+ \(install\):
+  INSTALL command given absolute DESTINATION path \(/absolute/archive\)\.
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-archive-error.cmake b/Tests/RunCMake/install/TARGETS-AbsoluteDest-archive-error.cmake
new file mode 100644
index 0000000..3ecc7e3
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-archive-error.cmake
@@ -0,0 +1,3 @@
+enable_language(C)
+add_library(mylib STATIC empty.c)
+install(TARGETS mylib ARCHIVE DESTINATION /absolute/archive)
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-error-result.txt b/Tests/RunCMake/install/TARGETS-AbsoluteDest-error-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-error-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-error-stderr.txt b/Tests/RunCMake/install/TARGETS-AbsoluteDest-error-stderr.txt
new file mode 100644
index 0000000..22c0d98
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-error-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error \(install-absolute-destination\) at TARGETS-AbsoluteDest-error\.cmake:[0-9]+ \(install\):
+  INSTALL command given absolute DESTINATION path \(/absolute/path\)\.
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-error.cmake b/Tests/RunCMake/install/TARGETS-AbsoluteDest-error.cmake
new file mode 100644
index 0000000..b16af5c
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-error.cmake
@@ -0,0 +1,12 @@
+enable_language(C)
+add_library(mylib STATIC empty.c)
+
+# FIXME(#27770): This diagnostic is issued at generate time. Currently, the
+# install generator doesn't capture the immediate diagnostic state, only the
+# top-most state of the current subdirectory, which we cannot affect (because
+# this test was include()d. When we fix that, we should test by changing the
+# diagnostic action here rather than by -W... in the test CLI arguments.
+# cmake_diagnostic(SET CMD_INSTALL_ABSOLUTE_DESTINATION SEND_ERROR)
+
+cmake_diagnostic(PROMOTE CMD_INSTALL_ABSOLUTE_DESTINATION WARN)
+install(TARGETS mylib DESTINATION /absolute/path)
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-library-error-result.txt b/Tests/RunCMake/install/TARGETS-AbsoluteDest-library-error-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-library-error-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-library-error-stderr.txt b/Tests/RunCMake/install/TARGETS-AbsoluteDest-library-error-stderr.txt
new file mode 100644
index 0000000..368434d
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-library-error-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error \(install-absolute-destination\) at TARGETS-AbsoluteDest-library-error\.cmake:[0-9]+ \(install\):
+  INSTALL command given absolute DESTINATION path \(/absolute/lib\)\.
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-library-error.cmake b/Tests/RunCMake/install/TARGETS-AbsoluteDest-library-error.cmake
new file mode 100644
index 0000000..b5be8fd
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-library-error.cmake
@@ -0,0 +1,3 @@
+enable_language(C)
+add_library(mylib MODULE empty.c)
+install(TARGETS mylib LIBRARY DESTINATION /absolute/lib)
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-runtime-error-result.txt b/Tests/RunCMake/install/TARGETS-AbsoluteDest-runtime-error-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-runtime-error-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-runtime-error-stderr.txt b/Tests/RunCMake/install/TARGETS-AbsoluteDest-runtime-error-stderr.txt
new file mode 100644
index 0000000..279fb88
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-runtime-error-stderr.txt
@@ -0,0 +1,2 @@
+CMake Error \(install-absolute-destination\) at TARGETS-AbsoluteDest-runtime-error\.cmake:[0-9]+ \(install\):
+  INSTALL command given absolute DESTINATION path \(/absolute/bin\)\.
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-runtime-error.cmake b/Tests/RunCMake/install/TARGETS-AbsoluteDest-runtime-error.cmake
new file mode 100644
index 0000000..638f7fb
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-runtime-error.cmake
@@ -0,0 +1,3 @@
+enable_language(C)
+add_executable(myexe empty.c)
+install(TARGETS myexe RUNTIME DESTINATION /absolute/bin)
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-warn-stderr.txt b/Tests/RunCMake/install/TARGETS-AbsoluteDest-warn-stderr.txt
new file mode 100644
index 0000000..730a8ed
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-warn-stderr.txt
@@ -0,0 +1,2 @@
+CMake Warning \(install-absolute-destination\) at TARGETS-AbsoluteDest-warn\.cmake:[0-9]+ \(install\):
+  INSTALL command given absolute DESTINATION path \(/absolute/path\)\.
diff --git a/Tests/RunCMake/install/TARGETS-AbsoluteDest-warn.cmake b/Tests/RunCMake/install/TARGETS-AbsoluteDest-warn.cmake
new file mode 100644
index 0000000..e2ca4be
--- /dev/null
+++ b/Tests/RunCMake/install/TARGETS-AbsoluteDest-warn.cmake
@@ -0,0 +1,3 @@
+enable_language(C)
+add_library(mylib STATIC empty.c)
+install(TARGETS mylib DESTINATION /absolute/path)
diff --git a/Utilities/Scripts/update-curl.bash b/Utilities/Scripts/update-curl.bash
index 3964f5a..213b297 100755
--- a/Utilities/Scripts/update-curl.bash
+++ b/Utilities/Scripts/update-curl.bash
@@ -8,7 +8,7 @@
 readonly ownership="Curl Upstream <curl-library@lists.haxx.se>"
 readonly subtree="Utilities/cmcurl"
 readonly repo="https://github.com/curl/curl.git"
-readonly tag="curl-8_19_0"
+readonly tag="curl-8_20_0"
 readonly shortlog=false
 readonly exact_tree_match=false
 readonly paths="
diff --git a/Utilities/cmcurl/CMake/FindBrotli.cmake b/Utilities/cmcurl/CMake/FindBrotli.cmake
index b5437e7..bd3363c 100644
--- a/Utilities/cmcurl/CMake/FindBrotli.cmake
+++ b/Utilities/cmcurl/CMake/FindBrotli.cmake
@@ -84,10 +84,6 @@
 endif()
 
 if(BROTLI_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_brotli_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::brotli)
     add_library(CURL::brotli INTERFACE IMPORTED)
     set_target_properties(CURL::brotli PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindCares.cmake b/Utilities/cmcurl/CMake/FindCares.cmake
index 3c05c3b..26efdf8 100644
--- a/Utilities/cmcurl/CMake/FindCares.cmake
+++ b/Utilities/cmcurl/CMake/FindCares.cmake
@@ -37,11 +37,15 @@
 
 set(_cares_pc_requires "libcares")
 
-if(CURL_USE_PKGCONFIG AND
-   NOT DEFINED CARES_INCLUDE_DIR AND
+if(NOT DEFINED CARES_INCLUDE_DIR AND
    NOT DEFINED CARES_LIBRARY)
-  find_package(PkgConfig QUIET)
-  pkg_check_modules(_cares ${_cares_pc_requires})
+  if(CURL_USE_PKGCONFIG)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(_cares ${_cares_pc_requires})
+  endif()
+  if(NOT _cares_FOUND AND CURL_USE_CMAKECONFIG)
+    find_package(c-ares CONFIG QUIET)
+  endif()
 endif()
 
 if(_cares_FOUND)
@@ -55,6 +59,16 @@
     set(_cares_LIBRARIES    "${_cares_STATIC_LIBRARIES}")
   endif()
   message(STATUS "Found Cares (via pkg-config): ${_cares_INCLUDE_DIRS} (found version \"${CARES_VERSION}\")")
+elseif(c-ares_CONFIG)
+  set(Cares_FOUND TRUE)
+  set(CARES_FOUND TRUE)
+  set(CARES_VERSION ${c-ares_VERSION})
+  if(CARES_USE_STATIC_LIBS)
+    set(_cares_LIBRARIES c-ares::cares_static)
+  else()
+    set(_cares_LIBRARIES c-ares::cares)
+  endif()
+  message(STATUS "Found Cares (via CMake Config): ${c-ares_CONFIG} (found version \"${CARES_VERSION}\")")
 else()
   find_path(CARES_INCLUDE_DIR NAMES "ares.h")
   if(CARES_USE_STATIC_LIBS)
@@ -106,10 +120,6 @@
     list(APPEND _cares_LIBRARIES "iphlpapi")  # for if_indextoname and others
   endif()
 
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_cares_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::cares)
     add_library(CURL::cares INTERFACE IMPORTED)
     set_target_properties(CURL::cares PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindGSS.cmake b/Utilities/cmcurl/CMake/FindGSS.cmake
index e36f75b..9237fb3 100644
--- a/Utilities/cmcurl/CMake/FindGSS.cmake
+++ b/Utilities/cmcurl/CMake/FindGSS.cmake
@@ -258,10 +258,6 @@
 )
 
 if(GSS_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_gss_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::gss)
     add_library(CURL::gss INTERFACE IMPORTED)
     set_target_properties(CURL::gss PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindGnuTLS.cmake b/Utilities/cmcurl/CMake/FindGnuTLS.cmake
index fff57b2..7f5b227 100644
--- a/Utilities/cmcurl/CMake/FindGnuTLS.cmake
+++ b/Utilities/cmcurl/CMake/FindGnuTLS.cmake
@@ -80,10 +80,6 @@
 endif()
 
 if(GNUTLS_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_gnutls_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::gnutls)
     add_library(CURL::gnutls INTERFACE IMPORTED)
     set_target_properties(CURL::gnutls PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindLDAP.cmake b/Utilities/cmcurl/CMake/FindLDAP.cmake
index 2f5cc71..8902b23 100644
--- a/Utilities/cmcurl/CMake/FindLDAP.cmake
+++ b/Utilities/cmcurl/CMake/FindLDAP.cmake
@@ -103,10 +103,6 @@
 endif()
 
 if(LDAP_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_ldap_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::ldap)
     add_library(CURL::ldap INTERFACE IMPORTED)
     set_target_properties(CURL::ldap PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindLibbacktrace.cmake b/Utilities/cmcurl/CMake/FindLibbacktrace.cmake
index c6f7b70..59da7fd 100644
--- a/Utilities/cmcurl/CMake/FindLibbacktrace.cmake
+++ b/Utilities/cmcurl/CMake/FindLibbacktrace.cmake
@@ -47,10 +47,6 @@
   set(_libbacktrace_INCLUDE_DIRS ${LIBBACKTRACE_INCLUDE_DIR})
   set(_libbacktrace_LIBRARIES    ${LIBBACKTRACE_LIBRARY})
 
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_libbacktrace_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::libbacktrace)
     add_library(CURL::libbacktrace INTERFACE IMPORTED)
     set_target_properties(CURL::libbacktrace PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindLibgsasl.cmake b/Utilities/cmcurl/CMake/FindLibgsasl.cmake
index 5ddf957..7fbaa71 100644
--- a/Utilities/cmcurl/CMake/FindLibgsasl.cmake
+++ b/Utilities/cmcurl/CMake/FindLibgsasl.cmake
@@ -79,10 +79,6 @@
 endif()
 
 if(LIBGSASL_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_libgsasl_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::libgsasl)
     add_library(CURL::libgsasl INTERFACE IMPORTED)
     set_target_properties(CURL::libgsasl PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindLibidn2.cmake b/Utilities/cmcurl/CMake/FindLibidn2.cmake
index 336a7f7..dc78734 100644
--- a/Utilities/cmcurl/CMake/FindLibidn2.cmake
+++ b/Utilities/cmcurl/CMake/FindLibidn2.cmake
@@ -80,10 +80,6 @@
 endif()
 
 if(LIBIDN2_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_libidn2_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::libidn2)
     add_library(CURL::libidn2 INTERFACE IMPORTED)
     set_target_properties(CURL::libidn2 PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindLibpsl.cmake b/Utilities/cmcurl/CMake/FindLibpsl.cmake
index 9b1a0cd..2287e67 100644
--- a/Utilities/cmcurl/CMake/FindLibpsl.cmake
+++ b/Utilities/cmcurl/CMake/FindLibpsl.cmake
@@ -80,10 +80,6 @@
 endif()
 
 if(LIBPSL_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_libpsl_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::libpsl)
     add_library(CURL::libpsl INTERFACE IMPORTED)
     set_target_properties(CURL::libpsl PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindLibrtmp.cmake b/Utilities/cmcurl/CMake/FindLibrtmp.cmake
deleted file mode 100644
index 853ba63..0000000
--- a/Utilities/cmcurl/CMake/FindLibrtmp.cmake
+++ /dev/null
@@ -1,116 +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
-#
-###########################################################################
-# Find the librtmp library
-#
-# Input variables:
-#
-# - `LIBRTMP_INCLUDE_DIR`:  Absolute path to librtmp include directory.
-# - `LIBRTMP_LIBRARY`:      Absolute path to `librtmp` library.
-#
-# Defines:
-#
-# - `LIBRTMP_FOUND`:        System has librtmp.
-# - `LIBRTMP_VERSION`:      Version of librtmp.
-# - `CURL::librtmp`:        librtmp library target.
-
-set(_librtmp_pc_requires "librtmp")
-
-if(CURL_USE_PKGCONFIG AND
-   NOT DEFINED LIBRTMP_INCLUDE_DIR AND
-   NOT DEFINED LIBRTMP_LIBRARY)
-  find_package(PkgConfig QUIET)
-  pkg_check_modules(_librtmp ${_librtmp_pc_requires})
-endif()
-
-if(_librtmp_FOUND AND _librtmp_INCLUDE_DIRS)
-  set(Librtmp_FOUND TRUE)
-  set(LIBRTMP_FOUND TRUE)
-  set(LIBRTMP_VERSION ${_librtmp_VERSION})
-  message(STATUS "Found Librtmp (via pkg-config): ${_librtmp_INCLUDE_DIRS} (found version \"${LIBRTMP_VERSION}\")")
-else()
-  find_path(LIBRTMP_INCLUDE_DIR NAMES "librtmp/rtmp.h")
-  find_library(LIBRTMP_LIBRARY NAMES "rtmp")
-
-  unset(LIBRTMP_VERSION CACHE)
-  if(LIBRTMP_INCLUDE_DIR AND EXISTS "${LIBRTMP_INCLUDE_DIR}/librtmp/rtmp.h")
-    set(_version_regex "#[\t ]*define[\t ]+RTMP_LIB_VERSION[\t ]+0x([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F]).*")
-    file(STRINGS "${LIBRTMP_INCLUDE_DIR}/librtmp/rtmp.h" _version_str REGEX "${_version_regex}")
-    string(REGEX REPLACE "${_version_regex}" "\\1" _version_str1 "${_version_str}")
-    string(REGEX REPLACE "${_version_regex}" "\\2" _version_str2 "${_version_str}")
-    if(CMAKE_VERSION VERSION_LESS 3.13)
-      # No support for hex version numbers, just strip leading zeroes
-      string(REGEX REPLACE "^0" "" _version_str1 "${_version_str1}")
-      string(REGEX REPLACE "^0" "" _version_str2 "${_version_str2}")
-    else()
-      math(EXPR _version_str1 "0x${_version_str1}" OUTPUT_FORMAT DECIMAL)
-      math(EXPR _version_str2 "0x${_version_str2}" OUTPUT_FORMAT DECIMAL)
-    endif()
-    set(LIBRTMP_VERSION "${_version_str1}.${_version_str2}")
-    unset(_version_regex)
-    unset(_version_str1)
-    unset(_version_str2)
-  endif()
-
-  include(FindPackageHandleStandardArgs)
-  find_package_handle_standard_args(Librtmp
-    REQUIRED_VARS
-      LIBRTMP_INCLUDE_DIR
-      LIBRTMP_LIBRARY
-    VERSION_VAR
-      LIBRTMP_VERSION
-  )
-
-  if(LIBRTMP_FOUND)
-    set(_librtmp_INCLUDE_DIRS ${LIBRTMP_INCLUDE_DIR})
-    set(_librtmp_LIBRARIES    ${LIBRTMP_LIBRARY})
-  endif()
-
-  mark_as_advanced(LIBRTMP_INCLUDE_DIR LIBRTMP_LIBRARY)
-
-  # Necessary when linking a static librtmp
-  find_package(OpenSSL)
-  if(OpenSSL_FOUND)
-    list(APPEND _librtmp_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
-  endif()
-endif()
-
-if(LIBRTMP_FOUND)
-  if(WIN32)
-    list(APPEND _librtmp_LIBRARIES "winmm")
-  endif()
-
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_librtmp_LIBRARY_DIRS})
-  endif()
-
-  if(NOT TARGET CURL::librtmp)
-    add_library(CURL::librtmp INTERFACE IMPORTED)
-    set_target_properties(CURL::librtmp PROPERTIES
-      INTERFACE_LIBCURL_PC_MODULES "${_librtmp_pc_requires}"
-      INTERFACE_COMPILE_OPTIONS "${_librtmp_CFLAGS}"
-      INTERFACE_INCLUDE_DIRECTORIES "${_librtmp_INCLUDE_DIRS}"
-      INTERFACE_LINK_DIRECTORIES "${_librtmp_LIBRARY_DIRS}"
-      INTERFACE_LINK_LIBRARIES "${_librtmp_LIBRARIES}")
-  endif()
-endif()
diff --git a/Utilities/cmcurl/CMake/FindLibssh.cmake b/Utilities/cmcurl/CMake/FindLibssh.cmake
index 732ab8e..3837d4d 100644
--- a/Utilities/cmcurl/CMake/FindLibssh.cmake
+++ b/Utilities/cmcurl/CMake/FindLibssh.cmake
@@ -106,10 +106,6 @@
     list(APPEND _libssh_LIBRARIES "iphlpapi")  # for if_nametoindex
   endif()
 
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_libssh_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::libssh)
     add_library(CURL::libssh INTERFACE IMPORTED)
     set_target_properties(CURL::libssh PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindLibssh2.cmake b/Utilities/cmcurl/CMake/FindLibssh2.cmake
index abed471..22481d6 100644
--- a/Utilities/cmcurl/CMake/FindLibssh2.cmake
+++ b/Utilities/cmcurl/CMake/FindLibssh2.cmake
@@ -37,11 +37,15 @@
 
 set(_libssh2_pc_requires "libssh2")
 
-if(CURL_USE_PKGCONFIG AND
-   NOT DEFINED LIBSSH2_INCLUDE_DIR AND
+if(NOT DEFINED LIBSSH2_INCLUDE_DIR AND
    NOT DEFINED LIBSSH2_LIBRARY)
-  find_package(PkgConfig QUIET)
-  pkg_check_modules(_libssh2 ${_libssh2_pc_requires})
+  if(CURL_USE_PKGCONFIG)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(_libssh2 ${_libssh2_pc_requires})
+  endif()
+  if(NOT _libssh2_FOUND AND CURL_USE_CMAKECONFIG)
+    find_package(libssh2 CONFIG QUIET)
+  endif()
 endif()
 
 if(_libssh2_FOUND AND _libssh2_INCLUDE_DIRS)
@@ -55,6 +59,16 @@
     set(_libssh2_LIBRARIES    "${_libssh2_STATIC_LIBRARIES}")
   endif()
   message(STATUS "Found Libssh2 (via pkg-config): ${_libssh2_INCLUDE_DIRS} (found version \"${LIBSSH2_VERSION}\")")
+elseif(libssh2_CONFIG)
+  set(Libssh2_FOUND TRUE)
+  set(LIBSSH2_FOUND TRUE)
+  set(LIBSSH2_VERSION ${libssh2_VERSION})
+  if(LIBSSH2_USE_STATIC_LIBS)
+    set(_libssh2_LIBRARIES libssh2::libssh2_static)
+  else()
+    set(_libssh2_LIBRARIES libssh2::libssh2)
+  endif()
+  message(STATUS "Found Libssh2 (via CMake Config): ${libssh2_CONFIG} (found version \"${LIBSSH2_VERSION}\")")
 else()
   find_path(LIBSSH2_INCLUDE_DIR NAMES "libssh2.h")
   if(LIBSSH2_USE_STATIC_LIBS)
@@ -91,10 +105,6 @@
 endif()
 
 if(LIBSSH2_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_libssh2_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::libssh2)
     add_library(CURL::libssh2 INTERFACE IMPORTED)
     set_target_properties(CURL::libssh2 PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindLibuv.cmake b/Utilities/cmcurl/CMake/FindLibuv.cmake
index f9d614b..47d043c 100644
--- a/Utilities/cmcurl/CMake/FindLibuv.cmake
+++ b/Utilities/cmcurl/CMake/FindLibuv.cmake
@@ -90,10 +90,6 @@
 endif()
 
 if(LIBUV_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_libuv_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::libuv)
     add_library(CURL::libuv INTERFACE IMPORTED)
     set_target_properties(CURL::libuv PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindMbedTLS.cmake b/Utilities/cmcurl/CMake/FindMbedTLS.cmake
index 8b67ce9..21a5f4a 100644
--- a/Utilities/cmcurl/CMake/FindMbedTLS.cmake
+++ b/Utilities/cmcurl/CMake/FindMbedTLS.cmake
@@ -45,13 +45,17 @@
 
 set(_mbedtls_pc_requires "mbedtls" "mbedx509" "mbedcrypto")
 
-if(CURL_USE_PKGCONFIG AND
-   NOT DEFINED MBEDTLS_INCLUDE_DIR AND
+if(NOT DEFINED MBEDTLS_INCLUDE_DIR AND
    NOT DEFINED MBEDTLS_LIBRARY AND
    NOT DEFINED MBEDX509_LIBRARY AND
    NOT DEFINED MBEDCRYPTO_LIBRARY)
-  find_package(PkgConfig QUIET)
-  pkg_check_modules(_mbedtls ${_mbedtls_pc_requires})
+  if(CURL_USE_PKGCONFIG)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(_mbedtls ${_mbedtls_pc_requires})
+  endif()
+  if(NOT _mbedtls_FOUND AND CURL_USE_CMAKECONFIG)
+    find_package(MbedTLS CONFIG QUIET)
+  endif()
 endif()
 
 if(_mbedtls_FOUND)
@@ -65,6 +69,17 @@
     set(_mbedtls_LIBRARIES    "${_mbedtls_STATIC_LIBRARIES}")
   endif()
   message(STATUS "Found MbedTLS (via pkg-config): ${_mbedtls_INCLUDE_DIRS} (found version \"${MBEDTLS_VERSION}\")")
+elseif(MbedTLS_CONFIG)
+  set(MbedTLS_FOUND TRUE)
+  set(MBEDTLS_FOUND TRUE)
+  set(MBEDTLS_VERSION ${MbedTLS_VERSION})
+  if(MBEDTLS_VERSION GREATER_EQUAL 4.0.0)
+    set(_mbedtls_LIBRARIES MbedTLS::tfpsacrypto)
+  else()
+    set(_mbedtls_LIBRARIES MbedTLS::mbedcrypto)
+  endif()
+  list(APPEND _mbedtls_LIBRARIES MbedTLS::mbedx509 MbedTLS::mbedtls)
+  message(STATUS "Found MbedTLS (via CMake Config): ${MbedTLS_CONFIG} (found version \"${MBEDTLS_VERSION}\")")
 else()
   set(_mbedtls_pc_requires "")  # Depend on pkg-config only when found via pkg-config
 
@@ -110,10 +125,6 @@
 endif()
 
 if(MBEDTLS_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_mbedtls_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::mbedtls)
     add_library(CURL::mbedtls INTERFACE IMPORTED)
     set_target_properties(CURL::mbedtls PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindNGHTTP2.cmake b/Utilities/cmcurl/CMake/FindNGHTTP2.cmake
index b2a8c97..f93113f 100644
--- a/Utilities/cmcurl/CMake/FindNGHTTP2.cmake
+++ b/Utilities/cmcurl/CMake/FindNGHTTP2.cmake
@@ -37,11 +37,15 @@
 
 set(_nghttp2_pc_requires "libnghttp2")
 
-if(CURL_USE_PKGCONFIG AND
-   NOT DEFINED NGHTTP2_INCLUDE_DIR AND
+if(NOT DEFINED NGHTTP2_INCLUDE_DIR AND
    NOT DEFINED NGHTTP2_LIBRARY)
-  find_package(PkgConfig QUIET)
-  pkg_check_modules(_nghttp2 ${_nghttp2_pc_requires})
+  if(CURL_USE_PKGCONFIG)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(_nghttp2 ${_nghttp2_pc_requires})
+  endif()
+  if(NOT _nghttp2_FOUND AND CURL_USE_CMAKECONFIG)
+    find_package(nghttp2 CONFIG QUIET)
+  endif()
 endif()
 
 if(_nghttp2_FOUND)
@@ -54,6 +58,15 @@
     set(_nghttp2_LIBRARIES    "${_nghttp2_STATIC_LIBRARIES}")
   endif()
   message(STATUS "Found NGHTTP2 (via pkg-config): ${_nghttp2_INCLUDE_DIRS} (found version \"${NGHTTP2_VERSION}\")")
+elseif(nghttp2_CONFIG)
+  set(NGHTTP2_FOUND TRUE)
+  set(NGHTTP2_VERSION ${nghttp2_VERSION})
+  if(NGHTTP2_USE_STATIC_LIBS)
+    set(_nghttp2_LIBRARIES nghttp2::nghttp2_static)
+  else()
+    set(_nghttp2_LIBRARIES nghttp2::nghttp2)
+  endif()
+  message(STATUS "Found NGHTTP2 (via CMake Config): ${nghttp2_CONFIG} (found version \"${NGHTTP2_VERSION}\")")
 else()
   find_path(NGHTTP2_INCLUDE_DIR NAMES "nghttp2/nghttp2.h")
   if(NGHTTP2_USE_STATIC_LIBS)
@@ -91,10 +104,6 @@
 endif()
 
 if(NGHTTP2_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_nghttp2_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::nghttp2)
     add_library(CURL::nghttp2 INTERFACE IMPORTED)
     set_target_properties(CURL::nghttp2 PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindNGHTTP3.cmake b/Utilities/cmcurl/CMake/FindNGHTTP3.cmake
index 57550bf..427c139 100644
--- a/Utilities/cmcurl/CMake/FindNGHTTP3.cmake
+++ b/Utilities/cmcurl/CMake/FindNGHTTP3.cmake
@@ -37,11 +37,15 @@
 
 set(_nghttp3_pc_requires "libnghttp3")
 
-if(CURL_USE_PKGCONFIG AND
-   NOT DEFINED NGHTTP3_INCLUDE_DIR AND
+if(NOT DEFINED NGHTTP3_INCLUDE_DIR AND
    NOT DEFINED NGHTTP3_LIBRARY)
-  find_package(PkgConfig QUIET)
-  pkg_check_modules(_nghttp3 ${_nghttp3_pc_requires})
+  if(CURL_USE_PKGCONFIG)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(_nghttp3 ${_nghttp3_pc_requires})
+  endif()
+  if(NOT _nghttp3_FOUND AND CURL_USE_CMAKECONFIG)
+    find_package(nghttp3 CONFIG QUIET)
+  endif()
 endif()
 
 if(_nghttp3_FOUND)
@@ -54,6 +58,15 @@
     set(_nghttp3_LIBRARIES    "${_nghttp3_STATIC_LIBRARIES}")
   endif()
   message(STATUS "Found NGHTTP3 (via pkg-config): ${_nghttp3_INCLUDE_DIRS} (found version \"${NGHTTP3_VERSION}\")")
+elseif(nghttp3_CONFIG)
+  set(NGHTTP3_FOUND TRUE)
+  set(NGHTTP3_VERSION ${nghttp3_VERSION})
+  if(NGHTTP3_USE_STATIC_LIBS)
+    set(_nghttp3_LIBRARIES nghttp3::nghttp3_static)
+  else()
+    set(_nghttp3_LIBRARIES nghttp3::nghttp3)
+  endif()
+  message(STATUS "Found NGHTTP3 (via CMake Config): ${nghttp3_CONFIG} (found version \"${NGHTTP3_VERSION}\")")
 else()
   find_path(NGHTTP3_INCLUDE_DIR NAMES "nghttp3/nghttp3.h")
   if(NGHTTP3_USE_STATIC_LIBS)
@@ -91,10 +104,6 @@
 endif()
 
 if(NGHTTP3_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_nghttp3_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::nghttp3)
     add_library(CURL::nghttp3 INTERFACE IMPORTED)
     set_target_properties(CURL::nghttp3 PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindNGTCP2.cmake b/Utilities/cmcurl/CMake/FindNGTCP2.cmake
index 615ee4a..e492916 100644
--- a/Utilities/cmcurl/CMake/FindNGTCP2.cmake
+++ b/Utilities/cmcurl/CMake/FindNGTCP2.cmake
@@ -74,12 +74,22 @@
 endif()
 
 set(_tried_pkgconfig FALSE)
-if(CURL_USE_PKGCONFIG AND
-   NOT DEFINED NGTCP2_INCLUDE_DIR AND
+if(NOT DEFINED NGTCP2_INCLUDE_DIR AND
    NOT DEFINED NGTCP2_LIBRARY)
-  find_package(PkgConfig QUIET)
-  pkg_check_modules(_ngtcp2 ${_ngtcp2_pc_requires})
-  set(_tried_pkgconfig TRUE)
+  if(CURL_USE_PKGCONFIG)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(_ngtcp2 ${_ngtcp2_pc_requires})
+    set(_tried_pkgconfig TRUE)
+  endif()
+  if(NOT _ngtcp2_FOUND AND CURL_USE_CMAKECONFIG)
+    find_package(ngtcp2 CONFIG QUIET)
+    # Skip using it if the crypto library target is not available
+    if(ngtcp2_CONFIG AND
+       NOT TARGET ngtcp2::${_crypto_library_lower}_static AND
+       NOT TARGET ngtcp2::${_crypto_library_lower})
+      unset(ngtcp2_CONFIG)
+    endif()
+  endif()
 endif()
 
 if(_ngtcp2_FOUND)
@@ -92,6 +102,15 @@
     set(_ngtcp2_LIBRARIES    "${_ngtcp2_STATIC_LIBRARIES}")
   endif()
   message(STATUS "Found NGTCP2 (via pkg-config): ${_ngtcp2_INCLUDE_DIRS} (found version \"${NGTCP2_VERSION}\")")
+elseif(ngtcp2_CONFIG)
+  set(NGTCP2_FOUND TRUE)
+  set(NGTCP2_VERSION ${ngtcp2_VERSION})
+  if(NGTCP2_USE_STATIC_LIBS)
+    set(_ngtcp2_LIBRARIES ngtcp2::ngtcp2_static ngtcp2::${_crypto_library_lower}_static)
+  else()
+    set(_ngtcp2_LIBRARIES ngtcp2::ngtcp2 ngtcp2::${_crypto_library_lower})
+  endif()
+  message(STATUS "Found NGTCP2 (via CMake Config): ${ngtcp2_CONFIG} (found version \"${NGTCP2_VERSION}\")")
 else()
   find_path(NGTCP2_INCLUDE_DIR NAMES "ngtcp2/ngtcp2.h")
   if(NGTCP2_USE_STATIC_LIBS)
@@ -155,10 +174,6 @@
 endif()
 
 if(NGTCP2_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_ngtcp2_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::ngtcp2)
     add_library(CURL::ngtcp2 INTERFACE IMPORTED)
     set_target_properties(CURL::ngtcp2 PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindNettle.cmake b/Utilities/cmcurl/CMake/FindNettle.cmake
index d22865f..c963180 100644
--- a/Utilities/cmcurl/CMake/FindNettle.cmake
+++ b/Utilities/cmcurl/CMake/FindNettle.cmake
@@ -85,10 +85,6 @@
 endif()
 
 if(NETTLE_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_nettle_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::nettle)
     add_library(CURL::nettle INTERFACE IMPORTED)
     set_target_properties(CURL::nettle PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindQuiche.cmake b/Utilities/cmcurl/CMake/FindQuiche.cmake
index 22482d7..137679b 100644
--- a/Utilities/cmcurl/CMake/FindQuiche.cmake
+++ b/Utilities/cmcurl/CMake/FindQuiche.cmake
@@ -68,10 +68,6 @@
 endif()
 
 if(QUICHE_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_quiche_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::quiche)
     add_library(CURL::quiche INTERFACE IMPORTED)
     set_target_properties(CURL::quiche PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindRustls.cmake b/Utilities/cmcurl/CMake/FindRustls.cmake
index 270e99c..1e07565 100644
--- a/Utilities/cmcurl/CMake/FindRustls.cmake
+++ b/Utilities/cmcurl/CMake/FindRustls.cmake
@@ -104,10 +104,6 @@
     mark_as_advanced(MATH_LIBRARY)
   endif()
 
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_rustls_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::rustls)
     add_library(CURL::rustls INTERFACE IMPORTED)
     set_target_properties(CURL::rustls PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindWolfSSL.cmake b/Utilities/cmcurl/CMake/FindWolfSSL.cmake
index 3a3c05f..964320a 100644
--- a/Utilities/cmcurl/CMake/FindWolfSSL.cmake
+++ b/Utilities/cmcurl/CMake/FindWolfSSL.cmake
@@ -45,11 +45,15 @@
 
 set(_wolfssl_pc_requires "wolfssl")
 
-if(CURL_USE_PKGCONFIG AND
-   NOT DEFINED WOLFSSL_INCLUDE_DIR AND
+if(NOT DEFINED WOLFSSL_INCLUDE_DIR AND
    NOT DEFINED WOLFSSL_LIBRARY)
-  find_package(PkgConfig QUIET)
-  pkg_check_modules(_wolfssl ${_wolfssl_pc_requires})
+  if(CURL_USE_PKGCONFIG)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(_wolfssl ${_wolfssl_pc_requires})
+  endif()
+  if(NOT _wolfssl_FOUND AND CURL_USE_CMAKECONFIG)
+    find_package(wolfssl CONFIG QUIET)
+  endif()
 endif()
 
 if(_wolfssl_FOUND)
@@ -57,6 +61,12 @@
   set(WOLFSSL_FOUND TRUE)
   set(WOLFSSL_VERSION ${_wolfssl_VERSION})
   message(STATUS "Found WolfSSL (via pkg-config): ${_wolfssl_INCLUDE_DIRS} (found version \"${WOLFSSL_VERSION}\")")
+elseif(wolfssl_CONFIG)
+  set(WolfSSL_FOUND TRUE)
+  set(WOLFSSL_FOUND TRUE)
+  set(WOLFSSL_VERSION ${wolfssl_VERSION})
+  set(_wolfssl_LIBRARIES wolfssl::wolfssl)
+  message(STATUS "Found WolfSSL (via CMake Config): ${wolfssl_CONFIG} (found version \"${WOLFSSL_VERSION}\")")
 else()
   find_path(WOLFSSL_INCLUDE_DIR NAMES "wolfssl/ssl.h")
   find_library(WOLFSSL_LIBRARY NAMES "wolfssl")
@@ -113,10 +123,6 @@
     mark_as_advanced(MATH_LIBRARY)
   endif()
 
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_wolfssl_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::wolfssl)
     add_library(CURL::wolfssl INTERFACE IMPORTED)
     set_target_properties(CURL::wolfssl PROPERTIES
diff --git a/Utilities/cmcurl/CMake/FindZstd.cmake b/Utilities/cmcurl/CMake/FindZstd.cmake
index baf6148..8dc620a 100644
--- a/Utilities/cmcurl/CMake/FindZstd.cmake
+++ b/Utilities/cmcurl/CMake/FindZstd.cmake
@@ -46,11 +46,21 @@
 
 set(_zstd_pc_requires "libzstd")
 
-if(CURL_USE_PKGCONFIG AND
-   NOT DEFINED ZSTD_INCLUDE_DIR AND
+if(NOT DEFINED ZSTD_INCLUDE_DIR AND
    NOT DEFINED ZSTD_LIBRARY)
-  find_package(PkgConfig QUIET)
-  pkg_check_modules(_zstd ${_zstd_pc_requires})
+  if(CURL_USE_PKGCONFIG)
+    find_package(PkgConfig QUIET)
+    pkg_check_modules(_zstd ${_zstd_pc_requires})
+  endif()
+  if(NOT _zstd_FOUND AND CURL_USE_CMAKECONFIG)
+    find_package(Zstd CONFIG QUIET)
+    # Skip using if older than v1.4.5
+    if(Zstd_CONFIG AND
+       NOT TARGET zstd::libzstd_static AND
+       NOT TARGET zstd::libzstd_shared)
+      unset(Zstd_CONFIG)
+    endif()
+  endif()
 endif()
 
 if(_zstd_FOUND)
@@ -64,6 +74,17 @@
     set(_zstd_LIBRARIES    "${_zstd_STATIC_LIBRARIES}")
   endif()
   message(STATUS "Found Zstd (via pkg-config): ${_zstd_INCLUDE_DIRS} (found version \"${ZSTD_VERSION}\")")
+elseif(Zstd_CONFIG)
+  set(ZSTD_FOUND TRUE)
+  set(ZSTD_VERSION ${Zstd_VERSION})
+  if(ZSTD_USE_STATIC_LIBS)
+    set(_zstd_LIBRARIES zstd::libzstd_static)
+  elseif(TARGET zstd::libzstd)
+    set(_zstd_LIBRARIES zstd::libzstd)  # v1.5.6+
+  else()
+    set(_zstd_LIBRARIES zstd::libzstd_shared)
+  endif()
+  message(STATUS "Found Zstd (via CMake Config): ${Zstd_CONFIG} (found version \"${ZSTD_VERSION}\")")
 else()
   find_path(ZSTD_INCLUDE_DIR NAMES "zstd.h")
   if(ZSTD_USE_STATIC_LIBS)
@@ -110,10 +131,6 @@
 endif()
 
 if(ZSTD_FOUND)
-  if(CMAKE_VERSION VERSION_LESS 3.13)
-    link_directories(${_zstd_LIBRARY_DIRS})
-  endif()
-
   if(NOT TARGET CURL::zstd)
     add_library(CURL::zstd INTERFACE IMPORTED)
     set_target_properties(CURL::zstd PROPERTIES
diff --git a/Utilities/cmcurl/CMake/Macros.cmake b/Utilities/cmcurl/CMake/Macros.cmake
index e182f21..5e26c38 100644
--- a/Utilities/cmcurl/CMake/Macros.cmake
+++ b/Utilities/cmcurl/CMake/Macros.cmake
@@ -40,20 +40,12 @@
 # Return result in variable: CURL_TEST_OUTPUT
 macro(curl_internal_test _curl_test)
   if(NOT DEFINED "${_curl_test}")
-    string(REPLACE ";" " " _cmake_required_definitions "${CMAKE_REQUIRED_DEFINITIONS}")
-    set(_curl_test_add_libraries "")
-    if(CMAKE_REQUIRED_LIBRARIES)
-      set(_curl_test_add_libraries
-        "-DLINK_LIBRARIES:STRING=${CMAKE_REQUIRED_LIBRARIES}")
-    endif()
-
     message(STATUS "Performing Test ${_curl_test}")
     try_compile(${_curl_test}
       ${PROJECT_BINARY_DIR}
       "${CMAKE_CURRENT_SOURCE_DIR}/CMake/CurlTests.c"
-      CMAKE_FLAGS
-        "-DCOMPILE_DEFINITIONS:STRING=-D${_curl_test} ${CURL_TEST_DEFINES} ${CMAKE_REQUIRED_FLAGS} ${_cmake_required_definitions}"
-        "${_curl_test_add_libraries}"
+      COMPILE_DEFINITIONS "-D${_curl_test}" ${CURL_TEST_DEFINES} ${CMAKE_REQUIRED_FLAGS} ${CMAKE_REQUIRED_DEFINITIONS}
+      LINK_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}"
       OUTPUT_VARIABLE CURL_TEST_OUTPUT)
     if(${_curl_test})
       set(${_curl_test} 1 CACHE INTERNAL "curl test")
@@ -252,3 +244,35 @@
     add_dependencies(tests-clang-tidy ${_target_clang_tidy})
   endif()
 endfunction()
+
+# Internal: Recurse into interface targets and collect their libraries
+# and library paths.
+macro(curl_collect_target_link_options _target)
+  get_target_property(_val ${_target} INTERFACE_LINK_DIRECTORIES)
+  if(_val)
+    list(APPEND _libdirs ${_val})
+  endif()
+  get_target_property(_val ${_target} IMPORTED)
+  if(_val)
+    # LOCATION is empty for interface library targets and safe to ignore.
+    # Explicitly skip this query to avoid CMake v3.18 and older erroring out.
+    get_target_property(_val ${_target} TYPE)
+    if(NOT "${_val}" STREQUAL "INTERFACE_LIBRARY")
+      get_target_property(_val ${_target} LOCATION)
+      if(_val)
+        list(APPEND _libs ${_val})
+      endif()
+    endif()
+  endif()
+  get_target_property(_val ${_target} INTERFACE_LINK_LIBRARIES)
+  if(_val)
+    foreach(_lib IN LISTS _val)
+      if(TARGET "${_lib}")
+        curl_collect_target_link_options(${_lib})
+      else()
+        list(APPEND _libs ${_lib})
+      endif()
+    endforeach()
+  endif()
+  unset(_val)
+endmacro()
diff --git a/Utilities/cmcurl/CMake/OtherTests.cmake b/Utilities/cmcurl/CMake/OtherTests.cmake
index 650a42d..6619f3a 100644
--- a/Utilities/cmcurl/CMake/OtherTests.cmake
+++ b/Utilities/cmcurl/CMake/OtherTests.cmake
@@ -78,7 +78,7 @@
 elseif(NOT HAVE_GETADDRINFO)
   set(HAVE_GETADDRINFO_THREADSAFE FALSE)
 elseif(APPLE OR
-       CMAKE_SYSTEM_NAME STREQUAL "AIX" OR
+       AIX OR CMAKE_SYSTEM_NAME STREQUAL "AIX" OR
        CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
        CMAKE_SYSTEM_NAME STREQUAL "HP-UX" OR
        CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD" OR
diff --git a/Utilities/cmcurl/CMake/PickyWarnings.cmake b/Utilities/cmcurl/CMake/PickyWarnings.cmake
index 1b67b0b..aca79ec 100644
--- a/Utilities/cmcurl/CMake/PickyWarnings.cmake
+++ b/Utilities/cmcurl/CMake/PickyWarnings.cmake
@@ -60,7 +60,9 @@
 endif()
 
 if(PICKY_COMPILER)
-  if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")
+  # Leave disabled for GCC <4.6, because they lack #pragma features to silence locally.
+  if((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.6) OR
+     CMAKE_C_COMPILER_ID MATCHES "Clang")
 
     # https://clang.llvm.org/docs/DiagnosticsReference.html
     # https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
@@ -147,7 +149,7 @@
       list(APPEND _picky_enable
         ${_picky_common_old}
         -Wconditional-uninitialized        # clang  3.0
-        -Wno-used-but-marked-unused        # clang  2.9            # Triggered by typecheck-gcc.h with clang 14+, dependency headers
+        -Wno-used-but-marked-unused        # clang  2.9            # for typecheck-gcc.h with clang 14+, dependency headers
         -Wshift-sign-overflow              # clang  2.9
         -Wshorten-64-to-32                 # clang  1.0
         -Wformat=2                         # clang  2.7  gcc  4.8
@@ -161,7 +163,7 @@
       if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.1)
         list(APPEND _picky_enable
           -Wno-covered-switch-default      # clang  3.1            appleclang  3.1  # Annoying to fix or silence
-          -Wno-disabled-macro-expansion    # clang  3.1            appleclang  3.1  # Triggered by curl/curl.h, standard headers
+          -Wno-disabled-macro-expansion    # clang  3.1            appleclang  3.1  # for std headers, and curl/curl.h (rare combos)
         )
         if(MSVC)
           list(APPEND _picky_enable
@@ -256,6 +258,7 @@
         list(APPEND _picky_enable
           -Warray-compare                  # clang 20.1  gcc 12.0  appleclang 26.4
           -Wc++-hidden-decl                # clang 21.1            appleclang 26.4
+          -Wimplicit-int-enum-cast         # clang 21.1
           -Wjump-misses-init               # clang 21.1  gcc  4.5  appleclang 26.4
           -Wno-implicit-void-ptr-cast      # clang 21.1            appleclang 26.4
           -Wtentative-definition-compat    # clang 21.1            appleclang 26.4
@@ -347,7 +350,7 @@
       endif()
     endif()
 
-    #
+    # Assemble list of flags
 
     set(_picky_skipped "")
     foreach(_ccopt IN LISTS _picky_enable)
@@ -378,15 +381,6 @@
     endforeach()
 
     if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
-      if(CMAKE_C_COMPILER_VERSION VERSION_LESS 4.5)
-        # Avoid false positives
-        list(APPEND _picky "-Wno-shadow")
-        list(APPEND _picky "-Wno-unreachable-code")
-      endif()
-      if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.2 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.6)
-        # GCC <4.6 do not support #pragma to suppress warnings locally. Disable them globally instead.
-        list(APPEND _picky "-Wno-overlength-strings")
-      endif()
       if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.0 AND CMAKE_C_COMPILER_VERSION VERSION_LESS 4.7)
         list(APPEND _picky "-Wno-missing-field-initializers")  # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36750
       endif()
@@ -436,11 +430,13 @@
   endforeach()
 endif()
 
-if(CMAKE_C_STANDARD STREQUAL 90 AND CMAKE_C_COMPILER_ID STREQUAL "AppleClang")
-  if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.2)
+if(CMAKE_C_STANDARD STREQUAL 90)
+  if((CMAKE_C_COMPILER_ID STREQUAL "Clang"      AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.0) OR
+     (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 4.2))
     list(APPEND _picky "-Wno-c99-extensions")  # Avoid: warning: '_Bool' is a C99 extension
   endif()
-  if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 8.1)
+  if((CMAKE_C_COMPILER_ID STREQUAL "Clang"      AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 3.9) OR
+     (CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 8.1))
     list(APPEND _picky "-Wno-comma")  # Just silly
   endif()
 endif()
diff --git a/Utilities/cmcurl/CMake/curl-config.in.cmake b/Utilities/cmcurl/CMake/curl-config.in.cmake
index 4e7c29f..3174771 100644
--- a/Utilities/cmcurl/CMake/curl-config.in.cmake
+++ b/Utilities/cmcurl/CMake/curl-config.in.cmake
@@ -23,6 +23,8 @@
 ###########################################################################
 @PACKAGE_INIT@
 
+option(CURL_USE_CMAKECONFIG "Enable detecting @PROJECT_NAME@ dependencies via CMake Config. Default: @CURL_USE_CMAKECONFIG@"
+  "@CURL_USE_CMAKECONFIG@")
 option(CURL_USE_PKGCONFIG "Enable pkg-config to detect @PROJECT_NAME@ dependencies. Default: @CURL_USE_PKGCONFIG@"
   "@CURL_USE_PKGCONFIG@")
 
@@ -33,6 +35,10 @@
 
 include(CMakeFindDependencyMacro)
 
+if("@HAVE_THREADS_POSIX@" OR "@HAVE_THREADS_POSIX_BORINGSSL@")
+  find_dependency(Threads)  # for Threads::Threads
+endif()
+
 if("@USE_OPENSSL@")
   if("@OPENSSL_VERSION_MAJOR@")
     find_dependency(OpenSSL "@OPENSSL_VERSION_MAJOR@")
@@ -42,8 +48,7 @@
   # Define lib duplicate to fixup lib order for GCC binutils ld in static builds
   if(TARGET OpenSSL::Crypto AND NOT TARGET CURL::OpenSSL_Crypto)
     add_library(CURL::OpenSSL_Crypto INTERFACE IMPORTED)
-    get_target_property(_curl_libname OpenSSL::Crypto LOCATION)
-    set_target_properties(CURL::OpenSSL_Crypto PROPERTIES INTERFACE_LINK_LIBRARIES "${_curl_libname}")
+    set_target_properties(CURL::OpenSSL_Crypto PROPERTIES INTERFACE_LINK_LIBRARIES OpenSSL::Crypto)
   endif()
 endif()
 if("@HAVE_LIBZ@")
@@ -51,13 +56,12 @@
   # Define lib duplicate to fixup lib order for GCC binutils ld in static builds
   if(TARGET ZLIB::ZLIB AND NOT TARGET CURL::ZLIB)
     add_library(CURL::ZLIB INTERFACE IMPORTED)
-    get_target_property(_curl_libname ZLIB::ZLIB LOCATION)
-    set_target_properties(CURL::ZLIB PROPERTIES INTERFACE_LINK_LIBRARIES "${_curl_libname}")
+    set_target_properties(CURL::ZLIB PROPERTIES INTERFACE_LINK_LIBRARIES ZLIB::ZLIB)
   endif()
 endif()
 
 set(_curl_cmake_module_path_save ${CMAKE_MODULE_PATH})
-set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_MODULE_PATH})
+list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
 
 set(_curl_libs "")
 
@@ -93,10 +97,6 @@
   find_dependency(Libpsl MODULE)
   list(APPEND _curl_libs CURL::libpsl)
 endif()
-if("@USE_LIBRTMP@")
-  find_dependency(Librtmp MODULE)
-  list(APPEND _curl_libs CURL::librtmp)
-endif()
 if("@USE_LIBSSH@")
   find_dependency(Libssh MODULE)
   list(APPEND _curl_libs CURL::libssh)
@@ -160,35 +160,9 @@
 
 # Alias for either shared or static library
 if(NOT TARGET @PROJECT_NAME@::@LIB_NAME@)
-  if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.11 AND CMAKE_VERSION VERSION_LESS 3.18)
-    set_target_properties(@PROJECT_NAME@::@LIB_SELECTED@ PROPERTIES IMPORTED_GLOBAL TRUE)
-  endif()
   add_library(@PROJECT_NAME@::@LIB_NAME@ ALIAS @PROJECT_NAME@::@LIB_SELECTED@)
 endif()
 
-if(TARGET @PROJECT_NAME@::@LIB_STATIC@)
-  # CMake before CMP0099 (CMake 3.17 2020-03-20) did not propagate libdirs to
-  # targets. It expected libs to have an absolute filename. As a workaround,
-  # manually apply dependency libdirs, for CMake consumers without this policy.
-  if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.17)
-    cmake_policy(GET CMP0099 _has_CMP0099)  # https://cmake.org/cmake/help/latest/policy/CMP0099.html
-  endif()
-  if(NOT _has_CMP0099 AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND _curl_libs)
-    set(_curl_libdirs "")
-    foreach(_curl_lib IN LISTS _curl_libs)
-      if(TARGET "${_curl_lib}")
-        get_target_property(_curl_libdir "${_curl_lib}" INTERFACE_LINK_DIRECTORIES)
-        if(_curl_libdir)
-          list(APPEND _curl_libdirs "${_curl_libdir}")
-        endif()
-      endif()
-    endforeach()
-    if(_curl_libdirs)
-      target_link_directories(@PROJECT_NAME@::@LIB_STATIC@ INTERFACE ${_curl_libdirs})
-    endif()
-  endif()
-endif()
-
 # For compatibility with CMake's FindCURL.cmake
 set(CURL_VERSION_STRING "@CURLVERSION@")
 set(CURL_LIBRARIES @PROJECT_NAME@::@LIB_NAME@)
diff --git a/Utilities/cmcurl/CMake/unix-cache.cmake b/Utilities/cmcurl/CMake/unix-cache.cmake
index f358a47..f027f66 100644
--- a/Utilities/cmcurl/CMake/unix-cache.cmake
+++ b/Utilities/cmcurl/CMake/unix-cache.cmake
@@ -90,7 +90,6 @@
   set(HAVE_FSETXATTR_5 1)
   set(HAVE_FSETXATTR_6 0)
 endif()
-set(HAVE_FTRUNCATE 1)
 set(HAVE_GETADDRINFO 1)
 if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
   set(HAVE_GETADDRINFO_THREADSAFE 0)
@@ -199,9 +198,12 @@
    CYGWIN)
   set(HAVE_PIPE2 0)
 elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR
+       BSD OR
+       CMAKE_SYSTEM_NAME STREQUAL "DragonFlyBSD" OR
        CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
        CMAKE_SYSTEM_NAME STREQUAL "NetBSD" OR
-       CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
+       CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR
+       CMAKE_SYSTEM_NAME STREQUAL "SunOS")
   set(HAVE_PIPE2 1)
 endif()
 set(HAVE_POLL 1)
@@ -232,7 +234,6 @@
 set(HAVE_SIGINTERRUPT 1)
 set(HAVE_SIGNAL 1)
 set(HAVE_SIGSETJMP 1)
-set(HAVE_SNPRINTF 1)
 set(HAVE_SOCKADDR_IN6_SIN6_ADDR 1)
 set(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1)
 set(HAVE_SOCKET 1)
diff --git a/Utilities/cmcurl/CMake/win32-cache.cmake b/Utilities/cmcurl/CMake/win32-cache.cmake
index 77535a6..bd9bb6e 100644
--- a/Utilities/cmcurl/CMake/win32-cache.cmake
+++ b/Utilities/cmcurl/CMake/win32-cache.cmake
@@ -29,7 +29,6 @@
   set(HAVE_BASENAME 1)
   set(HAVE_BOOL_T 1)  # = HAVE_STDBOOL_H
   set(HAVE_DIRENT_H 1)
-  set(HAVE_FTRUNCATE 1)
   set(HAVE_GETTIMEOFDAY 1)
   set(HAVE_LIBGEN_H 1)
   set(HAVE_OPENDIR 1)
@@ -42,7 +41,6 @@
   set(HAVE_UTIME_H 1)  # wrapper to sys/utime.h
 else()
   set(HAVE_DIRENT_H 0)
-  set(HAVE_FTRUNCATE 0)
   set(HAVE_GETTIMEOFDAY 0)
   set(HAVE_LIBGEN_H 0)
   set(HAVE_OPENDIR 0)
diff --git a/Utilities/cmcurl/CMakeLists.txt b/Utilities/cmcurl/CMakeLists.txt
index 017c6ea..4566ff2 100644
--- a/Utilities/cmcurl/CMakeLists.txt
+++ b/Utilities/cmcurl/CMakeLists.txt
@@ -23,6 +23,9 @@
 set(CURL_USE_SCHANNEL OFF)
 set(CURL_USE_WOLFSSL OFF)
 set(CURL_BROTLI "OFF" CACHE INTERNAL "Build curl with BROTLI support (AUTO, ON or OFF)")
+set(CURL_DEBUG_GLOBAL_MEM OFF)
+set(CURL_ENABLE_NTLM OFF)
+set(CURL_ENABLE_SMB OFF)
 set(CURL_DISABLE_ALTSVC ON)
 set(CURL_DISABLE_AWS OFF)
 set(CURL_DISABLE_BASIC_AUTH OFF)
@@ -53,7 +56,6 @@
 set(CURL_DISABLE_MQTT ON)
 set(CURL_DISABLE_NEGOTIATE_AUTH OFF)
 set(CURL_DISABLE_NETRC OFF)
-set(CURL_DISABLE_NTLM OFF)
 set(CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG OFF)
 set(CURL_DISABLE_PARSEDATE OFF)
 set(CURL_DISABLE_POP3 ON CACHE INTERNAL "Disable curl pop3 protocol?")
@@ -62,7 +64,6 @@
 set(CURL_DISABLE_RTSP ON CACHE INTERNAL "Disable curl rtsp protocol?")
 set(CURL_DISABLE_SHA512_256 OFF)
 set(CURL_DISABLE_SHUFFLE_DNS OFF)
-set(CURL_DISABLE_SMB OFF)
 set(CURL_DISABLE_SMTP ON CACHE INTERNAL "Disable curl smtp protocol?")
 set(CURL_DISABLE_SOCKETPAIR OFF)
 set(CURL_DISABLE_SRP OFF)
@@ -75,6 +76,7 @@
 set(CURL_HIDDEN_SYMBOLS OFF CACHE INTERNAL "No curl hidden symbols")
 set(CURL_LTO OFF CACHE INTERNAL "Turn on compiler Link Time Optimizations")
 set(CURL_STATIC_CRT OFF CACHE INTERNAL "Set to ON to build libcurl with static CRT on Windows (/MT).")
+set(CURL_USE_CMAKECONFIG OFF)
 set(CURL_WERROR OFF CACHE INTERNAL "Turn compiler warnings into errors")
 set(CURL_ZLIB "AUTO" CACHE INTERNAL "Build curl with ZLIB support (AUTO, ON or OFF)")
 set(CURL_ZSTD "OFF" CACHE INTERNAL "Build curl with ZSTD support (AUTO, ON or OFF)")
@@ -179,7 +181,7 @@
 ###########################################################################
 
 if(0) # XXX(cmake): not needed for build within cmake
-cmake_minimum_required(VERSION 3.7...3.16 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
 message(STATUS "Using CMake version ${CMAKE_VERSION}")
 
 # Collect command-line arguments for buildinfo.txt.
@@ -203,7 +205,7 @@
 endif()
 endif() # XXX(cmake): end
 
-set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH})
+list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake")
 include(Utilities)
 include(Macros)
 include(CMakeDependentOption)
@@ -447,7 +449,7 @@
     set(PICKY_COMPILER OFF)  # Do a best effort and skip passing non-clang warning options to clang-tidy.
     # This lets through warning options enabled via CURL_WERROR=ON, affecting lib and src.
   endif()
-  set(CURL_DISABLE_TYPECHECK ON)  # to improve performance and avoid potential interference.
+  set(CURL_DISABLE_TYPECHECK ON)  # to improve performance (1.4x), avoid potential interference and bugprone-macro-parentheses.
   set(CMAKE_C_CLANG_TIDY "${CLANG_TIDY}")
   list(APPEND CMAKE_C_CLANG_TIDY "--config-file=${PROJECT_SOURCE_DIR}/.clang-tidy.yml")
   if(CURL_WERROR)
@@ -459,6 +461,18 @@
   endif()
 endif()
 
+option(CURL_GCC_ANALYZER "Enable GCC --analyzer option" OFF)
+if(CURL_GCC_ANALYZER AND CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0)
+  set(CURL_DISABLE_TYPECHECK ON)  # to improve performance (1.1x).
+  # https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html
+  set(CURL_ANALYZER_CFLAGS "-fanalyzer")
+  # disable checks causing false positives only
+  list(APPEND CURL_ANALYZER_CFLAGS "-Wno-analyzer-fd-leak" "-Wno-analyzer-fd-use-without-check" "-Wno-analyzer-file-leak")
+  list(APPEND CURL_ANALYZER_CFLAGS "-Wno-analyzer-infinite-loop")
+  list(APPEND CURL_ANALYZER_CFLAGS "-Wno-analyzer-malloc-leak")
+  list(APPEND CURL_ANALYZER_CFLAGS "-Wno-analyzer-out-of-bounds")
+endif()
+
 option(CURL_CODE_COVERAGE "Enable code coverage build options" OFF)
 if(CURL_CODE_COVERAGE)
   if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
@@ -486,18 +500,10 @@
 option(CURL_DROP_UNUSED "Drop unused code and data from built binaries" OFF)
 if(CURL_DROP_UNUSED)
   if(APPLE)
-    if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
-      set_property(DIRECTORY APPEND PROPERTY LINK_OPTIONS "-Wl,-dead_strip")
-    else()
-      set_property(DIRECTORY APPEND PROPERTY LINK_FLAGS "-Wl,-dead_strip")
-    endif()
+    set_property(DIRECTORY APPEND PROPERTY LINK_OPTIONS "-Wl,-dead_strip")
   elseif(MSVC)  # Options below are toolchain defaults in Release configurations.
     # This option does not seem to have an effect with VS2010:
-    if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
-      set_property(DIRECTORY APPEND PROPERTY LINK_OPTIONS "-OPT:REF")
-    else()
-      set_property(DIRECTORY APPEND PROPERTY LINK_FLAGS "-OPT:REF")
-    endif()
+    set_property(DIRECTORY APPEND PROPERTY LINK_OPTIONS "-OPT:REF")
     # Optional, but reduces binary size further, with the cost of larger objects/static libraries:
     list(APPEND CURL_CFLAGS "-Gy")
   elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")
@@ -505,11 +511,7 @@
       # To make -Wl,--gc-sections work on Windows: https://sourceware.org/bugzilla/show_bug.cgi?id=11539
       set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS "-fno-asynchronous-unwind-tables")
     endif()
-    if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
-      set_property(DIRECTORY APPEND PROPERTY LINK_OPTIONS "-Wl,--gc-sections")
-    else()
-      set_property(DIRECTORY APPEND PROPERTY LINK_FLAGS "-Wl,--gc-sections")
-    endif()
+    set_property(DIRECTORY APPEND PROPERTY LINK_OPTIONS "-Wl,--gc-sections")
     # Optional, but reduces binary size further, with the cost of larger objects/static libraries:
     list(APPEND CURL_CFLAGS "-ffunction-sections" "-fdata-sections")
   endif()
@@ -560,6 +562,14 @@
   endif()
 endif()
 
+# Override to force-disable or force-enable the use of CMake Configs.
+if(MSVC AND NOT VCPKG_TOOLCHAIN AND NOT CMAKE_CROSSCOMPILING)
+  set(_curl_use_cmakeconfig_default ON)
+else()
+  set(_curl_use_cmakeconfig_default OFF)
+endif()
+option(CURL_USE_CMAKECONFIG "Enable detecting dependencies via CMake Config" ${_curl_use_cmakeconfig_default})
+
 # Override to force-disable or force-enable the use of pkg-config.
 if((UNIX AND NOT ANDROID AND (NOT APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Darwin")) OR
    VCPKG_TOOLCHAIN OR
@@ -647,8 +657,8 @@
 mark_as_advanced(CURL_DISABLE_BINDLOCAL)
 option(CURL_DISABLE_NETRC "Disable netrc parser" OFF)
 mark_as_advanced(CURL_DISABLE_NETRC)
-option(CURL_DISABLE_NTLM "Disable NTLM support" OFF)
-mark_as_advanced(CURL_DISABLE_NTLM)
+option(CURL_ENABLE_NTLM "Enable NTLM support" OFF)
+mark_as_advanced(CURL_ENABLE_NTLM)
 option(CURL_DISABLE_PARSEDATE "Disable date parsing" OFF)
 mark_as_advanced(CURL_DISABLE_PARSEDATE)
 option(CURL_DISABLE_POP3 "Disable POP3" OFF)
@@ -665,8 +675,8 @@
 mark_as_advanced(CURL_DISABLE_SHA512_256)
 option(CURL_DISABLE_SHUFFLE_DNS "Disable shuffle DNS feature" OFF)
 mark_as_advanced(CURL_DISABLE_SHUFFLE_DNS)
-option(CURL_DISABLE_SMB "Disable SMB" OFF)
-mark_as_advanced(CURL_DISABLE_SMB)
+option(CURL_ENABLE_SMB "Enable SMB" OFF)
+mark_as_advanced(CURL_ENABLE_SMB)
 option(CURL_DISABLE_SMTP "Disable SMTP" OFF)
 mark_as_advanced(CURL_DISABLE_SMTP)
 option(CURL_DISABLE_SOCKETPAIR "Disable use of socketpair for curl_multi_poll()" OFF)
@@ -681,7 +691,13 @@
 mark_as_advanced(CURL_DISABLE_TYPECHECK)
 option(CURL_DISABLE_VERBOSE_STRINGS "Disable verbose strings" OFF)
 mark_as_advanced(CURL_DISABLE_VERBOSE_STRINGS)
+option(CURL_DEBUG_GLOBAL_MEM "Debug curl_global_init_mem" OFF)
+mark_as_advanced(CURL_DEBUG_GLOBAL_MEM)
 
+if(CURL_DEBUG_GLOBAL_MEM)
+  # Set it via the command-line to make it apply to the entire directory.
+  set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "CURL_DEBUG_GLOBAL_MEM")
+endif()
 if(CURL_DISABLE_TYPECHECK)
   # Set it via the command-line to make it apply to examples also.
   set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "CURL_DISABLE_TYPECHECK")
@@ -711,7 +727,6 @@
   set(CURL_DISABLE_MQTT ON)
   set(CURL_DISABLE_POP3 ON)
   set(CURL_DISABLE_RTSP ON)
-  set(CURL_DISABLE_SMB ON)
   set(CURL_DISABLE_SMTP ON)
   set(CURL_DISABLE_TELNET ON)
   set(CURL_DISABLE_TFTP ON)
@@ -741,9 +756,11 @@
   add_custom_target(curl-lint
     COMMENT "Running lint checks" VERBATIM USES_TERMINAL
     WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+    COMMAND "${PERL_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/badwords-all"
     COMMAND "${PERL_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/checksrc-all.pl"
     COMMAND "${PERL_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/spacecheck.pl"
     DEPENDS
+      "${PROJECT_SOURCE_DIR}/scripts/badwords-all" "${PROJECT_SOURCE_DIR}/scripts/badwords"
       "${PROJECT_SOURCE_DIR}/scripts/checksrc-all.pl" "${PROJECT_SOURCE_DIR}/scripts/checksrc.pl"
       "${PROJECT_SOURCE_DIR}/scripts/spacecheck.pl"
   )
@@ -762,7 +779,7 @@
 endif() # XXX(cmake): end
 
 # If we are on AIX, do the _ALL_SOURCE magic
-if(CMAKE_SYSTEM_NAME STREQUAL "AIX")
+if(AIX OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
   set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS "_ALL_SOURCE")
 endif()
 
@@ -813,17 +830,23 @@
   set(HAVE_TIME_T_UNSIGNED 1)
 endif()
 
-if(ENABLE_THREADED_RESOLVER)
-  if(WIN32)
-    set(USE_THREADS_WIN32 ON)
-  else()
-    find_package(Threads REQUIRED)
-    set(USE_THREADS_POSIX ${CMAKE_USE_PTHREADS_INIT})
-    set(HAVE_PTHREAD_H ${CMAKE_USE_PTHREADS_INIT})
-    list(APPEND CURL_NETWORK_AND_TIME_LIBS ${CMAKE_THREAD_LIBS_INIT})
+if(NOT WIN32)
+  find_package(Threads)
+  if(CMAKE_USE_PTHREADS_INIT)
+    set(HAVE_THREADS_POSIX 1)
+    list(APPEND CURL_NETWORK_AND_TIME_LIBS Threads::Threads)
   endif()
 endif()
 
+if(ENABLE_THREADED_RESOLVER)
+  if(NOT WIN32 AND NOT HAVE_THREADS_POSIX)
+    message(FATAL_ERROR "Threaded resolver requires POSIX Threads.")
+  endif()
+  set(USE_RESOLV_THREADED ON)
+elseif(USE_ARES)
+  set(USE_RESOLV_ARES ON)
+endif()
+
 # Check for all needed libraries
 if(DOS)
   if(WATT_ROOT)
@@ -849,7 +872,7 @@
 elseif(NOT WIN32 AND NOT APPLE)
   check_library_exists("socket" "connect" "" HAVE_LIBSOCKET)
   if(HAVE_LIBSOCKET)
-    set(CURL_NETWORK_AND_TIME_LIBS "socket" ${CURL_NETWORK_AND_TIME_LIBS})
+    list(PREPEND CURL_NETWORK_AND_TIME_LIBS "socket")
   endif()
 endif()
 
@@ -1027,6 +1050,16 @@
   endif()
 
   if(HAVE_BORINGSSL OR HAVE_AWSLC)
+    if(NOT MSVC AND NOT ANDROID)  # BoringSSL/AWS-LC MSVC builds use native Windows threads
+      find_package(Threads)
+      if(CMAKE_USE_PTHREADS_INIT)
+        set(HAVE_THREADS_POSIX_BORINGSSL 1)
+        list(APPEND CURL_NETWORK_AND_TIME_LIBS Threads::Threads)
+        list(APPEND CMAKE_REQUIRED_LIBRARIES Threads::Threads)
+      elseif(OPENSSL_USE_STATIC_LIBS)
+        message(WARNING "BoringSSL/AWS-LC requires POSIX Threads.")
+      endif()
+    endif()
     if(OPENSSL_USE_STATIC_LIBS AND CMAKE_C_COMPILER_ID MATCHES "Clang")
       list(APPEND CURL_LIBS "stdc++")
       list(APPEND CMAKE_REQUIRED_LIBRARIES "stdc++")
@@ -1193,7 +1226,7 @@
       endif()
     endif()
     if(WIN32)
-      list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt")  # for OpenSSL/LibreSSL
+      list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt")  # for OpenSSL/LibreSSL BCryptGenRandom()
     endif()
   endif()
   if(USE_WOLFSSL)
@@ -1241,9 +1274,9 @@
 if(USE_WOLFSSL)
   curl_openssl_check_exists("wolfSSL_get_peer_certificate" HAVE_WOLFSSL_GET_PEER_CERTIFICATE)
   curl_openssl_check_exists("wolfSSL_UseALPN" HAVE_WOLFSSL_USEALPN)
-  curl_openssl_check_exists("wolfSSL_DES_ecb_encrypt" HAVE_WOLFSSL_DES_ECB_ENCRYPT)
   curl_openssl_check_exists("wolfSSL_BIO_new" HAVE_WOLFSSL_BIO_NEW)
   curl_openssl_check_exists("wolfSSL_BIO_set_shutdown" HAVE_WOLFSSL_BIO_SET_SHUTDOWN)
+  curl_openssl_check_exists("wc_Des_EcbEncrypt" HAVE_WC_DES_ECBENCRYPT)
 endif()
 
 if(USE_OPENSSL)
@@ -1389,7 +1422,7 @@
     find_package(LDAP MODULE)
     if(LDAP_FOUND)
       set(HAVE_LBER_H 1)
-      set(CURL_LIBS CURL::ldap ${CURL_LIBS})
+      list(PREPEND CURL_LIBS CURL::ldap)
 
       # LDAP feature checks
 
@@ -1426,7 +1459,7 @@
 if(WIN32)
   option(USE_WIN32_IDN "Use WinIDN for IDN support" OFF)
   if(USE_WIN32_IDN)
-    list(APPEND CURL_LIBS "normaliz")
+    list(APPEND CURL_LIBS "normaliz")  # for IdnToAscii(), IdnToUnicode()
   endif()
 else()
   set(USE_WIN32_IDN OFF)
@@ -1456,7 +1489,7 @@
 if(USE_LIBIDN2 AND NOT USE_APPLE_IDN AND NOT USE_WIN32_IDN)
   find_package(Libidn2 MODULE QUIET)
   if(LIBIDN2_FOUND)
-    set(CURL_LIBS CURL::libidn2 ${CURL_LIBS})
+    list(PREPEND CURL_LIBS CURL::libidn2)
     set(HAVE_IDN2_H 1)
     set(HAVE_LIBIDN2 1)
   endif()
@@ -1479,7 +1512,7 @@
 if(CURL_USE_LIBSSH2)
   find_package(Libssh2 MODULE)
   if(LIBSSH2_FOUND)
-    set(CURL_LIBS CURL::libssh2 ${CURL_LIBS})  # keep it before TLS-crypto, compression
+    list(PREPEND CURL_LIBS CURL::libssh2)  # keep it before TLS-crypto, compression
     set(USE_LIBSSH2 ON)
   endif()
 endif()
@@ -1489,7 +1522,7 @@
 mark_as_advanced(CURL_USE_LIBSSH)
 if(NOT USE_LIBSSH2 AND CURL_USE_LIBSSH)
   find_package(Libssh MODULE REQUIRED)
-  set(CURL_LIBS CURL::libssh ${CURL_LIBS})  # keep it before TLS-crypto, compression
+  list(PREPEND CURL_LIBS CURL::libssh)  # keep it before TLS-crypto, compression
   set(USE_LIBSSH ON)
 endif()
 
@@ -1548,12 +1581,6 @@
   set(HAVE_UV_H ON)
 endif()
 
-option(USE_LIBRTMP "Enable librtmp from rtmpdump" OFF)
-if(USE_LIBRTMP)
-  find_package(Librtmp MODULE REQUIRED)
-  list(APPEND CURL_LIBS CURL::librtmp)
-endif()
-
 option(ENABLE_UNIX_SOCKETS "Enable Unix domain sockets support" ON)
 if(ENABLE_UNIX_SOCKETS)
   if(WIN32 OR DOS)
@@ -1753,7 +1780,7 @@
     HAVE_UNISTD_H
 )
   if(${_variable})
-    string(APPEND CURL_TEST_DEFINES " -D${_variable}")
+    list(APPEND CURL_TEST_DEFINES "-D${_variable}")
   endif()
 endforeach()
 
@@ -1822,20 +1849,24 @@
 check_symbol_exists("getifaddrs"      "${CURL_INCLUDES};stdlib.h" HAVE_GETIFADDRS)  # ifaddrs.h
 check_symbol_exists("freeaddrinfo"    "${CURL_INCLUDES}" HAVE_FREEADDRINFO)  # ws2tcpip.h sys/socket.h netdb.h
 check_function_exists("pipe"          HAVE_PIPE)
-check_function_exists("pipe2"         HAVE_PIPE2)
 check_function_exists("eventfd"       HAVE_EVENTFD)
-check_symbol_exists("ftruncate"       "unistd.h" HAVE_FTRUNCATE)
 check_symbol_exists("getpeername"     "${CURL_INCLUDES}" HAVE_GETPEERNAME)  # winsock2.h unistd.h proto/bsdsocket.h
 check_symbol_exists("getsockname"     "${CURL_INCLUDES}" HAVE_GETSOCKNAME)  # winsock2.h unistd.h proto/bsdsocket.h
 check_function_exists("getrlimit"     HAVE_GETRLIMIT)
 check_function_exists("setlocale"     HAVE_SETLOCALE)
 check_function_exists("setrlimit"     HAVE_SETRLIMIT)
 
+if(NOT APPLE)
+  # Apple platforms do not offer pipe2(), but the iPhone Simulator-specific
+  # /usr/lib/system/libsystem_sim_kernel.dylib exports it. To avoid false
+  # detection, omit this feature check for Apple targets.
+  check_function_exists("pipe2"         HAVE_PIPE2)
+endif()
+
 if(NOT WIN32)
   check_function_exists("if_nametoindex"  HAVE_IF_NAMETOINDEX)  # net/if.h
   check_function_exists("realpath"        HAVE_REALPATH)
   check_function_exists("sched_yield"     HAVE_SCHED_YIELD)
-  check_function_exists("snprintf"        HAVE_SNPRINTF)  # to match detection method in ./configure
   check_symbol_exists("strcasecmp"        "string.h" HAVE_STRCASECMP)
   check_symbol_exists("stricmp"           "string.h" HAVE_STRICMP)
   check_symbol_exists("strcmpi"           "string.h" HAVE_STRCMPI)
@@ -1999,9 +2030,9 @@
 if(WIN32)
   list(APPEND CURL_NETWORK_AND_TIME_LIBS "ws2_32")
   if(NOT WINDOWS_STORE)
-    list(APPEND CURL_NETWORK_AND_TIME_LIBS "iphlpapi")
+    list(APPEND CURL_NETWORK_AND_TIME_LIBS "iphlpapi")  # for if_nametoindex()
   endif()
-  list(APPEND CURL_LIBS "bcrypt")
+  list(APPEND CURL_LIBS "bcrypt")  # for BCryptGenRandom()
 
   # We use crypto functions that are not available for UWP apps
   if(NOT WINDOWS_STORE)
@@ -2010,10 +2041,16 @@
 
   # Link required libraries for USE_WIN32_CRYPTO or USE_SCHANNEL
   if(USE_WIN32_CRYPTO OR USE_SCHANNEL)
-    list(APPEND CURL_LIBS "advapi32" "crypt32")
+    # for CryptAcquireContext(), CryptCreateHash(), CryptDestroyHash(), CryptGetHashParam(), CryptHashData(),
+    #     CryptReleaseContext() in NTLM, md4, md5, sha256, Schannel
+    # for CryptDestroyKey(), CryptEncrypt(), CryptImportKey() in NTLM
+    list(APPEND CURL_LIBS "advapi32")
+    # for Cert*() in openssl.c Native CA, Schannel
+    # for CryptDecodeObjectEx(), CryptQueryObject(), CryptStringToBinary(), PFXImportCertStore() in Schannel
+    list(APPEND CURL_LIBS "crypt32")
   endif()
   if(USE_WINDOWS_SSPI)
-    list(APPEND CURL_LIBS "secur32")
+    list(APPEND CURL_LIBS "secur32")  # for InitSecurityInterface()
   endif()
 endif()
 
@@ -2033,22 +2070,14 @@
 # to consume libcurl, regardless of the compiler used to build libcurl itself.
 if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
   if(USE_OPENSSL AND TARGET OpenSSL::Crypto)
-    get_target_property(_curl_imported OpenSSL::Crypto IMPORTED)
-    if(_curl_imported)
-      add_library(CURL::OpenSSL_Crypto INTERFACE IMPORTED)
-      get_target_property(_curl_libname OpenSSL::Crypto LOCATION)
-      set_target_properties(CURL::OpenSSL_Crypto PROPERTIES INTERFACE_LINK_LIBRARIES "${_curl_libname}")
-      list(APPEND CURL_LIBS CURL::OpenSSL_Crypto)
-    endif()
+    add_library(CURL::OpenSSL_Crypto INTERFACE IMPORTED)
+    set_target_properties(CURL::OpenSSL_Crypto PROPERTIES INTERFACE_LINK_LIBRARIES OpenSSL::Crypto)
+    list(APPEND CURL_LIBS CURL::OpenSSL_Crypto)
   endif()
   if(HAVE_LIBZ AND TARGET ZLIB::ZLIB)
-    get_target_property(_curl_imported ZLIB::ZLIB IMPORTED)
-    if(_curl_imported)
-      add_library(CURL::ZLIB INTERFACE IMPORTED)
-      get_target_property(_curl_libname ZLIB::ZLIB LOCATION)
-      set_target_properties(CURL::ZLIB PROPERTIES INTERFACE_LINK_LIBRARIES "${_curl_libname}")
-      list(APPEND CURL_LIBS CURL::ZLIB)
-    endif()
+    add_library(CURL::ZLIB INTERFACE IMPORTED)
+    set_target_properties(CURL::ZLIB PROPERTIES INTERFACE_LINK_LIBRARIES ZLIB::ZLIB)
+    list(APPEND CURL_LIBS CURL::ZLIB)
   endif()
   if(WIN32)
     add_library(CURL::win32_winsock INTERFACE IMPORTED)
@@ -2063,12 +2092,6 @@
 endif()
 
 if(CURL_LTO)
-  if(CMAKE_VERSION VERSION_LESS 3.9)
-    message(FATAL_ERROR "LTO has been requested, but your cmake version ${CMAKE_VERSION} is to old. You need at least 3.9")
-  endif()
-
-  cmake_policy(SET CMP0069 NEW)
-
   include(CheckIPOSupported)
   check_ipo_supported(RESULT CURL_HAS_LTO OUTPUT _lto_error LANGUAGES C)
   if(CURL_HAS_LTO)
@@ -2164,12 +2187,12 @@
 
 # NTLM support requires crypto functions from various SSL libs.
 # These conditions must match those in lib/curl_setup.h.
-if(NOT CURL_DISABLE_NTLM AND
+if(CURL_ENABLE_NTLM AND
    ((USE_OPENSSL AND HAVE_DES_ECB_ENCRYPT) OR
     (USE_MBEDTLS AND HAVE_MBEDTLS_DES_CRYPT_ECB) OR
     USE_GNUTLS OR
     USE_WIN32_CRYPTO OR
-    (USE_WOLFSSL AND HAVE_WOLFSSL_DES_ECB_ENCRYPT)))
+    (USE_WOLFSSL AND HAVE_WC_DES_ECBENCRYPT)))
   set(_use_curl_ntlm_core ON)
 endif()
 
@@ -2194,9 +2217,9 @@
 curl_add_if("POP3S"         NOT CURL_DISABLE_POP3 AND _ssl_enabled)
 curl_add_if("IMAP"          NOT CURL_DISABLE_IMAP)
 curl_add_if("IMAPS"         NOT CURL_DISABLE_IMAP AND _ssl_enabled)
-curl_add_if("SMB"           NOT CURL_DISABLE_SMB AND
+curl_add_if("SMB"           CURL_ENABLE_SMB AND
                             _use_curl_ntlm_core AND (SIZEOF_CURL_OFF_T GREATER 4))
-curl_add_if("SMBS"          NOT CURL_DISABLE_SMB AND _ssl_enabled AND
+curl_add_if("SMBS"          CURL_ENABLE_SMB AND _ssl_enabled AND
                             _use_curl_ntlm_core AND (SIZEOF_CURL_OFF_T GREATER 4))
 curl_add_if("SMTP"          NOT CURL_DISABLE_SMTP)
 curl_add_if("SMTPS"         NOT CURL_DISABLE_SMTP AND _ssl_enabled)
@@ -2205,7 +2228,6 @@
 curl_add_if("IPFS"          NOT CURL_DISABLE_IPFS)
 curl_add_if("IPNS"          NOT CURL_DISABLE_IPFS)
 curl_add_if("RTSP"          NOT CURL_DISABLE_RTSP)
-curl_add_if("RTMP"          USE_LIBRTMP)
 curl_add_if("MQTT"          NOT CURL_DISABLE_MQTT)
 curl_add_if("MQTTS"         NOT CURL_DISABLE_MQTT AND _ssl_enabled)
 curl_add_if("WS"            NOT CURL_DISABLE_WEBSOCKETS)
@@ -2227,8 +2249,8 @@
 curl_add_if("brotli"        HAVE_BROTLI)
 curl_add_if("gsasl"         USE_GSASL)
 curl_add_if("zstd"          HAVE_ZSTD)
-curl_add_if("AsynchDNS"     USE_ARES OR USE_THREADS_POSIX OR USE_THREADS_WIN32)
-curl_add_if("asyn-rr"       USE_ARES AND ENABLE_THREADED_RESOLVER AND USE_HTTPSRR)
+curl_add_if("AsynchDNS"     USE_RESOLV_ARES OR USE_RESOLV_THREADED)
+curl_add_if("asyn-rr"       USE_ARES AND USE_RESOLV_THREADED AND USE_HTTPSRR)
 curl_add_if("IDN"           (HAVE_LIBIDN2 AND HAVE_IDN2_H) OR
                             USE_WIN32_IDN OR
                             USE_APPLE_IDN)
@@ -2241,7 +2263,7 @@
                             (HAVE_GSSAPI OR USE_WINDOWS_SSPI))
 curl_add_if("Kerberos"      NOT CURL_DISABLE_KERBEROS_AUTH AND
                             (HAVE_GSSAPI OR USE_WINDOWS_SSPI))
-curl_add_if("NTLM"          NOT CURL_DISABLE_NTLM AND
+curl_add_if("NTLM"          CURL_ENABLE_NTLM AND
                             (_use_curl_ntlm_core OR USE_WINDOWS_SSPI))
 curl_add_if("TLS-SRP"       USE_TLS_SRP)
 curl_add_if("HTTP2"         USE_NGHTTP2)
@@ -2251,7 +2273,7 @@
                             OR USE_SCHANNEL OR USE_RUSTLS OR USE_MBEDTLS OR
                             (USE_WOLFSSL AND HAVE_WOLFSSL_BIO_NEW)))
 curl_add_if("Unicode"       ENABLE_UNICODE)
-curl_add_if("threadsafe"    HAVE_ATOMIC OR (USE_THREADS_POSIX AND HAVE_PTHREAD_H) OR WIN32)
+curl_add_if("threadsafe"    HAVE_ATOMIC OR HAVE_THREADS_POSIX OR WIN32)
 curl_add_if("Debug"         ENABLE_DEBUG)
 curl_add_if("ECH"           _ssl_enabled AND HAVE_ECH)
 curl_add_if("HTTPSRR"       _ssl_enabled AND USE_HTTPSRR)
@@ -2261,11 +2283,7 @@
 curl_add_if("AppleSecTrust" USE_APPLE_SECTRUST AND _ssl_enabled AND (USE_OPENSSL OR USE_GNUTLS))
 curl_add_if("NativeCA"      NOT USE_APPLE_SECTRUST AND _ssl_enabled AND CURL_CA_NATIVE)
 if(_items)
-  if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
-    list(SORT _items CASE INSENSITIVE)
-  else()
-    list(SORT _items)
-  endif()
+  list(SORT _items CASE INSENSITIVE)
 endif()
 set(CURL_SUPPORTED_FEATURES_LIST "${_items}")
 string(REPLACE ";" " " SUPPORT_FEATURES "${_items}")
@@ -2281,11 +2299,7 @@
 curl_add_if("Rustls"           _ssl_enabled AND USE_RUSTLS)
 
 if(_items)
-  if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
-    list(SORT _items CASE INSENSITIVE)
-  else()
-    list(SORT _items)
-  endif()
+  list(SORT _items CASE INSENSITIVE)
 endif()
 string(REPLACE ";" " " SSL_BACKENDS "${_items}")
 message(STATUS "Enabled SSL backends: ${SSL_BACKENDS}")
@@ -2375,29 +2389,15 @@
         # Assume the user will not need this information in the .pc file.
         continue()
       endif()
-      if(_lib MATCHES "CURL::")
-        # This is empty for 'CURL::*' targets and safe to ignore.
-        # Explicitly skip this query to avoid CMake v3.18 and older erroring out.
-        set(_libname "")
-      else()
-        get_target_property(_libname "${_lib}" LOCATION)
-      endif()
-      if(_libname)
-        list(APPEND _explicit_libs "${_libname}")
-      else()
-        get_target_property(_libs "${_lib}" INTERFACE_LINK_LIBRARIES)
-        if(_libs)
-          list(APPEND _explicit_libs "${_libs}")
-        endif()
-        get_target_property(_libdirs "${_lib}" INTERFACE_LINK_DIRECTORIES)
-        if(_libdirs)
-          list(APPEND _explicit_libdirs "${_libdirs}")
-        endif()
-      endif()
-      if(NOT _libname AND NOT _libs AND NOT _libdirs)
+      set(_libdirs "")
+      set(_libs "")
+      curl_collect_target_link_options("${_lib}")  # look into the target recursively
+      list(APPEND _explicit_libdirs ${_libdirs})
+      list(APPEND _explicit_libs ${_libs})
+      if(NOT _libs AND NOT _libdirs AND NOT _lib STREQUAL Threads::Threads)
         message(WARNING "Bad lib in library list: ${_lib}")
       endif()
-      if(_lib STREQUAL OpenSSL::SSL)
+      if(_lib STREQUAL OpenSSL::SSL AND NOT HAVE_BORINGSSL)  # BoringSSL does not provide openssl.pc
         set(_modules "openssl")
       elseif(_lib STREQUAL ZLIB::ZLIB)
         set(_modules "zlib")
@@ -2578,18 +2578,20 @@
   #   TARGETS_EXPORT_NAME
   #   CURL_SUPPORTED_FEATURES_LIST
   #   CURL_SUPPORTED_PROTOCOLS_LIST
+  #   CURL_USE_CMAKECONFIG
   #   CURL_USE_PKGCONFIG
   #   HAVE_BROTLI
   #   HAVE_GSSAPI
   #   HAVE_LIBIDN2
   #   HAVE_LIBZ ZLIB_VERSION_MAJOR
+  #   HAVE_THREADS_POSIX
+  #   HAVE_THREADS_POSIX_BORINGSSL
   #   HAVE_ZSTD
   #   USE_ARES
   #   USE_BACKTRACE
   #   USE_GNUTLS
   #   USE_GSASL
   #   USE_LIBPSL
-  #   USE_LIBRTMP
   #   USE_LIBSSH
   #   USE_LIBSSH2
   #   USE_LIBUV
@@ -2626,7 +2628,6 @@
       "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibgsasl.cmake"
       "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibidn2.cmake"
       "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibpsl.cmake"
-      "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibrtmp.cmake"
       "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibssh.cmake"
       "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibssh2.cmake"
       "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FindLibuv.cmake"
@@ -2651,12 +2652,14 @@
       COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake")
   endif()
 
-  install(FILES "${PROJECT_SOURCE_DIR}/scripts/wcurl"
-    DESTINATION ${CMAKE_INSTALL_BINDIR}
-    PERMISSIONS
-      OWNER_READ OWNER_WRITE OWNER_EXECUTE
-      GROUP_READ GROUP_EXECUTE
-      WORLD_READ WORLD_EXECUTE)
+  if(BUILD_CURL_EXE)
+    install(FILES "${PROJECT_SOURCE_DIR}/scripts/wcurl"
+      DESTINATION ${CMAKE_INSTALL_BINDIR}
+      PERMISSIONS
+        OWNER_READ OWNER_WRITE OWNER_EXECUTE
+        GROUP_READ GROUP_EXECUTE
+        WORLD_READ WORLD_EXECUTE)
+  endif()
 
   # The `-DEV` part is important
   string(REGEX REPLACE "([0-9]+\.[0-9]+)\.([0-9]+.*)" "\\2" CPACK_PACKAGE_VERSION_PATCH "${_curl_version}")
diff --git a/Utilities/cmcurl/include/curl/curl.h b/Utilities/cmcurl/include/curl/curl.h
index 10351cb..3b0af7a 100644
--- a/Utilities/cmcurl/include/curl/curl.h
+++ b/Utilities/cmcurl/include/curl/curl.h
@@ -220,8 +220,8 @@
 #define CURL_HTTPPOST_LARGE (1 << 7)
 
   char *showfilename;               /* The filename to show. If not set, the
-                                       actual filename will be used (if this
-                                       is a file part) */
+                                       actual filename is used (if this is
+                                       a file part) */
   void *userp;                      /* custom pointer used for
                                        HTTPPOST_CALLBACK posts */
   curl_off_t contentlen;            /* alternative length of contents
@@ -229,8 +229,8 @@
                                        set. Added in 7.46.0 */
 };
 
-/* This is a return code for the progress callback that, when returned, will
-   signal libcurl to continue executing the default progress function */
+/* This is a return code for the progress callback that, when returned,
+   signals libcurl to continue executing the default progress function */
 #define CURL_PROGRESSFUNC_CONTINUE 0x10000001
 
 /* This is the CURLOPT_PROGRESSFUNCTION callback prototype. It is now
@@ -267,17 +267,17 @@
 
 #ifndef CURL_MAX_HTTP_HEADER
 /* The only reason to have a max limit for this is to avoid the risk of a bad
-   server feeding libcurl with a never-ending header that will cause reallocs
+   server feeding libcurl with a never-ending header that causes reallocs
    infinitely */
 #define CURL_MAX_HTTP_HEADER (100*1024)
 #endif
 
 /* This is a magic return code for the write callback that, when returned,
-   will signal libcurl to pause receiving on the current transfer. */
+   signals libcurl to pause receiving on the current transfer. */
 #define CURL_WRITEFUNC_PAUSE 0x10000001
 
 /* This is a magic return code for the write callback that, when returned,
-   will signal an error from the callback. */
+   signals an error from the callback. */
 #define CURL_WRITEFUNC_ERROR 0xFFFFFFFF
 
 typedef size_t (*curl_write_callback)(char *buffer,
@@ -285,7 +285,7 @@
                                       size_t nitems,
                                       void *outstream);
 
-/* This callback will be called when a new resolver request is made */
+/* This callback is called when a new resolver request is made */
 typedef int (*curl_resolver_start_callback)(void *resolver_state,
                                             void *reserved, void *userdata);
 
@@ -385,11 +385,11 @@
                                   curl_off_t offset,
                                   int origin); /* 'whence' */
 
-/* This is a return code for the read callback that, when returned, will
-   signal libcurl to immediately abort the current transfer. */
+/* This is a return code for the read callback that, when returned,
+   signals libcurl to immediately abort the current transfer. */
 #define CURL_READFUNC_ABORT 0x10000000
-/* This is a return code for the read callback that, when returned, will
-   signal libcurl to pause sending data on the current transfer. */
+/* This is a return code for the read callback that, when returned,
+   signals libcurl to pause sending data on the current transfer. */
 #define CURL_READFUNC_PAUSE 0x10000001
 
 /* Return code for when the trailing headers' callback has terminated
@@ -609,7 +609,7 @@
                                     or wrong format */
   CURLE_REMOTE_FILE_NOT_FOUND,   /* 78 - remote file not found */
   CURLE_SSH,                     /* 79 - error from the SSH layer, somewhat
-                                    generic so the error message will be of
+                                    generic so the error message is of
                                     interest when this has happened */
 
   CURLE_SSL_SHUTDOWN_FAILED,     /* 80 - Failed to shut down the SSL
@@ -627,7 +627,7 @@
   CURLE_FTP_BAD_FILE_LIST,       /* 87 - unable to parse FTP file list */
   CURLE_CHUNK_FAILED,            /* 88 - chunk callback reported error */
   CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the
-                                    session will be queued */
+                                    session is queued */
   CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* 90 - specified pinned public key did not
                                      match */
   CURLE_SSL_INVALIDCERTSTATUS,   /* 91 - invalid certificate status */
@@ -888,7 +888,7 @@
   CURLKHSTAT_REJECT, /* reject the connection, return an error */
   CURLKHSTAT_DEFER,  /* do not accept it, but we cannot answer right now.
                         Causes a CURLE_PEER_FAILED_VERIFICATION error but the
-                        connection will be left intact etc */
+                        connection is left intact etc */
   CURLKHSTAT_FINE_REPLACE, /* accept and replace the wrong key */
   CURLKHSTAT_LAST    /* not for use, only a marker for last-in-list */
 };
@@ -1170,12 +1170,12 @@
    * bytes big. */
   CURLOPT(CURLOPT_ERRORBUFFER, CURLOPTTYPE_OBJECTPOINT, 10),
 
-  /* Function that will be called to store the output (instead of fwrite). The
-   * parameters will use fwrite() syntax, make sure to follow them. */
+  /* Function that is called to store the output (instead of fwrite). The
+   * parameters use fwrite() syntax, make sure to follow them. */
   CURLOPT(CURLOPT_WRITEFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 11),
 
-  /* Function that will be called to read the input (instead of fread). The
-   * parameters will use fread() syntax, make sure to follow them. */
+  /* Function that is called to read the input (instead of fread). The
+   * parameters use fread() syntax, make sure to follow them. */
   CURLOPT(CURLOPT_READFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 12),
 
   /* Time-out the read operation after this amount of seconds */
@@ -1329,7 +1329,7 @@
   /* 55 = OBSOLETE */
 
   /* DEPRECATED
-   * Function that will be called instead of the internal progress display
+   * Function that is called instead of the internal progress display
    * function. This function should be defined as the curl_progress_callback
    * prototype defines. */
   CURLOPTDEPRECATED(CURLOPT_PROGRESSFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 56,
@@ -1358,7 +1358,7 @@
 
   /* Set the krb4/5 security level, this also enables krb4/5 awareness. This
    * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string
-   * is set but does not match one of these, 'private' will be used. */
+   * is set but does not match one of these, 'private' is used. */
   CURLOPTDEPRECATED(CURLOPT_KRBLEVEL, CURLOPTTYPE_STRINGPOINT, 63,
                     8.17.0, "removed"),
 
@@ -1411,8 +1411,8 @@
      OK within this time, then fine... This only aborts the connect phase. */
   CURLOPT(CURLOPT_CONNECTTIMEOUT, CURLOPTTYPE_LONG, 78),
 
-  /* Function that will be called to store headers (instead of fwrite). The
-   * parameters will use fwrite() syntax, make sure to follow them. */
+  /* Function that is called to store headers (instead of fwrite). The
+   * parameters use fwrite() syntax, make sure to follow them. */
   CURLOPT(CURLOPT_HEADERFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 79),
 
   /* Set this to force the HTTP request to get back to GET. Only really usable
@@ -1437,7 +1437,7 @@
   CURLOPT(CURLOPT_HTTP_VERSION, CURLOPTTYPE_VALUES, 84),
 
   /* Specifically switch on or off the FTP engine's use of the EPSV command. By
-     default, that one will always be attempted before the more traditional
+     default, that one is always attempted before the more traditional
      PASV command. */
   CURLOPT(CURLOPT_FTP_USE_EPSV, CURLOPTTYPE_LONG, 85),
 
@@ -1515,7 +1515,7 @@
   CURLOPT(CURLOPT_UNRESTRICTED_AUTH, CURLOPTTYPE_LONG, 105),
 
   /* Specifically switch on or off the FTP engine's use of the EPRT command (
-     it also disables the LPRT attempt). By default, those ones will always be
+     it also disables the LPRT attempt). By default, those ones are always
      attempted before the good old traditional PORT command. */
   CURLOPT(CURLOPT_FTP_USE_EPRT, CURLOPTTYPE_LONG, 106),
 
@@ -1555,7 +1555,7 @@
      systems with support for more than one, i.e IPv4 _and_ IPv6. */
   CURLOPT(CURLOPT_IPRESOLVE, CURLOPTTYPE_VALUES, 113),
 
-  /* Set this option to limit the size of a file that will be downloaded from
+  /* Set this option to limit the size of a file that is to be downloaded from
      an HTTP or FTP server.
 
      Note there is also _LARGE version which adds large file support for
@@ -1572,13 +1572,13 @@
    */
   CURLOPT(CURLOPT_RESUME_FROM_LARGE, CURLOPTTYPE_OFF_T, 116),
 
-  /* Sets the maximum size of data that will be downloaded from
+  /* Sets the maximum size of data that is to be downloaded from
    * an HTTP or FTP server. See MAXFILESIZE above for the LONG version.
    */
   CURLOPT(CURLOPT_MAXFILESIZE_LARGE, CURLOPTTYPE_OFF_T, 117),
 
   /* Set this option to the filename of your .netrc file you want libcurl
-     to parse (using the CURLOPT_NETRC option). If not set, libcurl will do
+     to parse (using the CURLOPT_NETRC option). If not set, libcurl does
      a poor attempt to find the user's home directory and check for a .netrc
      file in there. */
   CURLOPT(CURLOPT_NETRC_FILE, CURLOPTTYPE_STRINGPOINT, 118),
@@ -1636,7 +1636,7 @@
 
   /* Set to non-zero to skip the IP address received in a 227 PASV FTP server
      response. Typically used for FTP-SSL purposes but is not restricted to
-     that. libcurl will then instead use the same IP address it used for the
+     that. libcurl is then instead using the same IP address it used for the
      control connection. */
   CURLOPT(CURLOPT_FTP_SKIP_PASV_IP, CURLOPTTYPE_LONG, 137),
 
@@ -1648,7 +1648,7 @@
   CURLOPT(CURLOPT_LOCALPORT, CURLOPTTYPE_LONG, 139),
 
   /* Number of ports to try, including the first one set with LOCALPORT.
-     Thus, setting it to 1 will make no additional attempts but the first.
+     Thus, setting it to 1 makes no additional attempts but the first.
   */
   CURLOPT(CURLOPT_LOCALPORTRANGE, CURLOPTTYPE_LONG, 140),
 
@@ -1656,19 +1656,19 @@
      extracting it with CURLINFO_LASTSOCKET */
   CURLOPT(CURLOPT_CONNECT_ONLY, CURLOPTTYPE_LONG, 141),
 
-  /* Function that will be called to convert from the
+  /* Function that is called to convert from the
      network encoding (instead of using the iconv calls in libcurl) */
   CURLOPTDEPRECATED(CURLOPT_CONV_FROM_NETWORK_FUNCTION,
                     CURLOPTTYPE_FUNCTIONPOINT, 142,
                     7.82.0, "Serves no purpose anymore"),
 
-  /* Function that will be called to convert to the
+  /* Function that is called to convert to the
      network encoding (instead of using the iconv calls in libcurl) */
   CURLOPTDEPRECATED(CURLOPT_CONV_TO_NETWORK_FUNCTION,
                     CURLOPTTYPE_FUNCTIONPOINT, 143,
                     7.82.0, "Serves no purpose anymore"),
 
-  /* Function that will be called to convert from UTF8
+  /* Function that is called to convert from UTF8
      (instead of using the iconv calls in libcurl)
      Note that this is used only for SSL certificate processing */
   CURLOPTDEPRECATED(CURLOPT_CONV_FROM_UTF8_FUNCTION,
@@ -1762,9 +1762,9 @@
 
   /* Comma separated list of hostnames defining no-proxy zones. These should
      match both hostnames directly, and hostnames within a domain. For
-     example, local.com will match local.com and www.local.com, but NOT
+     example, local.com matches local.com and www.local.com, but NOT
      notlocal.com or www.notlocal.com. For compatibility with other
-     implementations of this, .local.com will be considered to be the same as
+     implementations of this, .local.com is considered to be the same as
      local.com. A single * is the only valid wildcard, and effectively
      disables the use of proxy. */
   CURLOPT(CURLOPT_NOPROXY, CURLOPTTYPE_STRINGPOINT, 177),
@@ -1874,8 +1874,8 @@
      in outgoing requests. The current default is 0, but it might change in a
      future libcurl release.
 
-     libcurl will ask for the compressed methods it knows of, and if that
-     is not any, it will not ask for transfer-encoding at all even if this
+     libcurl asks for the compressed methods it knows of, and if that
+     is not any, it does not ask for transfer-encoding at all even if this
      option is set to 1. */
   CURLOPT(CURLOPT_TRANSFER_ENCODING, CURLOPTTYPE_LONG, 207),
 
@@ -1911,7 +1911,7 @@
   /* Enable/disable SASL initial response */
   CURLOPT(CURLOPT_SASL_IR, CURLOPTTYPE_LONG, 218),
 
-  /* Function that will be called instead of the internal progress display
+  /* Function that is called instead of the internal progress display
    * function. This function should be defined as the curl_xferinfo_callback
    * prototype defines. (Deprecates CURLOPT_PROGRESSFUNCTION) */
   CURLOPT(CURLOPT_XFERINFOFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 219),
@@ -2093,7 +2093,7 @@
   /* Head start in milliseconds to give happy eyeballs. */
   CURLOPT(CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, CURLOPTTYPE_LONG, 271),
 
-  /* Function that will be called before a resolver request is made */
+  /* Function that is called before a resolver request is made */
   CURLOPT(CURLOPT_RESOLVER_START_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 272),
 
   /* User data to pass to the resolver start callback. */
@@ -2202,7 +2202,7 @@
   /* used by scp/sftp to verify the host's public key */
   CURLOPT(CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, CURLOPTTYPE_STRINGPOINT, 311),
 
-  /* Function that will be called immediately before the initial request
+  /* Function that is called immediately before the initial request
      is made on a connection (after any protocol negotiation step). */
   CURLOPT(CURLOPT_PREREQFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 312),
 
@@ -2348,13 +2348,13 @@
 #define CURL_RTSPREQ_LAST          12L /* not used */
 
   /* These enums are for use with the CURLOPT_NETRC option. */
-#define CURL_NETRC_IGNORED  0L /* The .netrc will never be read.
+#define CURL_NETRC_IGNORED  0L /* The .netrc is never read.
                                   This is the default. */
-#define CURL_NETRC_OPTIONAL 1L /* A user:password in the URL will be preferred
+#define CURL_NETRC_OPTIONAL 1L /* A user:password in the URL is preferred
                                   to one in the .netrc. */
-#define CURL_NETRC_REQUIRED 2L /* A user:password in the URL will be ignored.
+#define CURL_NETRC_REQUIRED 2L /* A user:password in the URL is ignored.
                                   Unless one is set programmatically, the
-                                  .netrc will be queried. */
+                                  .netrc is queried. */
 enum CURL_NETRC_OPTION {
   /* we set a single member here, to make sure we still provide the enum, but
      the values to use are defined above with L suffixes */
@@ -2635,7 +2635,7 @@
 
 /*
  * callback function for curl_formget()
- * The void *arg pointer will be the one passed as second argument to
+ * The void *arg pointer is the one passed as second argument to
  *   curl_formget().
  * The character buffer passed to it must not be freed.
  * Should return the buffer length passed to it as the argument "len" on
@@ -2650,7 +2650,7 @@
  * DESCRIPTION
  *
  * Serialize a curl_httppost struct built with curl_formadd().
- * Accepts a void pointer as second argument which will be passed to
+ * Accepts a void pointer as second argument which is passed to
  * the curl_formget_callback function.
  * Returns 0 on success.
  */
@@ -2695,7 +2695,7 @@
  * %XX versions). This function returns a new allocated string or NULL if an
  * error occurred.
  */
-CURL_EXTERN char *curl_easy_escape(CURL *handle,
+CURL_EXTERN char *curl_easy_escape(CURL *curl,
                                    const char *string,
                                    int length);
 
@@ -2714,9 +2714,9 @@
  * Conversion Note: On non-ASCII platforms the ASCII %XX codes are
  * converted into the host encoding.
  */
-CURL_EXTERN char *curl_easy_unescape(CURL *handle,
+CURL_EXTERN char *curl_easy_unescape(CURL *curl,
                                      const char *string,
-                                     int length,
+                                     int inlength,
                                      int *outlength);
 
 /* the previous version */
@@ -2756,7 +2756,7 @@
  * initialize libcurl and set user defined memory management callback
  * functions. Users can implement memory management routines to check for
  * memory leaks, check for misuse of the curl library etc. User registered
- * callback routines will be invoked by this library instead of the system
+ * callback routines is invoked by this library instead of the system
  * memory management routines like malloc, free etc.
  */
 CURL_EXTERN CURLcode curl_global_init_mem(long flags,
@@ -2806,8 +2806,8 @@
  *
  * The backend can be identified by the id (e.g. CURLSSLBACKEND_OPENSSL). The
  * backend can also be specified via the name parameter (passing -1 as id). If
- * both id and name are specified, the name will be ignored. If neither id nor
- * name are specified, the function will fail with CURLSSLSET_UNKNOWN_BACKEND
+ * both id and name are specified, the name is ignored. If neither id nor
+ * name are specified, the function fails with CURLSSLSET_UNKNOWN_BACKEND
  * and set the "avail" pointer to the NULL-terminated list of available
  * backends.
  *
@@ -2818,7 +2818,7 @@
  * NULL-terminated list of available SSL backends.
  *
  * The SSL backend can be set only once. If it has already been set, a
- * subsequent attempt to change it will result in a CURLSSLSET_TOO_LATE.
+ * subsequent attempt to change it results in a CURLSSLSET_TOO_LATE.
  */
 
 struct curl_ssl_backend {
@@ -2842,8 +2842,8 @@
  *
  * DESCRIPTION
  *
- * Appends a string to a linked list. If no list exists, it will be created
- * first. Returns the new list, after appending.
+ * Appends a string to a linked list. If no list exists, it is created first.
+ * Returns the new list, after appending.
  */
 CURL_EXTERN struct curl_slist *curl_slist_append(struct curl_slist *list,
                                                  const char *data);
@@ -2991,7 +2991,8 @@
   CURLINFO_EARLYDATA_SENT_T = CURLINFO_OFF_T + 68,
   CURLINFO_HTTPAUTH_USED    = CURLINFO_LONG + 69,
   CURLINFO_PROXYAUTH_USED   = CURLINFO_LONG + 70,
-  CURLINFO_LASTONE          = 70
+  CURLINFO_SIZE_DELIVERED   = CURLINFO_OFF_T + 71,
+  CURLINFO_LASTONE          = 71
 } CURLINFO;
 
 /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as
@@ -3075,9 +3076,8 @@
 } CURLSHoption;
 
 CURL_EXTERN CURLSH *curl_share_init(void);
-CURL_EXTERN CURLSHcode curl_share_setopt(CURLSH *share, CURLSHoption option,
-                                         ...);
-CURL_EXTERN CURLSHcode curl_share_cleanup(CURLSH *share);
+CURL_EXTERN CURLSHcode curl_share_setopt(CURLSH *sh, CURLSHoption option, ...);
+CURL_EXTERN CURLSHcode curl_share_cleanup(CURLSH *sh);
 
 /****************************************************************************
  * Structures for querying information about the curl library at runtime.
@@ -3245,10 +3245,10 @@
  * DESCRIPTION
  *
  * The curl_easy_pause function pauses or unpauses transfers. Select the new
- * state by setting the bitmask, use the convenience defines below.
+ * state by setting the action bitmask, use the convenience defines below.
  *
  */
-CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask);
+CURL_EXTERN CURLcode curl_easy_pause(CURL *curl, int action);
 
 #define CURLPAUSE_RECV      (1 << 0)
 #define CURLPAUSE_RECV_CONT 0
@@ -3267,7 +3267,7 @@
  * The curl_easy_ssls_import function adds a previously exported SSL session
  * to the SSL session cache of the easy handle (or the underlying share).
  */
-CURL_EXTERN CURLcode curl_easy_ssls_import(CURL *handle,
+CURL_EXTERN CURLcode curl_easy_ssls_import(CURL *curl,
                                            const char *session_key,
                                            const unsigned char *shmac,
                                            size_t shmac_len,
@@ -3276,7 +3276,7 @@
 
 /* This is the curl_ssls_export_cb callback prototype. It
  * is passed to curl_easy_ssls_export() to extract SSL sessions/tickets. */
-typedef CURLcode curl_ssls_export_cb(CURL *handle,
+typedef CURLcode curl_ssls_export_cb(CURL *curl,
                                      void *userptr,
                                      const char *session_key,
                                      const unsigned char *shmac,
@@ -3298,7 +3298,7 @@
  * callback.
  *
  */
-CURL_EXTERN CURLcode curl_easy_ssls_export(CURL *handle,
+CURL_EXTERN CURLcode curl_easy_ssls_export(CURL *curl,
                                            curl_ssls_export_cb *export_fn,
                                            void *userptr);
 
@@ -3325,18 +3325,19 @@
 #else
 #if defined(__STDC__) && (__STDC__ >= 1)
 #if 0 /* Triggers clang -Wdisabled-macro-expansion, skip for CMake.  */
-/* This preprocessor magic that replaces a call with the exact same call is
-   only done to make sure application authors pass exactly three arguments
-   to these functions. Use recursive macros to allow using these symbols via
-   the C++ global namespace '::' or reusing them as method names. */
+/* This preprocessor magic ensures that application authors pass exactly three
+   arguments to these functions. For compatibility with C++ global namespace
+   '::' and reusing these symbols as method names, while also avoiding
+   recursive macros, use a two-stage solution. */
+#define curl_exactly_three_arguments(a, b, c) (a, b, c)
 #define curl_easy_setopt(handle, opt, param) \
-  curl_easy_setopt(handle, opt, param)
+  curl_easy_setopt curl_exactly_three_arguments(handle, opt, param)
 #define curl_easy_getinfo(handle, info, arg) \
-  curl_easy_getinfo(handle, info, arg)
+  curl_easy_getinfo curl_exactly_three_arguments(handle, info, arg)
 #define curl_share_setopt(share, opt, param) \
-  curl_share_setopt(share, opt, param)
+  curl_share_setopt curl_exactly_three_arguments(share, opt, param)
 #define curl_multi_setopt(handle, opt, param) \
-  curl_multi_setopt(handle, opt, param)
+  curl_multi_setopt curl_exactly_three_arguments(handle, opt, param)
 #endif
 #endif /* __STDC__ >= 1 */
 #endif /* gcc >= 4.3 && !__cplusplus && !CURL_DISABLE_TYPECHECK */
diff --git a/Utilities/cmcurl/include/curl/curlver.h b/Utilities/cmcurl/include/curl/curlver.h
index 2ea215b..8ae3fbc 100644
--- a/Utilities/cmcurl/include/curl/curlver.h
+++ b/Utilities/cmcurl/include/curl/curlver.h
@@ -32,16 +32,16 @@
 
 /* This is the version number of the libcurl package from which this header
    file origins: */
-#define LIBCURL_VERSION "8.19.0"
+#define LIBCURL_VERSION "8.20.0"
 
 /* The numeric version number is also available "in parts" by using these
    defines: */
 #define LIBCURL_VERSION_MAJOR 8
-#define LIBCURL_VERSION_MINOR 19
+#define LIBCURL_VERSION_MINOR 20
 #define LIBCURL_VERSION_PATCH 0
 /* This is the numeric version of the libcurl version number, meant for easier
-   parsing and comparisons by programs. The LIBCURL_VERSION_NUM define will
-   always follow this syntax:
+   parsing and comparisons by programs. The LIBCURL_VERSION_NUM define always
+   follows this syntax:
 
          0xXXYYZZ
 
@@ -58,7 +58,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 0x081300
+#define LIBCURL_VERSION_NUM 0x081400
 
 /*
  * This is the date and time when the full source package was created. The
diff --git a/Utilities/cmcurl/include/curl/easy.h b/Utilities/cmcurl/include/curl/easy.h
index 0be6915..197e6e7 100644
--- a/Utilities/cmcurl/include/curl/easy.h
+++ b/Utilities/cmcurl/include/curl/easy.h
@@ -51,7 +51,7 @@
  * 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
+ * is 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.
diff --git a/Utilities/cmcurl/include/curl/header.h b/Utilities/cmcurl/include/curl/header.h
index 85c10c8..5c3281a 100644
--- a/Utilities/cmcurl/include/curl/header.h
+++ b/Utilities/cmcurl/include/curl/header.h
@@ -55,14 +55,14 @@
   CURLHE_NOT_BUILT_IN   /* if API was disabled in the build */
 } CURLHcode;
 
-CURL_EXTERN CURLHcode curl_easy_header(CURL *easy,
+CURL_EXTERN CURLHcode curl_easy_header(CURL *curl,
                                        const char *name,
-                                       size_t index,
+                                       size_t nameindex,
                                        unsigned int origin,
                                        int request,
                                        struct curl_header **hout);
 
-CURL_EXTERN struct curl_header *curl_easy_nextheader(CURL *easy,
+CURL_EXTERN struct curl_header *curl_easy_nextheader(CURL *curl,
                                                      unsigned int origin,
                                                      int request,
                                                      struct curl_header *prev);
diff --git a/Utilities/cmcurl/include/curl/multi.h b/Utilities/cmcurl/include/curl/multi.h
index ad6f53f..060b73e 100644
--- a/Utilities/cmcurl/include/curl/multi.h
+++ b/Utilities/cmcurl/include/curl/multi.h
@@ -132,8 +132,8 @@
  *
  * Returns: CURLMcode type, general multi error code.
  */
-CURL_EXTERN CURLMcode curl_multi_add_handle(CURLM *multi_handle,
-                                            CURL *curl_handle);
+CURL_EXTERN CURLMcode curl_multi_add_handle(CURLM *m,
+                                            CURL *curl);
 
 /*
  * Name:    curl_multi_remove_handle()
@@ -142,8 +142,8 @@
  *
  * Returns: CURLMcode type, general multi error code.
  */
-CURL_EXTERN CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
-                                               CURL *curl_handle);
+CURL_EXTERN CURLMcode curl_multi_remove_handle(CURLM *m,
+                                               CURL *curl);
 
 /*
  * Name:    curl_multi_fdset()
@@ -154,7 +154,7 @@
  *
  * Returns: CURLMcode type, general multi error code.
  */
-CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *m,
                                        fd_set *read_fd_set,
                                        fd_set *write_fd_set,
                                        fd_set *exc_fd_set,
@@ -168,7 +168,7 @@
  *
  * Returns:  CURLMcode type, general multi error code.
  */
-CURL_EXTERN CURLMcode curl_multi_wait(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_wait(CURLM *m,
                                       struct curl_waitfd extra_fds[],
                                       unsigned int extra_nfds,
                                       int timeout_ms,
@@ -182,7 +182,7 @@
  *
  * Returns:  CURLMcode type, general multi error code.
  */
-CURL_EXTERN CURLMcode curl_multi_poll(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_poll(CURLM *m,
                                       struct curl_waitfd extra_fds[],
                                       unsigned int extra_nfds,
                                       int timeout_ms,
@@ -195,7 +195,7 @@
  *
  * Returns:  CURLMcode type, general multi error code.
  */
-CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle);
+CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *m);
 
 /*
  * Name:    curl_multi_perform()
@@ -213,7 +213,7 @@
  *          still have occurred problems on individual transfers even when
  *          this returns OK.
  */
-CURL_EXTERN CURLMcode curl_multi_perform(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_perform(CURLM *m,
                                          int *running_handles);
 
 /*
@@ -221,12 +221,12 @@
  *
  * Desc:    Cleans up and removes a whole multi stack. It does not free or
  *          touch any individual easy handles in any way. We need to define
- *          in what state those handles will be if this function is called
- *          in the middle of a transfer.
+ *          in what state those handles are going to be if this function is
+ *          called in the middle of a transfer.
  *
  * Returns: CURLMcode type, general multi error code.
  */
-CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle);
+CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *m);
 
 /*
  * Name:    curl_multi_info_read()
@@ -236,19 +236,19 @@
  *          error code from the transfer or the fact that a transfer is
  *          completed. More details on these should be written down as well.
  *
- *          Repeated calls to this function will return a new struct each
- *          time, until a special "end of msgs" struct is returned as a signal
- *          that there is no more to get at this point.
+ *          Repeated calls to this function return a new struct each time,
+ *          until a special "end of msgs" struct is returned as a signal that
+ *          there is no more to get at this point.
  *
- *          The data the returned pointer points to will not survive calling
+ *          The data the returned pointer points to does not survive calling
  *          curl_multi_cleanup().
  *
  *          The 'CURLMsg' struct is meant to be simple and only contain basic
- *          information. If more involved information is wanted, we will
- *          provide the particular "transfer handle" in that struct and that
+ *          information. If more involved information is wanted, we provide
+ *          the particular "transfer handle" in that struct and that
  *          should/could/would be used in subsequent curl_easy_getinfo() calls
  *          (or similar). The point being that we must never expose complex
- *          structs to applications, as then we will undoubtably get backwards
+ *          structs to applications, as then we undoubtably get backwards
  *          compatibility problems in the future.
  *
  * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out
@@ -256,7 +256,7 @@
  *          queue (after this read) in the integer the second argument points
  *          to.
  */
-CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle,
+CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *m,
                                           int *msgs_in_queue);
 
 /*
@@ -308,21 +308,21 @@
  *
  * Returns: The callback should return zero.
  */
-typedef int (*curl_multi_timer_callback)(CURLM *multi,    /* multi handle */
+typedef int (*curl_multi_timer_callback)(CURLM *m,        /* multi handle */
                                          long timeout_ms, /* see above */
                                          void *userp);    /* private callback
                                                              pointer */
 
 CURL_EXTERN CURLMcode CURL_DEPRECATED(7.19.5, "Use curl_multi_socket_action()")
-curl_multi_socket(CURLM *multi_handle, curl_socket_t s, int *running_handles);
+curl_multi_socket(CURLM *m, curl_socket_t s, int *running_handles);
 
-CURL_EXTERN CURLMcode curl_multi_socket_action(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_socket_action(CURLM *m,
                                                curl_socket_t s,
                                                int ev_bitmask,
                                                int *running_handles);
 
 CURL_EXTERN CURLMcode CURL_DEPRECATED(7.19.5, "Use curl_multi_socket_action()")
-curl_multi_socket_all(CURLM *multi_handle, int *running_handles);
+curl_multi_socket_all(CURLM *m, int *running_handles);
 
 #ifndef CURL_ALLOW_OLD_MULTI_SOCKET
 /* This macro below was added in 7.16.3 to push users who recompile to use
@@ -340,8 +340,8 @@
  *
  * Returns: CURLM error code.
  */
-CURL_EXTERN CURLMcode curl_multi_timeout(CURLM *multi_handle,
-                                         long *milliseconds);
+CURL_EXTERN CURLMcode curl_multi_timeout(CURLM *m,
+                                         long *timeout_ms);
 
 typedef enum {
   /* This is the socket callback function pointer */
@@ -369,11 +369,11 @@
   CURLOPT(CURLMOPT_MAX_PIPELINE_LENGTH, CURLOPTTYPE_LONG, 8),
 
   /* a connection with a content-length longer than this
-     will not be considered for pipelining */
+     is not considered for pipelining */
   CURLOPT(CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE, CURLOPTTYPE_OFF_T, 9),
 
   /* a connection with a chunk length longer than this
-     will not be considered for pipelining */
+     is not considered for pipelining */
   CURLOPT(CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE, CURLOPTTYPE_OFF_T, 10),
 
   /* a list of site names(+port) that are blocked from pipelining */
@@ -403,20 +403,30 @@
   /* This is the argument passed to the notify callback */
   CURLOPT(CURLMOPT_NOTIFYDATA, CURLOPTTYPE_OBJECTPOINT, 19),
 
+  /* maximum number of threads used with threaded DNS resolver */
+  CURLOPT(CURLMOPT_RESOLVE_THREADS_MAX, CURLOPTTYPE_LONG, 20),
+
+  /* set to 1L for not joining threads when multi is cleaned up */
+  CURLOPT(CURLMOPT_QUICK_EXIT, CURLOPTTYPE_LONG, 21),
+
   CURLMOPT_LASTENTRY /* the last unused */
 } CURLMoption;
 
 /* Definition of bits for the CURLMOPT_NETWORK_CHANGED argument: */
 
-/* - CURLMNWC_CLEAR_CONNS tells libcurl to prevent further reuse of existing
-   connections. Connections that are idle will be closed. Ongoing transfers
-   will continue with the connection they have. */
-#define CURLMNWC_CLEAR_CONNS (1L << 0)
+/* - CURLMNWC_CLEAR_ALL tells libcurl to clear "everything" that could be
+   associated with this network, including both connections and DNS data. */
+#define CURLMNWC_CLEAR_ALL (1L << 0)
 
-/* - CURLMNWC_CLEAR_DNS tells libcurl to prevent further reuse of existing
-   connections. Connections that are idle will be closed. Ongoing transfers
-   will continue with the connection they have. */
-#define CURLMNWC_CLEAR_DNS (1L << 0)
+/* - CURLMNWC_CLEAR_CONNS tells libcurl to prevent further reuse of existing
+   connections. Connections that are idle are closed. Ongoing transfers do
+   continue with the connection they have. */
+#define CURLMNWC_CLEAR_CONNS (1L << 1)
+
+/* - CURLMNWC_CLEAR_DNS tells libcurl to clear the DNS cache associated with
+   this multi handle. Ongoing transfers keep using their already resolved
+   addresses, but future name resolutions are performed again. */
+#define CURLMNWC_CLEAR_DNS (1L << 2)
 
 /*
  * Name:    curl_multi_setopt()
@@ -425,7 +435,7 @@
  *
  * Returns: CURLM error code.
  */
-CURL_EXTERN CURLMcode curl_multi_setopt(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_setopt(CURLM *m,
                                         CURLMoption option, ...);
 
 /*
@@ -437,7 +447,7 @@
  *
  * Returns: CURLM error code.
  */
-CURL_EXTERN CURLMcode curl_multi_assign(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_assign(CURLM *m,
                                         curl_socket_t sockfd, void *sockp);
 
 /*
@@ -450,7 +460,7 @@
  *
  * Returns: NULL on failure, otherwise a CURL **array pointer
  */
-CURL_EXTERN CURL **curl_multi_get_handles(CURLM *multi_handle);
+CURL_EXTERN CURL **curl_multi_get_handles(CURLM *m);
 
 typedef enum {
   CURLMINFO_NONE, /* first, never use this */
@@ -479,7 +489,7 @@
  *
  * Returns: CULRM_OK or error when value could not be obtained.
  */
-CURL_EXTERN CURLMcode curl_multi_get_offt(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_get_offt(CURLM *m,
                                           CURLMinfo_offt info,
                                           curl_off_t *pvalue);
 
@@ -518,7 +528,7 @@
  *
  * Returns: CURLMcode type, general multi error code.
  */
-CURL_EXTERN CURLMcode curl_multi_waitfds(CURLM *multi,
+CURL_EXTERN CURLMcode curl_multi_waitfds(CURLM *m,
                                          struct curl_waitfd *ufds,
                                          unsigned int size,
                                          unsigned int *fd_count);
@@ -532,15 +542,15 @@
 /*
  * Callback to install via CURLMOPT_NOTIFYFUNCTION.
  */
-typedef void (*curl_notify_callback)(CURLM *multi,
+typedef void (*curl_notify_callback)(CURLM *m,
                                      unsigned int notification,
                                      CURL *easy,
                                      void *user_data);
 
-CURL_EXTERN CURLMcode curl_multi_notify_disable(CURLM *multi,
+CURL_EXTERN CURLMcode curl_multi_notify_disable(CURLM *m,
                                                 unsigned int notification);
 
-CURL_EXTERN CURLMcode curl_multi_notify_enable(CURLM *multi,
+CURL_EXTERN CURLMcode curl_multi_notify_enable(CURLM *m,
                                                unsigned int notification);
 
 #ifdef __cplusplus
diff --git a/Utilities/cmcurl/include/curl/system.h b/Utilities/cmcurl/include/curl/system.h
index 064833d..c2dbab5 100644
--- a/Utilities/cmcurl/include/curl/system.h
+++ b/Utilities/cmcurl/include/curl/system.h
@@ -45,7 +45,7 @@
  * size of off_t is independent of large file support settings. Keep your
  * build on the safe side avoiding an off_t gating. If you have a 64-bit
  * off_t then take for sure that another 64-bit data type exists, dig deeper
- * and you will find it.
+ * to find it.
  *
  */
 
diff --git a/Utilities/cmcurl/include/curl/typecheck-gcc.h b/Utilities/cmcurl/include/curl/typecheck-gcc.h
index d9a672e..d600c73 100644
--- a/Utilities/cmcurl/include/curl/typecheck-gcc.h
+++ b/Utilities/cmcurl/include/curl/typecheck-gcc.h
@@ -37,8 +37,8 @@
  *       order to work around gcc bug #32061. It affects only gcc 4.3.x/4.4.x
  *       when compiling with -Wlogical-op.
  *
- * To add an option that uses the same type as an existing option, you will
- * need to extend the appropriate _curl_*_option macro
+ * To add an option that uses the same type as an existing option, you need
+ * to extend the appropriate _curl_*_option macro
  */
 
 #define curl_easy_setopt(handle, option, value)                         \
@@ -856,7 +856,7 @@
                                             const void *);
 #ifdef HEADER_SSL_H
 /* hack: if we included OpenSSL's ssl.h, we know about SSL_CTX
- * this will of course break if we are included before OpenSSL headers...
+ * this of course breaks if we are included before OpenSSL headers...
  */
 typedef CURLcode (*Wcurl_ssl_ctx_callback5)(CURL *, SSL_CTX *, void *);
 typedef CURLcode (*Wcurl_ssl_ctx_callback6)(CURL *, SSL_CTX *, const void *);
diff --git a/Utilities/cmcurl/include/curl/urlapi.h b/Utilities/cmcurl/include/curl/urlapi.h
index f7a42b3..b1f3a23 100644
--- a/Utilities/cmcurl/include/curl/urlapi.h
+++ b/Utilities/cmcurl/include/curl/urlapi.h
@@ -114,10 +114,10 @@
 
 /*
  * curl_url_cleanup() frees the CURLU handle and related resources used for
- * the URL parsing. It will not free strings previously returned with the URL
+ * the URL parsing. It does not free strings previously returned with the URL
  * API.
  */
-CURL_EXTERN void curl_url_cleanup(CURLU *handle);
+CURL_EXTERN void curl_url_cleanup(CURLU *u);
 
 /*
  * curl_url_dup() duplicates a CURLU handle and returns a new copy. The new
@@ -130,15 +130,15 @@
  * handle. Returns error code. The returned pointer MUST be freed with
  * curl_free() afterwards.
  */
-CURL_EXTERN CURLUcode curl_url_get(const CURLU *handle, CURLUPart what,
+CURL_EXTERN CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
                                    char **part, unsigned int flags);
 
 /*
  * curl_url_set() sets a specific part of the URL in a CURLU handle. Returns
- * error code. The passed in string will be copied. Passing a NULL instead of
+ * error code. The passed in string is copied. Passing a NULL instead of
  * a part string, clears that part.
  */
-CURL_EXTERN CURLUcode curl_url_set(CURLU *handle, CURLUPart what,
+CURL_EXTERN CURLUcode curl_url_set(CURLU *u, CURLUPart what,
                                    const char *part, unsigned int flags);
 
 /*
diff --git a/Utilities/cmcurl/include/curl/websockets.h b/Utilities/cmcurl/include/curl/websockets.h
index 402a2ca..bf93715 100644
--- a/Utilities/cmcurl/include/curl/websockets.h
+++ b/Utilities/cmcurl/include/curl/websockets.h
@@ -67,7 +67,7 @@
  * Sends data over the websocket connection. Use after successful
  * curl_easy_perform() with CURLOPT_CONNECT_ONLY option.
  */
-CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
+CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer_arg,
                                   size_t buflen, size_t *sent,
                                   curl_off_t fragsize,
                                   unsigned int flags);
diff --git a/Utilities/cmcurl/lib/CMakeLists.txt b/Utilities/cmcurl/lib/CMakeLists.txt
index 673e9be..a11224c 100644
--- a/Utilities/cmcurl/lib/CMakeLists.txt
+++ b/Utilities/cmcurl/lib/CMakeLists.txt
@@ -138,9 +138,9 @@
   endif()
 endif()
 
-if(SHARE_LIB_OBJECT AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
+if(SHARE_LIB_OBJECT)
   set(LIB_OBJECT "libcurl_object")
-  add_library(${LIB_OBJECT} OBJECT ${HHEADERS} ${CSOURCES})  # Requires CMake 3.12
+  add_library(${LIB_OBJECT} OBJECT ${HHEADERS} ${CSOURCES})
   if(WIN32)
     # Define CURL_STATICLIB always, to disable __declspec(dllexport) for
     # exported libcurl symbols. We handle exports via libcurl.def instead.
@@ -168,6 +168,10 @@
   if(CURL_CLANG_TIDY)
     set_target_properties(${LIB_OBJECT} PROPERTIES UNITY_BUILD OFF)
   endif()
+  if(CURL_ANALYZER_CFLAGS)
+    set_target_properties(${LIB_OBJECT} PROPERTIES UNITY_BUILD OFF)
+    set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_OPTIONS ${CURL_ANALYZER_CFLAGS})
+  endif()
   if(CURL_CODE_COVERAGE)
     set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_DEFINITIONS ${CURL_COVERAGE_MACROS})
     set_property(TARGET ${LIB_OBJECT} APPEND PROPERTY COMPILE_OPTIONS ${CURL_COVERAGE_CFLAGS})
@@ -217,6 +221,10 @@
       set_target_properties(${LIB_STATIC} PROPERTIES UNITY_BUILD OFF)
     endif()
   endif()
+  if(CURL_ANALYZER_CFLAGS)
+    set_target_properties(${LIB_STATIC} PROPERTIES UNITY_BUILD OFF)
+    set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_OPTIONS ${CURL_ANALYZER_CFLAGS})
+  endif()
   if(CURL_CODE_COVERAGE)
     set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_DEFINITIONS ${CURL_COVERAGE_MACROS})
     set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_OPTIONS ${CURL_COVERAGE_CFLAGS})
@@ -225,27 +233,6 @@
   target_include_directories(${LIB_STATIC} INTERFACE
     "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
     "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
-
-  # CMake before CMP0099 (CMake 3.17 2020-03-20) did not propagate libdirs to
-  # targets. It expected libs to have an absolute filename. As a workaround,
-  # manually apply dependency libdirs, for CMake consumers without this policy.
-  if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.17)
-    cmake_policy(GET CMP0099 _has_CMP0099)  # https://cmake.org/cmake/help/latest/policy/CMP0099.html
-  endif()
-  if(NOT _has_CMP0099 AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.13 AND CURL_LIBS)
-    set(_curl_libdirs "")
-    foreach(_curl_lib IN LISTS CURL_LIBS)
-      if(TARGET "${_curl_lib}")
-        get_target_property(_curl_libdir "${_curl_lib}" INTERFACE_LINK_DIRECTORIES)
-        if(_curl_libdir)
-          list(APPEND _curl_libdirs "${_curl_libdir}")
-        endif()
-      endif()
-    endforeach()
-    if(_curl_libdirs)
-      target_link_directories(${LIB_STATIC} INTERFACE ${_curl_libdirs})
-    endif()
-  endif()
 endif()
 
 if(BUILD_SHARED_LIBS)
@@ -282,14 +269,14 @@
   if(CURL_CLANG_TIDY)
     set_target_properties(${LIB_SHARED} PROPERTIES UNITY_BUILD OFF)
   endif()
+  if(CURL_ANALYZER_CFLAGS)
+    set_target_properties(${LIB_SHARED} PROPERTIES UNITY_BUILD OFF)
+    set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_OPTIONS ${CURL_ANALYZER_CFLAGS})
+  endif()
   if(CURL_CODE_COVERAGE)
     set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_DEFINITIONS ${CURL_COVERAGE_MACROS})
     set_property(TARGET ${LIB_SHARED} APPEND PROPERTY COMPILE_OPTIONS ${CURL_COVERAGE_CFLAGS})
-    if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
-      set_property(TARGET ${LIB_SHARED} APPEND PROPERTY LINK_OPTIONS ${CURL_COVERAGE_LDFLAGS})
-    else()
-      set_property(TARGET ${LIB_SHARED} APPEND PROPERTY LINK_FLAGS ${CURL_COVERAGE_LDFLAGS})
-    endif()
+    set_property(TARGET ${LIB_SHARED} APPEND PROPERTY LINK_OPTIONS ${CURL_COVERAGE_LDFLAGS})
   endif()
 
   target_include_directories(${LIB_SHARED} INTERFACE
@@ -299,7 +286,7 @@
   if(CMAKE_DLL_NAME_WITH_SOVERSION OR
     CYGWIN OR
     APPLE OR
-    CMAKE_SYSTEM_NAME STREQUAL "AIX" OR
+    AIX OR CMAKE_SYSTEM_NAME STREQUAL "AIX" OR
     CMAKE_SYSTEM_NAME STREQUAL "Linux" OR
     CMAKE_SYSTEM_NAME STREQUAL "SunOS" OR
     CMAKE_SYSTEM_NAME STREQUAL "Haiku" OR
@@ -360,11 +347,7 @@
     set(CMAKE_REQUIRED_LINK_OPTIONS "-Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/libcurl.vers")
     check_c_source_compiles("int main(void) { return 0; }" HAVE_VERSIONED_SYMBOLS)
     if(HAVE_VERSIONED_SYMBOLS)
-      if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13)
-        set_property(TARGET ${LIB_SHARED} APPEND PROPERTY LINK_OPTIONS "${CMAKE_REQUIRED_LINK_OPTIONS}")
-      else()
-        set_property(TARGET ${LIB_SHARED} APPEND PROPERTY LINK_FLAGS "${CMAKE_REQUIRED_LINK_OPTIONS}")
-      endif()
+      set_property(TARGET ${LIB_SHARED} APPEND PROPERTY LINK_OPTIONS "${CMAKE_REQUIRED_LINK_OPTIONS}")
     else()
       message(WARNING "Versioned symbols requested, but not supported by the toolchain.")
     endif()
@@ -402,7 +385,7 @@
 if(PERL_EXECUTABLE)
   add_custom_target(curl-optiontable
     COMMENT "Generating lib/easyoptions.c" VERBATIM USES_TERMINAL
-    COMMAND "${PERL_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/optiontable.pl" < "${PROJECT_SOURCE_DIR}/include/curl/curl.h"
+    COMMAND "${PERL_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/optiontable.pl" "${PROJECT_SOURCE_DIR}/include/curl/curl.h"
       > "${PROJECT_SOURCE_DIR}/lib/easyoptions.c"
     DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/optiontable.pl" "${PROJECT_SOURCE_DIR}/include/curl/curl.h"
   )
diff --git a/Utilities/cmcurl/lib/Makefile.inc b/Utilities/cmcurl/lib/Makefile.inc
index 84b4e54..d762f72 100644
--- a/Utilities/cmcurl/lib/Makefile.inc
+++ b/Utilities/cmcurl/lib/Makefile.inc
@@ -47,7 +47,6 @@
 LIB_CURLX_HFILES =      \
   curlx/base64.h        \
   curlx/basename.h      \
-  curlx/curlx.h         \
   curlx/dynbuf.h        \
   curlx/fopen.h         \
   curlx/inet_ntop.h     \
@@ -150,6 +149,7 @@
   asyn-thrdd.c       \
   bufq.c             \
   bufref.c           \
+  cf-dns.c           \
   cf-h1-proxy.c      \
   cf-h2-proxy.c      \
   cf-haproxy.c       \
@@ -172,7 +172,6 @@
   curl_memrchr.c     \
   curl_ntlm_core.c   \
   curl_range.c       \
-  curl_rtmp.c        \
   curl_sasl.c        \
   curl_sha512_256.c  \
   curl_share.c       \
@@ -182,6 +181,7 @@
   cw-out.c           \
   cw-pause.c         \
   dict.c             \
+  dnscache.c         \
   doh.c              \
   dynhds.c           \
   easy.c             \
@@ -236,6 +236,7 @@
   pingpong.c         \
   pop3.c             \
   progress.c         \
+  protocol.c         \
   psl.c              \
   rand.c             \
   ratelimit.c        \
@@ -259,6 +260,8 @@
   system_win32.c     \
   telnet.c           \
   tftp.c             \
+  thrdpool.c         \
+  thrdqueue.c        \
   transfer.c         \
   uint-bset.c        \
   uint-hash.c        \
@@ -276,6 +279,7 @@
   asyn.h             \
   bufq.h             \
   bufref.h           \
+  cf-dns.h           \
   cf-h1-proxy.h      \
   cf-h2-proxy.h      \
   cf-haproxy.h       \
@@ -304,7 +308,6 @@
   curl_ntlm_core.h   \
   curl_printf.h      \
   curl_range.h       \
-  curl_rtmp.h        \
   curl_sasl.h        \
   curl_setup.h       \
   curl_sha256.h      \
@@ -316,6 +319,7 @@
   cw-out.h           \
   cw-pause.h         \
   dict.h             \
+  dnscache.h         \
   doh.h              \
   dynhds.h           \
   easy_lock.h        \
@@ -327,6 +331,7 @@
   fileinfo.h         \
   formdata.h         \
   ftp.h              \
+  ftp-int.h          \
   ftplistparser.h    \
   functypes.h        \
   getinfo.h          \
@@ -362,6 +367,7 @@
   pingpong.h         \
   pop3.h             \
   progress.h         \
+  protocol.h         \
   psl.h              \
   rand.h             \
   ratelimit.h        \
@@ -386,6 +392,8 @@
   system_win32.h     \
   telnet.h           \
   tftp.h             \
+  thrdpool.h         \
+  thrdqueue.h        \
   transfer.h         \
   uint-bset.h        \
   uint-hash.h        \
diff --git a/Utilities/cmcurl/lib/altsvc.c b/Utilities/cmcurl/lib/altsvc.c
index 34da3e1..81b5379 100644
--- a/Utilities/cmcurl/lib/altsvc.c
+++ b/Utilities/cmcurl/lib/altsvc.c
@@ -45,6 +45,24 @@
 
 #define H3VERSION "h3"
 
+#if defined(DEBUGBUILD) || defined(UNITTESTS)
+/* to play well with debug builds, we can *set* a fixed time this will
+   return */
+static time_t altsvc_debugtime(void *unused)
+{
+  const char *timestr = getenv("CURL_TIME");
+  (void)unused;
+  if(timestr) {
+    curl_off_t val;
+    curlx_str_number(&timestr, &val, TIME_T_MAX);
+    return (time_t)val;
+  }
+  return time(NULL);
+}
+#undef time
+#define time(x) altsvc_debugtime(x)
+#endif
+
 /* Given the ALPN ID, return the name */
 const char *Curl_alpnid2str(enum alpnid id)
 {
@@ -126,6 +144,20 @@
                          srcport, dstport);
 }
 
+/* append the new entry to the list after possibly removing an old entry
+   first */
+static void altsvc_append(struct altsvcinfo *asi, struct altsvc *as)
+{
+  while(Curl_llist_count(&asi->list) >= MAX_ALTSVC_ENTRIES) {
+    /* It's full. Remove the first entry in the list */
+    struct Curl_llist_node *e = Curl_llist_head(&asi->list);
+    struct altsvc *oldas = Curl_node_elem(e);
+    Curl_node_remove(e);
+    altsvc_free(oldas);
+  }
+  Curl_llist_append(&asi->list, as, &as->node);
+}
+
 /* only returns SERIOUS errors */
 static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line)
 {
@@ -162,25 +194,27 @@
      curlx_str_newline(&line))
     ;
   else {
-    struct altsvc *as;
     char dbuf[MAX_ALTSVC_DATELEN + 1];
     time_t expires = 0;
+    time_t now = time(NULL);
 
     /* The date parser works on a null-terminated string. The maximum length
        is upheld by curlx_str_quotedword(). */
     memcpy(dbuf, curlx_str(&date), curlx_strlen(&date));
     dbuf[curlx_strlen(&date)] = 0;
     Curl_getdate_capped(dbuf, &expires);
-    as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn,
-                       (size_t)srcport, (size_t)dstport);
-    if(as) {
-      as->expires = expires;
-      as->prio = 0; /* not supported, set zero */
-      as->persist = persist ? 1 : 0;
-      Curl_llist_append(&asi->list, as, &as->node);
+
+    if(now < expires) {
+      struct altsvc *as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn,
+                                        (size_t)srcport, (size_t)dstport);
+      if(as) {
+        as->expires = expires;
+        as->persist = persist ? 1 : 0;
+        altsvc_append(asi, as);
+      }
+      else
+        return CURLE_OUT_OF_MEMORY;
     }
-    else
-      return CURLE_OUT_OF_MEMORY;
   }
 
   return CURLE_OK;
@@ -208,19 +242,22 @@
 
   fp = curlx_fopen(file, FOPEN_READTEXT);
   if(fp) {
-    bool eof = FALSE;
-    struct dynbuf buf;
-    curlx_dyn_init(&buf, MAX_ALTSVC_LINE);
-    do {
-      result = Curl_get_line(&buf, fp, &eof);
-      if(!result) {
-        const char *lineptr = curlx_dyn_ptr(&buf);
-        curlx_str_passblanks(&lineptr);
-        if(curlx_str_single(&lineptr, '#'))
-          altsvc_add(asi, lineptr);
-      }
-    } while(!result && !eof);
-    curlx_dyn_free(&buf); /* free the line buffer */
+    curlx_struct_stat stat;
+    if((curlx_fstat(fileno(fp), &stat) == -1) || !S_ISDIR(stat.st_mode)) {
+      bool eof = FALSE;
+      struct dynbuf buf;
+      curlx_dyn_init(&buf, MAX_ALTSVC_LINE);
+      do {
+        result = Curl_get_line(&buf, fp, &eof);
+        if(!result) {
+          const char *lineptr = curlx_dyn_ptr(&buf);
+          curlx_str_passblanks(&lineptr);
+          if(curlx_str_single(&lineptr, '#'))
+            altsvc_add(asi, lineptr);
+        }
+      } while(!result && !eof);
+      curlx_dyn_free(&buf); /* free the line buffer */
+    }
     curlx_fclose(fp);
   }
   return result;
@@ -257,7 +294,7 @@
                 "%s %s%s%s %u "
                 "\"%d%02d%02d "
                 "%02d:%02d:%02d\" "
-                "%u %u\n",
+                "%d 0\n", /* prio still always zero */
                 Curl_alpnid2str(as->src.alpnid),
                 src6_pre, as->src.host, src6_post,
                 as->src.port,
@@ -268,7 +305,7 @@
 
                 stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
                 stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
-                as->persist, as->prio);
+                as->persist);
   return CURLE_OK;
 }
 
@@ -427,24 +464,6 @@
   }
 }
 
-#if defined(DEBUGBUILD) || defined(UNITTESTS)
-/* to play well with debug builds, we can *set* a fixed time this will
-   return */
-static time_t altsvc_debugtime(void *unused)
-{
-  const char *timestr = getenv("CURL_TIME");
-  (void)unused;
-  if(timestr) {
-    curl_off_t val;
-    curlx_str_number(&timestr, &val, TIME_T_MAX);
-    return (time_t)val;
-  }
-  return time(NULL);
-}
-#undef time
-#define time(x) altsvc_debugtime(x)
-#endif
-
 /*
  * Curl_altsvc_parse() takes an incoming alt-svc response header and stores
  * the data correctly in the cache.
@@ -588,7 +607,7 @@
             else
               as->expires = maxage + secs;
             as->persist = persist;
-            Curl_llist_append(&asi->list, as, &as->node);
+            altsvc_append(asi, as);
             infof(data, "Added alt-svc: %.*s:%d over %s",
                   (int)curlx_strlen(&dsthost), curlx_str(&dsthost),
                   dstport, Curl_alpnid2str(dstalpnid));
diff --git a/Utilities/cmcurl/lib/altsvc.h b/Utilities/cmcurl/lib/altsvc.h
index 2931af4..dc1740b 100644
--- a/Utilities/cmcurl/lib/altsvc.h
+++ b/Utilities/cmcurl/lib/altsvc.h
@@ -28,9 +28,12 @@
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC)
 #include "llist.h"
 
+/* the maximum number of alt-svc entries kept in a single cache */
+#define MAX_ALTSVC_ENTRIES 5000
+
 struct althost {
   char *host;
-  unsigned short port;
+  uint16_t port;
   enum alpnid alpnid;
 };
 
@@ -39,7 +42,6 @@
   struct althost dst;
   time_t expires;
   struct Curl_llist_node node;
-  unsigned int prio;
   BIT(persist);
 };
 
diff --git a/Utilities/cmcurl/lib/amigaos.c b/Utilities/cmcurl/lib/amigaos.c
index d2afdbf..e4f3bfb 100644
--- a/Utilities/cmcurl/lib/amigaos.c
+++ b/Utilities/cmcurl/lib/amigaos.c
@@ -118,7 +118,7 @@
  * allocates memory also.
  */
 
-struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port)
+struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, uint16_t port)
 {
   struct Curl_addrinfo *ai = NULL;
   struct hostent *h;
@@ -141,7 +141,7 @@
     }
   }
   else {
-#ifdef CURLRES_THREADED
+#ifdef USE_RESOLV_THREADED
     /* gethostbyname() is not thread-safe, so we need to reopen bsdsocket
      * on the thread's context
      */
diff --git a/Utilities/cmcurl/lib/asyn-ares.c b/Utilities/cmcurl/lib/asyn-ares.c
index 4d063d9..a4a7464 100644
--- a/Utilities/cmcurl/lib/asyn-ares.c
+++ b/Utilities/cmcurl/lib/asyn-ares.c
@@ -23,12 +23,12 @@
  ***************************************************************************/
 #include "curl_setup.h"
 
-#ifdef CURLRES_ARES
+#ifdef USE_RESOLV_ARES
 
 /***********************************************************************
- * Only for ares-enabled builds
- * And only for functions that fulfill the asynch resolver backend API
- * as defined in asyn.h, nothing else belongs in this file!
+ * Only for ares-enabled builds and only for functions that fulfill
+ * the asynch resolver backend API as defined in asyn.h,
+ * nothing else belongs in this file!
  **********************************************************************/
 
 #ifdef HAVE_NETINET_IN_H
@@ -58,35 +58,10 @@
 #include "progress.h"
 #include "curlx/timediff.h"
 #include "httpsrr.h"
-
 #include <ares.h>
-#include <ares_version.h> /* really old c-ares did not include this by
-                             itself */
 
-#if ARES_VERSION >= 0x010704
-#define HAVE_CARES_SERVERS_CSV 1
-#define HAVE_CARES_LOCAL_DEV 1
-#define HAVE_CARES_SET_LOCAL 1
-#endif
-
-#if ARES_VERSION >= 0x010b00
-#define HAVE_CARES_PORTS_CSV 1
-#endif
-
-#if ARES_VERSION >= 0x011000
-/* 1.16.0 or later has ares_getaddrinfo */
-#define HAVE_CARES_GETADDRINFO 1
-#else
-/* How long we are willing to wait for additional parallel responses after
-   obtaining a "definitive" one. For old c-ares without getaddrinfo.
-
-   This is intended to equal the c-ares default timeout. curl always uses that
-   default value. Unfortunately, c-ares does not expose its default timeout in
-   its API, but it is officially documented as 5 seconds.
-
-   See query_completed_cb() for an explanation of how this is used.
- */
-#define HAPPY_EYEBALLS_DNS_TIMEOUT 5000
+#if ARES_VERSION < 0x011000
+#error "requires c-ares 1.16.0 or newer"
 #endif
 
 #ifdef USE_HTTPSRR
@@ -101,7 +76,13 @@
 static int ares_ver = 0;
 
 static CURLcode async_ares_set_dns_servers(struct Curl_easy *data,
-                                           bool reset_on_null);
+                                           struct Curl_resolv_async *async);
+static CURLcode async_ares_set_dns_interface(struct Curl_easy *data,
+                                             struct Curl_resolv_async *async);
+static CURLcode async_ares_set_dns_local_ip4(struct Curl_easy *data,
+                                             struct Curl_resolv_async *async);
+static CURLcode async_ares_set_dns_local_ip6(struct Curl_easy *data,
+                                             struct Curl_resolv_async *async);
 
 /*
  * Curl_async_global_init() - the generic low-level asynchronous name
@@ -142,13 +123,18 @@
   }
 }
 
-static CURLcode async_ares_init(struct Curl_easy *data)
+static CURLcode async_ares_init(struct Curl_easy *data,
+                                struct Curl_resolv_async *async)
 {
-  struct async_ares_ctx *ares = &data->state.async.ares;
+  struct async_ares_ctx *ares = &async->ares;
   int status;
   struct ares_options options;
   int optmask = ARES_OPT_SOCK_STATE_CB;
-  CURLcode rc = CURLE_OK;
+  CURLcode result = CURLE_OK;
+
+  /* initial status - failed */
+  ares->ares_status = ARES_ENOTFOUND;
+  async->queries_ongoing = 0;
 
   options.sock_state_cb = sock_state_cb;
   options.sock_state_cb_data = data;
@@ -172,199 +158,168 @@
   status = ares_init_options(&ares->channel, &options, optmask);
   if(status != ARES_SUCCESS) {
     ares->channel = NULL;
-    rc = (status == ARES_ENOMEM) ? CURLE_OUT_OF_MEMORY : CURLE_FAILED_INIT;
+    result = (status == ARES_ENOMEM) ? CURLE_OUT_OF_MEMORY : CURLE_FAILED_INIT;
     goto out;
   }
 
-  rc = async_ares_set_dns_servers(data, FALSE);
-  if(rc && rc != CURLE_NOT_BUILT_IN)
+  result = async_ares_set_dns_servers(data, async);
+  if(result && result != CURLE_NOT_BUILT_IN)
     goto out;
 
-  rc = Curl_async_ares_set_dns_interface(data);
-  if(rc && rc != CURLE_NOT_BUILT_IN)
+  result = async_ares_set_dns_interface(data, async);
+  if(result && result != CURLE_NOT_BUILT_IN)
     goto out;
 
-  rc = Curl_async_ares_set_dns_local_ip4(data);
-  if(rc && rc != CURLE_NOT_BUILT_IN)
+  result = async_ares_set_dns_local_ip4(data, async);
+  if(result && result != CURLE_NOT_BUILT_IN)
     goto out;
 
-  rc = Curl_async_ares_set_dns_local_ip6(data);
-  if(rc && rc != CURLE_NOT_BUILT_IN)
+  result = async_ares_set_dns_local_ip6(data, async);
+  if(result && result != CURLE_NOT_BUILT_IN)
     goto out;
 
-  rc = CURLE_OK;
+  result = CURLE_OK;
 
 out:
-  if(rc && ares->channel) {
+  if(result && ares->channel) {
     ares_destroy(ares->channel);
     ares->channel = NULL;
   }
-  return rc;
-}
-
-static CURLcode async_ares_init_lazy(struct Curl_easy *data)
-{
-  struct async_ares_ctx *ares = &data->state.async.ares;
-  if(!ares->channel)
-    return async_ares_init(data);
-  return CURLE_OK;
-}
-
-CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl)
-{
-  struct async_ares_ctx *ares = &data->state.async.ares;
-  CURLcode result = CURLE_OK;
-  if(!ares->channel) {
-    result = async_ares_init(data);
-  }
-  *impl = ares->channel;
   return result;
 }
 
 /*
  * async_ares_cleanup() cleans up async resolver data.
  */
-static void async_ares_cleanup(struct Curl_easy *data)
+static void async_ares_cleanup(struct Curl_resolv_async *async)
 {
-  struct async_ares_ctx *ares = &data->state.async.ares;
-  if(ares->temp_ai) {
-    Curl_freeaddrinfo(ares->temp_ai);
-    ares->temp_ai = NULL;
+  struct async_ares_ctx *ares = &async->ares;
+  if(ares->res_A) {
+    Curl_freeaddrinfo(ares->res_A);
+    ares->res_A = NULL;
+  }
+  if(ares->res_AAAA) {
+    Curl_freeaddrinfo(ares->res_AAAA);
+    ares->res_AAAA = NULL;
   }
 #ifdef USE_HTTPSRR
   Curl_httpsrr_cleanup(&ares->hinfo);
 #endif
 }
 
-void Curl_async_ares_shutdown(struct Curl_easy *data)
+void Curl_async_ares_shutdown(struct Curl_easy *data,
+                              struct Curl_resolv_async *async)
 {
   /* c-ares has a method to "cancel" operations on a channel, but
    * as reported in #18216, this does not totally reset the channel
    * and ares may get stuck.
    * We need to destroy the channel and on demand create a new
    * one to avoid that. */
-  Curl_async_ares_destroy(data);
+  Curl_async_ares_destroy(data, async);
 }
 
-void Curl_async_ares_destroy(struct Curl_easy *data)
+void Curl_async_ares_destroy(struct Curl_easy *data,
+                             struct Curl_resolv_async *async)
 {
-  struct async_ares_ctx *ares = &data->state.async.ares;
+  struct async_ares_ctx *ares = &async->ares;
+  (void)data;
   if(ares->channel) {
     ares_destroy(ares->channel);
     ares->channel = NULL;
   }
-  async_ares_cleanup(data);
+  async_ares_cleanup(async);
 }
 
-/*
- * Curl_async_pollset() is called when someone from the outside world
- * (using curl_multi_fdset()) wants to get our fd_set setup.
- */
-
-CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps)
+CURLcode Curl_async_pollset(struct Curl_easy *data,
+                            struct Curl_resolv_async *async,
+                            struct easy_pollset *ps)
 {
-  struct async_ares_ctx *ares = &data->state.async.ares;
-  if(ares->channel)
-    return Curl_ares_pollset(data, ares->channel, ps);
-  return CURLE_OK;
+  struct async_ares_ctx *ares = &async->ares;
+  CURLcode result = CURLE_OK;
+
+  if(ares->channel) {
+    result = Curl_ares_pollset(data, ares->channel, ps);
+    if(!result) {
+      timediff_t ms = Curl_ares_timeout_ms(data, async, ares->channel);
+      Curl_expire(data, ms, EXPIRE_ASYNC_NAME);
+    }
+  }
+  return result;
 }
 
 /*
- * Curl_async_is_resolved() is called repeatedly to check if a previous
+ * Curl_async_take_result() is called repeatedly to check if a previous
  * name resolve request has completed. It should also make sure to time-out if
  * the operation seems to take too long.
  *
  * Returns normal CURLcode errors.
  */
-CURLcode Curl_async_is_resolved(struct Curl_easy *data,
-                                struct Curl_dns_entry **dns)
+CURLcode Curl_async_take_result(struct Curl_easy *data,
+                                struct Curl_resolv_async *async,
+                                struct Curl_dns_entry **pdns)
 {
-  struct async_ares_ctx *ares = &data->state.async.ares;
+  struct async_ares_ctx *ares = &async->ares;
   CURLcode result = CURLE_OK;
 
-  DEBUGASSERT(dns);
-  *dns = NULL;
-
-  if(data->state.async.done) {
-    *dns = data->state.async.dns;
-    return ares->result;
-  }
+  DEBUGASSERT(pdns);
+  *pdns = NULL;
+  if(!ares)
+    return CURLE_FAILED_INIT;
 
   if(Curl_ares_perform(ares->channel, 0) < 0) {
     result = CURLE_UNRECOVERABLE_POLL;
     goto out;
   }
 
-#ifndef HAVE_CARES_GETADDRINFO
-  /* Now that we have checked for any last minute results above, see if there
-     are any responses still pending when the EXPIRE_HAPPY_EYEBALLS_DNS timer
-     expires. */
-  if(ares->num_pending &&
-     /* This is only set to non-zero if the timer was started. */
-     (ares->happy_eyeballs_dns_time.tv_sec ||
-      ares->happy_eyeballs_dns_time.tv_usec) &&
-     (curlx_ptimediff_ms(Curl_pgrs_now(data),
-                         &ares->happy_eyeballs_dns_time) >=
-      HAPPY_EYEBALLS_DNS_TIMEOUT)) {
-    /* Remember that the EXPIRE_HAPPY_EYEBALLS_DNS timer is no longer
-       running. */
-    memset(&ares->happy_eyeballs_dns_time, 0,
-           sizeof(ares->happy_eyeballs_dns_time));
-
-    /* Cancel the raw c-ares request, which will fire query_completed_cb() with
-       ARES_ECANCELLED synchronously for all pending responses. This will
-       leave us with res->num_pending == 0, which is perfect for the next
-       block. */
-    ares_cancel(ares->channel);
-    DEBUGASSERT(ares->num_pending == 0);
+  if(async->queries_ongoing) {
+    result = CURLE_AGAIN;
+    goto out;
   }
-#endif
 
-  if(!ares->num_pending) {
-    /* all c-ares operations done, what is the result to report? */
-    Curl_resolv_unlink(data, &data->state.async.dns);
-    data->state.async.done = TRUE;
-    result = ares->result;
-    if(ares->ares_status == ARES_SUCCESS && !result) {
-      data->state.async.dns =
-        Curl_dnscache_mk_entry(data, &ares->temp_ai,
-                               data->state.async.hostname, 0,
-                               data->state.async.port, FALSE);
-      if(!data->state.async.dns) {
-        result = CURLE_OUT_OF_MEMORY;
-        goto out;
-      }
+  /* all c-ares operations done, what is the result to report? */
+  result = ares->result;
+  if(ares->ares_status == ARES_SUCCESS && !result) {
+    struct Curl_dns_entry *dns =
+      Curl_dnscache_mk_entry2(data, async->dns_queries,
+                             &ares->res_AAAA, &ares->res_A,
+                             async->hostname, async->port);
+    if(!dns) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto out;
+    }
 #ifdef HTTPSRR_WORKS
-      {
+    if(async->dns_queries & CURL_DNSQ_HTTPS) {
+      if(ares->hinfo.complete) {
         struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&ares->hinfo);
         if(!lhrr)
           result = CURLE_OUT_OF_MEMORY;
         else
-          data->state.async.dns->hinfo = lhrr;
+          Curl_dns_entry_set_https_rr(dns, lhrr);
       }
+      else
+        Curl_dns_entry_set_https_rr(dns, NULL);
+    }
 #endif
-      if(!result)
-        result = Curl_dnscache_add(data, data->state.async.dns);
+    if(!result) {
+      *pdns = dns;
     }
-    /* if we have not found anything, report the proper
-     * CURLE_COULDNT_RESOLVE_* code */
-    if(!result && !data->state.async.dns) {
-      const char *msg = NULL;
-      if(ares->ares_status != ARES_SUCCESS)
-        msg = ares_strerror(ares->ares_status);
-      result = Curl_resolver_error(data, msg);
-    }
-
-    if(result)
-      Curl_resolv_unlink(data, &data->state.async.dns);
-    *dns = data->state.async.dns;
-    CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound",
-                 result, *dns ? "" : "not ");
-    async_ares_cleanup(data);
+  }
+  /* if we have not found anything, report the proper
+   * CURLE_COULDNT_RESOLVE_* code */
+  if(!result && !*pdns) {
+    const char *msg = NULL;
+    if(ares->ares_status != ARES_SUCCESS)
+      msg = ares_strerror(ares->ares_status);
+    result = Curl_async_failed(data, async, msg);
   }
 
+  CURL_TRC_DNS(data, "ares: is_resolved() result=%d, dns=%sfound",
+               result, *pdns ? "" : "not ");
+  async_ares_cleanup(async);
+
 out:
-  ares->result = result;
+  if(result != CURLE_AGAIN)
+    ares->result = result;
   return result;
 }
 
@@ -375,10 +330,10 @@
   int itimeout_ms;
 
 #if TIMEDIFF_T_MAX > INT_MAX
-    itimeout_ms = (timeout_ms > INT_MAX) ? INT_MAX :
-                   ((timeout_ms < 0) ? -1 : (int)timeout_ms);
+  itimeout_ms = (timeout_ms > INT_MAX) ? INT_MAX :
+                 ((timeout_ms < 0) ? -1 : (int)timeout_ms);
 #else
-    itimeout_ms = (int)timeout_ms;
+  itimeout_ms = (int)timeout_ms;
 #endif
   max_timeout.tv_sec = itimeout_ms / 1000;
   max_timeout.tv_usec = (itimeout_ms % 1000) * 1000;
@@ -394,26 +349,91 @@
     return 1000;
 }
 
+static const struct Curl_addrinfo *async_ares_get_ai(
+  const struct Curl_addrinfo *ai,
+  int ai_family,
+  unsigned int index)
+{
+  unsigned int i = 0;
+  for(i = 0; ai; ai = ai->ai_next) {
+    if(ai->ai_family == ai_family) {
+      if(i == index)
+        return ai;
+      ++i;
+    }
+  }
+  return NULL;
+}
+
+const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data,
+                                              struct Curl_resolv_async *async,
+                                              int ai_family,
+                                              unsigned int index)
+{
+  struct async_ares_ctx *ares = &async->ares;
+
+  (void)data;
+  switch(ai_family) {
+  case AF_INET:
+    if(ares->res_A)
+      return async_ares_get_ai(ares->res_A, ai_family, index);
+    break;
+  case AF_INET6:
+    if(ares->res_AAAA)
+      return async_ares_get_ai(ares->res_AAAA, ai_family, index);
+    break;
+  default:
+    break;
+  }
+  return NULL;
+}
+
+#ifdef USE_HTTPSRR
+const struct Curl_https_rrinfo *Curl_async_get_https(
+  struct Curl_easy *data,
+  struct Curl_resolv_async *async)
+{
+  if(Curl_async_knows_https(data, async))
+    return &async->ares.hinfo;
+  return NULL;
+}
+
+bool Curl_async_knows_https(struct Curl_easy *data,
+                            struct Curl_resolv_async *async)
+{
+  (void)data;
+  if(async->dns_queries & CURL_DNSQ_HTTPS)
+    return ((async->dns_responses & CURL_DNSQ_HTTPS) ||
+            !async->queries_ongoing);
+  return TRUE; /* we know it will never come */
+}
+
+#endif /* USE_HTTPSRR */
+
 /*
  * Curl_async_await()
  *
  * Waits for a resolve to finish. This function should be avoided since using
  * this risk getting the multi interface to "hang".
  *
- * 'entry' MUST be non-NULL.
+ * 'pdns' MUST be non-NULL.
  *
  * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
  * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
  */
-CURLcode Curl_async_await(struct Curl_easy *data,
-                          struct Curl_dns_entry **dns)
+CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id,
+                          struct Curl_dns_entry **pdns)
 {
-  struct async_ares_ctx *ares = &data->state.async.ares;
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
+  struct async_ares_ctx *ares = async ? &async->ares : NULL;
   struct curltime start = *Curl_pgrs_now(data);
   CURLcode result = CURLE_OK;
 
-  DEBUGASSERT(dns);
-  *dns = NULL; /* clear on entry */
+  DEBUGASSERT(pdns);
+  *pdns = NULL; /* clear on entry */
+
+  if(!ares)
+    return CURLE_FAILED_INIT;
 
   /* Wait for the name resolve query to complete or time out. */
   while(!result) {
@@ -422,8 +442,8 @@
     timeout_ms = Curl_timeleft_ms(data);
     if(!timeout_ms) { /* no applicable timeout from `data`*/
       timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &start);
-      if(elapsed_ms < (CURL_TIMEOUT_RESOLVE * 1000))
-        timeout_ms = (CURL_TIMEOUT_RESOLVE * 1000) - elapsed_ms;
+      if(elapsed_ms < CURL_TIMEOUT_RESOLVE_MS)
+        timeout_ms = CURL_TIMEOUT_RESOLVE_MS - elapsed_ms;
       else
         timeout_ms = -1;
     }
@@ -439,8 +459,10 @@
       break;
     }
 
-    result = Curl_async_is_resolved(data, dns);
-    if(result || data->state.async.done)
+    result = Curl_async_take_result(data, async, pdns);
+    if(result == CURLE_AGAIN)
+      result = CURLE_OK;
+    else if(result || *pdns)
       break;
 
     if(Curl_pgrsUpdate(data)) {
@@ -449,148 +471,17 @@
     }
   }
 
-  /* Operation complete, if the lookup was successful we now have the entry
-     in the cache. */
-  data->state.async.done = TRUE;
-  *dns = data->state.async.dns;
-
   if(result)
     ares_cancel(ares->channel);
   return result;
 }
 
-#ifndef HAVE_CARES_GETADDRINFO
-
-/* Connects results to the list */
-static void async_addr_concat(struct Curl_addrinfo **pbase,
-                              struct Curl_addrinfo *ai)
-{
-  if(!ai)
-    return;
-
-  /* When adding `ai` to an existing address list, we prefer ipv6
-   * to be in front. */
-#ifdef USE_IPV6 /* CURLRES_IPV6 */
-  if(*pbase && (*pbase)->ai_family == PF_INET6) {
-    /* ipv6 already in front, append `ai` */
-    struct Curl_addrinfo *tail = *pbase;
-    while(tail->ai_next)
-      tail = tail->ai_next;
-    tail->ai_next = ai;
-  }
-  else
-#endif /* CURLRES_IPV6 */
-  {
-    /* prepend to the (possibly) existing list. */
-    struct Curl_addrinfo *tail = ai;
-    while(tail->ai_next)
-      tail = tail->ai_next;
-    tail->ai_next = *pbase;
-    *pbase = ai;
-  }
-}
-
-/*
- * ares_query_completed_cb() is the callback that ares will call when
- * the host query initiated by ares_gethostbyname() from
- * Curl_async_getaddrinfo(), when using ares, is completed either
- * successfully or with failure.
- */
-static void async_ares_hostbyname_cb(void *user_data,
-                                     int status,
-                                     int timeouts,
-                                     struct hostent *hostent)
-{
-  struct Curl_easy *data = (struct Curl_easy *)user_data;
-  struct async_ares_ctx *ares = &data->state.async.ares;
-
-  (void)timeouts; /* ignored */
-
-  if(ARES_EDESTRUCTION == status)
-    return;
-
-  if(ARES_SUCCESS == status) {
-    ares->ares_status = status; /* one success overrules any error */
-    async_addr_concat(&ares->temp_ai,
-                      Curl_he2ai(hostent, data->state.async.port));
-  }
-  else if(ares->ares_status != ARES_SUCCESS) {
-    /* no success so far, remember last error */
-    ares->ares_status = status;
-  }
-
-  ares->num_pending--;
-
-  CURL_TRC_DNS(data, "ares: hostbyname done, status=%d, pending=%d, "
-               "addr=%sfound",
-               status, ares->num_pending, ares->temp_ai ? "" : "not ");
-  /* If there are responses still pending, we presume they must be the
-     complementary IPv4 or IPv6 lookups that we started in parallel in
-     Curl_async_getaddrinfo() (for Happy Eyeballs). If we have got a
-     "definitive" response from one of a set of parallel queries, we need to
-     think about how long we are willing to wait for more responses. */
-  if(ares->num_pending
-     /* Only these c-ares status values count as "definitive" for these
-        purposes. For example, ARES_ENODATA is what we expect when there is
-        no IPv6 entry for a domain name, and that is not a reason to get more
-        aggressive in our timeouts for the other response. Other errors are
-        either a result of bad input (which should affect all parallel
-        requests), local or network conditions, non-definitive server
-        responses, or us cancelling the request. */
-     && (status == ARES_SUCCESS || status == ARES_ENOTFOUND)) {
-    /* Right now, there can only be up to two parallel queries, so do not
-       bother handling any other cases. */
-    DEBUGASSERT(ares->num_pending == 1);
-
-    /* it is possible that one of these parallel queries could succeed
-       quickly, but the other could always fail or timeout (when we are
-       talking to a pool of DNS servers that can only successfully resolve
-       IPv4 address, for example).
-
-       it is also possible that the other request could always take longer
-       because it needs more time or only the second DNS server can fulfill it
-       successfully. But, to align with the philosophy of Happy Eyeballs, we
-       do not want to wait _too_ long or users will think requests are slow
-       when IPv6 lookups do not actually work (but IPv4 ones do).
-
-       So, now that we have a usable answer (some IPv4 addresses, some IPv6
-       addresses, or "no such domain"), we start a timeout for the remaining
-       pending responses. Even though it is typical that this resolved
-       request came back quickly, that need not be the case. It might be that
-       this completing request did not get a result from the first DNS
-       server or even the first round of the whole DNS server pool. So it
-       could already be a long time after we issued the DNS queries in
-       the first place. Without modifying c-ares, we cannot know exactly
-       where in its retry cycle we are. We could guess based on how much
-       time has gone by, but it does not really matter. Happy Eyeballs tells
-       us that, given usable information in hand, we do not want to
-       wait "too much longer" after we get a result.
-
-       We wait an additional amount of time equal to the default c-ares
-       query timeout. That is enough time for a typical parallel response to
-       arrive without being "too long". Even on a network where one of the two
-       types of queries is failing or timing out constantly, this will usually
-       mean we wait a total of the default c-ares timeout (5 seconds) plus the
-       round trip time for the successful request, which seems bearable. The
-       downside is that c-ares might race with us to issue one more retry
-       before we give up, but it seems better to "waste" that request instead
-       of trying to guess the perfect timeout to prevent it. After all, we do
-       not even know where in the c-ares retry cycle each request is.
-    */
-    ares->happy_eyeballs_dns_time = *Curl_pgrs_now(data);
-    Curl_expire(data, HAPPY_EYEBALLS_DNS_TIMEOUT, EXPIRE_HAPPY_EYEBALLS_DNS);
-  }
-}
-
-#else
-/* c-ares 1.16.0 or later */
-
 /*
  * async_ares_node2addr() converts an address list provided by c-ares
  * to an internal libcurl compatible list.
  */
-static struct Curl_addrinfo *
-async_ares_node2addr(struct ares_addrinfo_node *node)
+static struct Curl_addrinfo *async_ares_node2addr(
+  struct ares_addrinfo_node *node)
 {
   /* traverse the ares_addrinfo_node list */
   struct ares_addrinfo_node *ai;
@@ -660,46 +551,70 @@
   return cafirst;
 }
 
-static void async_ares_addrinfo_cb(void *user_data, int status, int timeouts,
-                                   struct ares_addrinfo *ares_ai)
+static void async_ares_A_cb(void *user_data, int status, int timeouts,
+                            struct ares_addrinfo *ares_ai)
 {
-  struct Curl_easy *data = (struct Curl_easy *)user_data;
-  struct async_ares_ctx *ares = &data->state.async.ares;
+  struct Curl_resolv_async *async = user_data;
+  struct async_ares_ctx *ares = async ? &async->ares : NULL;
+
   (void)timeouts;
-  if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */
-    ares->ares_status = status;
+  if(!async)
+    return;
+
+  async->dns_responses |= CURL_DNSQ_A;
+  async->queries_ongoing--;
+  async->done = !async->queries_ongoing;
   if(status == ARES_SUCCESS) {
-    ares->temp_ai = async_ares_node2addr(ares_ai->nodes);
+    ares->ares_status = ARES_SUCCESS;
+    ares->res_A = async_ares_node2addr(ares_ai->nodes);
     ares_freeaddrinfo(ares_ai);
   }
-  ares->num_pending--;
-  CURL_TRC_DNS(data, "ares: addrinfo done, query status=%d, "
-               "overall status=%d, pending=%d, addr=%sfound",
-               status, ares->ares_status, ares->num_pending,
-               ares->temp_ai ? "" : "not ");
+  else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */
+    ares->ares_status = status;
 }
 
-#endif
+#ifdef CURLRES_IPV6
+static void async_ares_AAAA_cb(void *user_data, int status, int timeouts,
+                               struct ares_addrinfo *ares_ai)
+{
+  struct Curl_resolv_async *async = user_data;
+  struct async_ares_ctx *ares = async ? &async->ares : NULL;
+
+  (void)timeouts;
+  if(!async)
+    return;
+
+  async->dns_responses |= CURL_DNSQ_AAAA;
+  async->queries_ongoing--;
+  async->done = !async->queries_ongoing;
+  if(status == ARES_SUCCESS) {
+    ares->ares_status = ARES_SUCCESS;
+    ares->res_AAAA = async_ares_node2addr(ares_ai->nodes);
+    ares_freeaddrinfo(ares_ai);
+  }
+  else if(ares->ares_status != ARES_SUCCESS) /* do not overwrite success */
+    ares->ares_status = status;
+}
+#endif /* CURLRES_IPV6 */
 
 #ifdef USE_HTTPSRR
 static void async_ares_rr_done(void *user_data, ares_status_t status,
                                size_t timeouts,
                                const ares_dns_record_t *dnsrec)
 {
-  struct Curl_easy *data = user_data;
-  struct async_ares_ctx *ares = &data->state.async.ares;
+  struct Curl_resolv_async *async = user_data;
+  struct async_ares_ctx *ares = async ? &async->ares : NULL;
+
+  if(!async)
+    return;
 
   (void)timeouts;
-  --ares->num_pending;
-  CURL_TRC_DNS(data, "ares: httpsrr done, status=%d, pending=%d, "
-               "dnsres=%sfound",
-               status, ares->num_pending,
-               (dnsrec &&
-                ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER)) ?
-                "" : "not ");
+  async->dns_responses |= CURL_DNSQ_HTTPS;
+  async->queries_ongoing--;
+  async->done = !async->queries_ongoing;
   if((ARES_SUCCESS != status) || !dnsrec)
     return;
-  ares->result = Curl_httpsrr_from_ares(data, dnsrec, &ares->hinfo);
+  ares->result = Curl_httpsrr_from_ares(dnsrec, &ares->hinfo);
 }
 #endif /* USE_HTTPSRR */
 
@@ -708,115 +623,92 @@
  *
  * Starts a name resolve for the given hostname and port number.
  */
-CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname,
-                                int port, int ip_version)
+CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
+                                struct Curl_resolv_async *async)
 {
-  struct async_ares_ctx *ares = &data->state.async.ares;
-#ifdef USE_HTTPSRR
-  char *rrname = NULL;
-#endif
+  struct async_ares_ctx *ares = &async->ares;
+  char service[12];
+  int socktype;
+  CURLcode result = CURLE_OK;
 
-  if(async_ares_init_lazy(data))
-    return CURLE_FAILED_INIT;
-
-  data->state.async.done = FALSE;   /* not done */
-  data->state.async.dns = NULL;     /* clear */
-  data->state.async.port = port;
-  data->state.async.ip_version = ip_version;
-  data->state.async.hostname = curlx_strdup(hostname);
-  if(!data->state.async.hostname)
-    return CURLE_OUT_OF_MEMORY;
-#ifdef USE_HTTPSRR
-  if(port != 443) {
-    rrname = curl_maprintf("_%d_.https.%s", port, hostname);
-    if(!rrname)
-      return CURLE_OUT_OF_MEMORY;
+  if(ares->channel) {
+    DEBUGASSERT(0);
+    result = CURLE_FAILED_INIT;
+    goto out;
   }
-#endif
 
-  /* initial status - failed */
-  ares->ares_status = ARES_ENOTFOUND;
-  ares->result = CURLE_OK;
+  result = async_ares_init(data, async);
+  if(result)
+    goto out;
+
+  result = Curl_resolv_announce_start(data, ares->channel);
+  if(result)
+    goto out;
 
 #if defined(CURLVERBOSE) && ARES_VERSION >= 0x011800 /* >= v1.24.0 */
   if(CURL_TRC_DNS_is_verbose(data)) {
     char *csv = ares_get_servers_csv(ares->channel);
-    CURL_TRC_DNS(data, "asyn-ares: servers=%s", csv);
+    CURL_TRC_DNS(data, "ares: servers=%s", csv);
     ares_free_string(csv);
   }
 #endif
 
-#ifdef HAVE_CARES_GETADDRINFO
-  {
-    struct ares_addrinfo_hints hints;
-    char service[12];
-    int pf = PF_INET;
-    memset(&hints, 0, sizeof(hints));
+  curl_msnprintf(service, sizeof(service), "%d", async->port);
+  socktype =
+    (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ?
+    SOCK_STREAM : SOCK_DGRAM;
+
 #ifdef CURLRES_IPV6
-    if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
-      /* The stack seems to be IPv6-enabled */
-      if(ip_version == CURL_IPRESOLVE_V6)
-        pf = PF_INET6;
-      else
-        pf = PF_UNSPEC;
-    }
-#endif /* CURLRES_IPV6 */
-    CURL_TRC_DNS(data, "asyn-ares: fire off getaddrinfo for %s",
-                 (pf == PF_UNSPEC) ? "A+AAAA" :
-                 ((pf == PF_INET) ? "A" : "AAAA"));
-    hints.ai_family = pf;
-    hints.ai_socktype =
-      (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ?
-      SOCK_STREAM : SOCK_DGRAM;
-    /* Since the service is a numerical one, set the hint flags
-     * accordingly to save a call to getservbyname in inside C-Ares
-     */
+  if(async->dns_queries & CURL_DNSQ_AAAA) {
+    struct ares_addrinfo_hints hints;
+
+    memset(&hints, 0, sizeof(hints));
+    CURL_TRC_DNS(data, "ares: query AAAA records for %s", async->hostname);
+    hints.ai_family = PF_INET6;
+    hints.ai_socktype = socktype;
     hints.ai_flags = ARES_AI_NUMERICSERV;
-    curl_msnprintf(service, sizeof(service), "%d", port);
-    ares->num_pending = 1;
-    ares_getaddrinfo(ares->channel, data->state.async.hostname,
-                     service, &hints, async_ares_addrinfo_cb, data);
+    async->queries_ongoing++;
+    ares_getaddrinfo(ares->channel, async->hostname,
+                     service, &hints, async_ares_AAAA_cb, async);
   }
-#else
+#endif /* CURLRES_IPV6 */
 
-#if ARES_VERSION >= 0x010601  /* IPv6 supported since 1.6.1 */
-  if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
-    /* The stack seems to be IPv6-enabled */
-    /* areschannel is already setup in the Curl_open() function */
-    CURL_TRC_DNS(data, "asyn-ares: fire off query for A");
-    ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET,
-                       async_ares_hostbyname_cb, data);
-    CURL_TRC_DNS(data, "asyn-ares: fire off query for AAAA");
-    ares->num_pending = 2;
-    ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET6,
-                       async_ares_hostbyname_cb, data);
+  if(async->dns_queries & CURL_DNSQ_A) {
+    struct ares_addrinfo_hints hints;
+
+    memset(&hints, 0, sizeof(hints));
+    CURL_TRC_DNS(data, "ares: query A records for %s", async->hostname);
+    hints.ai_family = PF_INET;
+    hints.ai_socktype = socktype;
+    hints.ai_flags = ARES_AI_NUMERICSERV;
+    async->queries_ongoing++;
+    ares_getaddrinfo(ares->channel, async->hostname,
+                     service, &hints, async_ares_A_cb, async);
   }
-  else
-#endif
-  {
-    /* areschannel is already setup in the Curl_open() function */
-    CURL_TRC_DNS(data, "asyn-ares: fire off query for A");
-    ares->num_pending = 1;
-    ares_gethostbyname(ares->channel, data->state.async.hostname, PF_INET,
-                       async_ares_hostbyname_cb, data);
-  }
-#endif
+
 #ifdef USE_HTTPSRR
-  {
-    CURL_TRC_DNS(data, "asyn-ares: fire off query for HTTPSRR: %s",
-                 rrname ? rrname : data->state.async.hostname);
-    memset(&ares->hinfo, 0, sizeof(ares->hinfo));
-    ares->hinfo.port = -1;
+  memset(&ares->hinfo, 0, sizeof(ares->hinfo));
+  if(async->dns_queries & CURL_DNSQ_HTTPS) {
+    char *rrname = NULL;
+    if(async->port != 443) {
+      rrname = curl_maprintf("_%d._https.%s", async->port, async->hostname);
+      if(!rrname)
+        return CURLE_OUT_OF_MEMORY;
+    }
+    CURL_TRC_DNS(data, "ares: query HTTPS records for %s",
+                 rrname ? rrname : async->hostname);
     ares->hinfo.rrname = rrname;
-    ares->num_pending++; /* one more */
+    async->queries_ongoing++;
     ares_query_dnsrec(ares->channel,
-                      rrname ? rrname : data->state.async.hostname,
+                      rrname ? rrname : async->hostname,
                       ARES_CLASS_IN, ARES_REC_TYPE_HTTPS,
-                      async_ares_rr_done, data, NULL);
+                      async_ares_rr_done, async, NULL);
   }
-#endif
+#endif /* USE_HTTPSRR */
 
-  return CURLE_OK;
+out:
+  ares->result = result;
+  return result ? result : (async->queries_ongoing ? CURLE_AGAIN : CURLE_OK);
 }
 
 /* Set what DNS server are is to use. This is called in 2 situations:
@@ -826,33 +718,24 @@
  * 2. When we lazy init the ares channel and NULL means that there
  *    are no preferences and we do not reset any existing channel. */
 static CURLcode async_ares_set_dns_servers(struct Curl_easy *data,
-                                           bool reset_on_null)
+                                           struct Curl_resolv_async *async)
 {
-  struct async_ares_ctx *ares = &data->state.async.ares;
+  struct async_ares_ctx *ares = async ? &async->ares : NULL;
   CURLcode result = CURLE_NOT_BUILT_IN;
   const char *servers = data->set.str[STRING_DNS_SERVERS];
   int ares_result = ARES_SUCCESS;
 
-#if defined(DEBUGBUILD) && defined(HAVE_CARES_SERVERS_CSV)
+#ifdef DEBUGBUILD
   if(getenv("CURL_DNS_SERVER"))
     servers = getenv("CURL_DNS_SERVER");
 #endif
 
-  if(!servers) {
-    if(reset_on_null) {
-      Curl_async_destroy(data);
-    }
+  if(!servers)
     return CURLE_OK;
-  }
 
-#ifdef HAVE_CARES_SERVERS_CSV
   /* if channel is not there, this is a parameter check */
-  if(ares->channel)
-#ifdef HAVE_CARES_PORTS_CSV
+  if(ares && ares->channel)
     ares_result = ares_set_servers_ports_csv(ares->channel, servers);
-#else
-    ares_result = ares_set_servers_csv(ares->channel, servers);
-#endif
   switch(ares_result) {
   case ARES_SUCCESS:
     result = CURLE_OK;
@@ -868,43 +751,29 @@
     result = CURLE_BAD_FUNCTION_ARGUMENT;
     break;
   }
-#else /* c-ares version too old! */
-  (void)data;
-  (void)(ares_result);
-#endif
   return result;
 }
 
-CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data)
+static CURLcode async_ares_set_dns_interface(struct Curl_easy *data,
+                                             struct Curl_resolv_async *async)
 {
-  return async_ares_set_dns_servers(data, TRUE);
-}
-
-CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data)
-{
-#ifdef HAVE_CARES_LOCAL_DEV
-  struct async_ares_ctx *ares = &data->state.async.ares;
+  struct async_ares_ctx *ares = async ? &async->ares : NULL;
   const char *interf = data->set.str[STRING_DNS_INTERFACE];
 
   if(!interf)
     interf = "";
 
   /* if channel is not there, this is a parameter check */
-  if(ares->channel)
+  if(ares && ares->channel)
     ares_set_local_dev(ares->channel, interf);
 
   return CURLE_OK;
-#else /* c-ares version too old! */
-  (void)data;
-  (void)interf;
-  return CURLE_NOT_BUILT_IN;
-#endif
 }
 
-CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data)
+static CURLcode async_ares_set_dns_local_ip4(struct Curl_easy *data,
+                                             struct Curl_resolv_async *async)
 {
-#ifdef HAVE_CARES_SET_LOCAL
-  struct async_ares_ctx *ares = &data->state.async.ares;
+  struct async_ares_ctx *ares = async ? &async->ares : NULL;
   struct in_addr a4;
   const char *local_ip4 = data->set.str[STRING_DNS_LOCAL_IP4];
 
@@ -919,21 +788,17 @@
   }
 
   /* if channel is not there yet, this is a parameter check */
-  if(ares->channel)
+  if(ares && ares->channel)
     ares_set_local_ip4(ares->channel, ntohl(a4.s_addr));
 
   return CURLE_OK;
-#else /* c-ares version too old! */
-  (void)data;
-  (void)local_ip4;
-  return CURLE_NOT_BUILT_IN;
-#endif
 }
 
-CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data)
+static CURLcode async_ares_set_dns_local_ip6(struct Curl_easy *data,
+                                             struct Curl_resolv_async *async)
 {
-#if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6)
-  struct async_ares_ctx *ares = &data->state.async.ares;
+#ifdef USE_IPV6
+  struct async_ares_ctx *ares = async ? &async->ares : NULL;
   unsigned char a6[INET6_ADDRSTRLEN];
   const char *local_ip6 = data->set.str[STRING_DNS_LOCAL_IP6];
 
@@ -949,14 +814,15 @@
   }
 
   /* if channel is not there, this is a parameter check */
-  if(ares->channel)
+  if(ares && ares->channel)
     ares_set_local_ip6(ares->channel, a6);
 
   return CURLE_OK;
-#else /* c-ares version too old! */
+#else /* no IPv6 support */
   (void)data;
+  (void)async;
   return CURLE_NOT_BUILT_IN;
 #endif
 }
 
-#endif /* CURLRES_ARES */
+#endif /* USE_RESOLV_ARES */
diff --git a/Utilities/cmcurl/lib/asyn-base.c b/Utilities/cmcurl/lib/asyn-base.c
index 44bf98c..62cb0ef 100644
--- a/Utilities/cmcurl/lib/asyn-base.c
+++ b/Utilities/cmcurl/lib/asyn-base.c
@@ -39,12 +39,14 @@
 
 #ifdef USE_ARES
 #include <ares.h>
-#include <ares_version.h> /* really old c-ares did not include it by itself */
 #endif
 
 #include "urldata.h"
+#include "connect.h"
+#include "curl_trc.h"
 #include "hostip.h"
 #include "multiif.h"
+#include "progress.h"
 #include "select.h"
 #include "url.h"
 
@@ -53,10 +55,21 @@
  **********************************************************************/
 #ifdef CURLRES_ASYNCH
 
+timediff_t Curl_async_timeleft_ms(struct Curl_easy *data,
+                                  struct Curl_resolv_async *async)
+{
+  if(async->timeout_ms) {
+    timediff_t elapsed_ms =
+      curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start);
+    return async->timeout_ms - elapsed_ms;
+  }
+  return Curl_timeleft_ms(data);
+}
+
 #ifdef USE_ARES
 
-#if ARES_VERSION < 0x010600
-#error "requires c-ares 1.6.0 or newer"
+#if ARES_VERSION < 0x011000
+#error "requires c-ares 1.16.0 or newer"
 #endif
 
 /*
@@ -71,12 +84,8 @@
                            ares_channel channel,
                            struct easy_pollset *ps)
 {
-  struct timeval maxtime = { CURL_TIMEOUT_RESOLVE, 0 };
-  struct timeval timebuf;
   curl_socket_t sockets[16];  /* ARES documented limit */
   unsigned int bitmap, i;
-  struct timeval *timeout;
-  timediff_t milli;
   CURLcode result = CURLE_OK;
 
   DEBUGASSERT(channel);
@@ -97,15 +106,33 @@
     if(result)
       return result;
   }
-
-  timeout = ares_timeout(channel, &maxtime, &timebuf);
-  if(!timeout)
-    timeout = &maxtime;
-  milli = curlx_tvtoms(timeout);
-  Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
   return result;
 }
 
+timediff_t Curl_ares_timeout_ms(struct Curl_easy *data,
+                                struct Curl_resolv_async *async,
+                                ares_channel channel)
+{
+  timediff_t async_timeout_ms;
+
+  DEBUGASSERT(channel);
+  if(!channel)
+    return -1;
+
+  async_timeout_ms = Curl_async_timeleft_ms(data, async);
+  if((async_timeout_ms > 0) && (async_timeout_ms < INT_MAX)) {
+    struct timeval timebuf;
+    struct timeval *timeout;
+    struct timeval end = { (int)async_timeout_ms / 1000,
+                           ((int)async_timeout_ms % 1000) * 1000 };
+
+    timeout = ares_timeout(channel, &end, &timebuf);
+    if(timeout)
+      return curlx_tvtoms(timeout);
+  }
+  return async_timeout_ms;
+}
+
 /*
  * Curl_ares_perform()
  *
@@ -178,32 +205,61 @@
 
 #include "doh.h"
 
-void Curl_async_shutdown(struct Curl_easy *data)
+void Curl_async_shutdown(struct Curl_easy *data,
+                         struct Curl_resolv_async *async)
 {
-#ifdef CURLRES_ARES
-  Curl_async_ares_shutdown(data);
+  if(async) {
+    CURL_TRC_DNS(data, "[%u] shutdown async", async->id);
+    async->shutdown = TRUE;
+#ifdef USE_RESOLV_ARES
+    Curl_async_ares_shutdown(data, async);
 #endif
-#ifdef CURLRES_THREADED
-  Curl_async_thrdd_shutdown(data);
+#ifdef USE_RESOLV_THREADED
+    Curl_async_thrdd_shutdown(data, async);
 #endif
 #ifndef CURL_DISABLE_DOH
-  Curl_doh_cleanup(data);
+    Curl_doh_cleanup(data, async);
 #endif
-  Curl_safefree(data->state.async.hostname);
+  }
 }
 
-void Curl_async_destroy(struct Curl_easy *data)
+void Curl_async_destroy(struct Curl_easy *data,
+                        struct Curl_resolv_async *async)
 {
-#ifdef CURLRES_ARES
-  Curl_async_ares_destroy(data);
+  if(async) {
+    CURL_TRC_DNS(data, "[%u] destroy async", async->id);
+    async->shutdown = TRUE;
+#ifdef USE_RESOLV_ARES
+    Curl_async_ares_destroy(data, async);
 #endif
-#ifdef CURLRES_THREADED
-  Curl_async_thrdd_destroy(data);
+#ifdef USE_RESOLV_THREADED
+    Curl_async_thrdd_destroy(data, async);
 #endif
 #ifndef CURL_DISABLE_DOH
-  Curl_doh_cleanup(data);
+    Curl_doh_cleanup(data, async);
 #endif
-  Curl_safefree(data->state.async.hostname);
+    curlx_safefree(async);
+  }
+}
+
+CURLcode Curl_async_failed(struct Curl_easy *data,
+                           struct Curl_resolv_async *async,
+                           const char *detail)
+{
+  const char *host_or_proxy = "host";
+  CURLcode result = CURLE_COULDNT_RESOLVE_HOST;
+
+#ifndef CURL_DISABLE_PROXY
+  if(async->for_proxy) {
+    host_or_proxy = "proxy";
+    result = CURLE_COULDNT_RESOLVE_PROXY;
+  }
+#endif
+
+  failf(data, "Could not resolve %s: %s%s%s%s",
+        host_or_proxy, async->hostname,
+        detail ? " (" : "", detail ? detail : "", detail ? ")" : "");
+  return result;
 }
 
 #endif /* USE_CURL_ASYNC */
diff --git a/Utilities/cmcurl/lib/asyn-thrdd.c b/Utilities/cmcurl/lib/asyn-thrdd.c
index c3ed8ea..90f055c 100644
--- a/Utilities/cmcurl/lib/asyn-thrdd.c
+++ b/Utilities/cmcurl/lib/asyn-thrdd.c
@@ -26,9 +26,7 @@
 /***********************************************************************
  * Only for threaded name resolves builds
  **********************************************************************/
-#ifdef CURLRES_THREADED
-
-#include "socketpair.h"
+#ifdef USE_RESOLV_THREADED
 
 #ifdef HAVE_NETINET_IN_H
 #include <netinet/in.h>
@@ -44,10 +42,6 @@
 #include <inet.h>
 #endif
 
-#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
-#include <pthread.h>
-#endif
-
 #ifdef HAVE_GETADDRINFO
 #define RESOLVER_ENOMEM  EAI_MEMORY  /* = WSA_NOT_ENOUGH_MEMORY on Windows */
 #else
@@ -64,7 +58,11 @@
 #include "multiif.h"
 #include "curl_threads.h"
 #include "progress.h"
+#include "rand.h"
 #include "select.h"
+#include "thrdqueue.h"
+#include "curlx/strparse.h"
+#include "curlx/wait.h"
 
 #ifdef USE_ARES
 #include <ares.h>
@@ -101,229 +99,93 @@
 #endif
 }
 
-static void async_thrdd_destroy(struct Curl_easy *data);
-static void async_thrdd_shutdown(struct Curl_easy *data);
+#ifdef CURLVERBOSE
+#define CURL_ASYN_ITEM_DESC_LEN   64
+#define async_item_description(x)   (x)->description
+#else
+#define async_item_description(x)   NULL
+#endif
 
-CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl)
-{
-  (void)data;
-  *impl = NULL;
-  return CURLE_OK;
-}
+struct async_thrdd_item {
+  struct Curl_addrinfo *res;
+#ifdef CURLVERBOSE
+  char description[CURL_ASYN_ITEM_DESC_LEN];
+#endif
+  int sock_error;
+  uint32_t mid;
+  uint32_t resolv_id;
+  uint16_t port;
+  uint8_t transport;
+  uint8_t dns_queries;
+#ifdef DEBUGBUILD
+  uint32_t delay_ms;
+  uint32_t delay_fail_ms;
+#endif
+  char hostname[1];
+};
 
 /* Give up reference to add_ctx */
-static void addr_ctx_unlink(struct async_thrdd_addr_ctx **paddr_ctx,
-                            struct Curl_easy *data)
+static void async_thrdd_item_destroy(struct async_thrdd_item *item)
 {
-  struct async_thrdd_addr_ctx *addr_ctx = *paddr_ctx;
-  bool destroy;
-
-  if(!addr_ctx)
-    return;
-
-  Curl_mutex_acquire(&addr_ctx->mutx);
-  if(!data)  /* called by resolving thread */
-    addr_ctx->thrd_done = TRUE;
-
-  DEBUGASSERT(addr_ctx->ref_count);
-  --addr_ctx->ref_count;
-  destroy = !addr_ctx->ref_count;
-  Curl_mutex_release(&addr_ctx->mutx);
-
-  if(destroy) {
-    Curl_mutex_destroy(&addr_ctx->mutx);
-    curlx_free(addr_ctx->hostname);
-    if(addr_ctx->res)
-      Curl_freeaddrinfo(addr_ctx->res);
-    Curl_wakeup_destroy(addr_ctx->sock_pair);
-    curlx_free(addr_ctx);
+  if(item) {
+    if(item->res)
+      Curl_freeaddrinfo(item->res);
+    curlx_free(item);
   }
-  *paddr_ctx = NULL;
 }
 
 /* Initialize context for threaded resolver */
-static struct async_thrdd_addr_ctx *
-addr_ctx_create(struct Curl_easy *data,
-                const char *hostname, int port,
-                const struct addrinfo *hints)
+static struct async_thrdd_item *async_thrdd_item_create(
+  struct Curl_easy *data,
+  uint32_t resolv_id, uint8_t dns_queries,
+  const char *hostname, uint16_t port,
+  uint8_t transport)
 {
-  struct async_thrdd_addr_ctx *addr_ctx = curlx_calloc(1, sizeof(*addr_ctx));
-  if(!addr_ctx)
+  size_t hostlen = strlen(hostname);
+  struct async_thrdd_item *item;
+
+  item = curlx_calloc(1, sizeof(*item) + hostlen);
+  if(!item)
     return NULL;
 
-  addr_ctx->thread_hnd = curl_thread_t_null;
-  addr_ctx->port = port;
-  addr_ctx->ref_count = 1;
+  if(hostlen) /* NUL byte of name already in struct size */
+    memcpy(item->hostname, hostname, hostlen);
+  item->mid = data->mid;
+  item->resolv_id = resolv_id;
+  item->dns_queries = dns_queries;
+  item->port = port;
+  item->transport = transport;
 
-#ifdef HAVE_GETADDRINFO
-  DEBUGASSERT(hints);
-  addr_ctx->hints = *hints;
-#else
-  (void)hints;
+#ifdef CURLVERBOSE
+  curl_msnprintf(item->description, sizeof(item->description),
+                 "[%" FMT_OFF_T "/%u] %s %s:%u",
+                 data->id, item->resolv_id,
+                 Curl_resolv_query_str(dns_queries),
+                 item->hostname, item->port);
 #endif
 
-  Curl_mutex_init(&addr_ctx->mutx);
-
-#ifndef CURL_DISABLE_SOCKETPAIR
-  /* create socket pair or pipe */
-  if(Curl_wakeup_init(addr_ctx->sock_pair, FALSE) < 0) {
-    addr_ctx->sock_pair[0] = CURL_SOCKET_BAD;
-    addr_ctx->sock_pair[1] = CURL_SOCKET_BAD;
-    goto err_exit;
-  }
-#endif
-  addr_ctx->sock_error = 0;
-
-  /* Copying hostname string because original can be destroyed by parent
-   * thread during gethostbyname execution.
-   */
-  addr_ctx->hostname = curlx_strdup(hostname);
-  if(!addr_ctx->hostname)
-    goto err_exit;
-
-  return addr_ctx;
-
-err_exit:
-  addr_ctx_unlink(&addr_ctx, data);
-  return NULL;
-}
-
-#ifdef HAVE_GETADDRINFO
-
-/*
- * getaddrinfo_thread() resolves a name and then exits.
- *
- * For builds without ARES, but with USE_IPV6, create a resolver thread
- * and wait on it.
- */
-static CURL_THREAD_RETURN_T CURL_STDCALL getaddrinfo_thread(void *arg)
-{
-  struct async_thrdd_addr_ctx *addr_ctx = arg;
-  curl_bit do_abort;
-
-  Curl_mutex_acquire(&addr_ctx->mutx);
-  do_abort = addr_ctx->do_abort;
-  Curl_mutex_release(&addr_ctx->mutx);
-
-  if(!do_abort) {
-    char service[12];
-    int rc;
-
-    curl_msnprintf(service, sizeof(service), "%d", addr_ctx->port);
-
-    rc = Curl_getaddrinfo_ex(addr_ctx->hostname, service,
-                             &addr_ctx->hints, &addr_ctx->res);
-
-    if(rc) {
-      addr_ctx->sock_error = SOCKERRNO ? SOCKERRNO : rc;
-      if(addr_ctx->sock_error == 0)
-        addr_ctx->sock_error = RESOLVER_ENOMEM;
-    }
-    else {
-      Curl_addrinfo_set_port(addr_ctx->res, addr_ctx->port);
-    }
-
-    Curl_mutex_acquire(&addr_ctx->mutx);
-    do_abort = addr_ctx->do_abort;
-    Curl_mutex_release(&addr_ctx->mutx);
-#ifndef CURL_DISABLE_SOCKETPAIR
-    if(!do_abort) {
-      /* Thread is done, notify transfer */
-      int err = Curl_wakeup_signal(addr_ctx->sock_pair);
-      if(err) {
-        /* update sock_error to errno */
-        addr_ctx->sock_error = err;
+#ifdef DEBUGBUILD
+  {
+    const char *p = getenv("CURL_DBG_RESOLV_DELAY");
+    if(p) {
+      curl_off_t l;
+      if(!curlx_str_number(&p, &l, UINT32_MAX)) {
+        item->delay_ms = (uint32_t)l;
       }
     }
-#endif
-  }
-
-  addr_ctx_unlink(&addr_ctx, NULL);
-  return 0;
-}
-
-#else /* HAVE_GETADDRINFO */
-
-/*
- * gethostbyname_thread() resolves a name and then exits.
- */
-static CURL_THREAD_RETURN_T CURL_STDCALL gethostbyname_thread(void *arg)
-{
-  struct async_thrdd_addr_ctx *addr_ctx = arg;
-  bool do_abort;
-
-  Curl_mutex_acquire(&addr_ctx->mutx);
-  do_abort = addr_ctx->do_abort;
-  Curl_mutex_release(&addr_ctx->mutx);
-
-  if(!do_abort) {
-    addr_ctx->res = Curl_ipv4_resolve_r(addr_ctx->hostname, addr_ctx->port);
-    if(!addr_ctx->res) {
-      addr_ctx->sock_error = SOCKERRNO;
-      if(addr_ctx->sock_error == 0)
-        addr_ctx->sock_error = RESOLVER_ENOMEM;
-    }
-
-    Curl_mutex_acquire(&addr_ctx->mutx);
-    do_abort = addr_ctx->do_abort;
-    Curl_mutex_release(&addr_ctx->mutx);
-#ifndef CURL_DISABLE_SOCKETPAIR
-    if(!do_abort) {
-      int err = Curl_wakeup_signal(addr_ctx->sock_pair);
-      if(err) {
-        /* update sock_error to errno */
-        addr_ctx->sock_error = err;
+    p = getenv("CURL_DBG_RESOLV_FAIL_DELAY");
+    if(p) {
+      curl_off_t l;
+      if(!curlx_str_number(&p, &l, UINT32_MAX)) {
+        unsigned char c = 0;
+        Curl_rand_bytes(data, FALSE, &c, 1);
+        item->delay_fail_ms = (uint32_t)l + c;
       }
     }
-#endif
   }
-
-  addr_ctx_unlink(&addr_ctx, NULL);
-  return 0;
-}
-
-#endif /* HAVE_GETADDRINFO */
-
-/*
- * async_thrdd_destroy() cleans up async resolver data and thread handle.
- */
-static void async_thrdd_destroy(struct Curl_easy *data)
-{
-  struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
-  struct async_thrdd_addr_ctx *addr = thrdd->addr;
-
-#ifdef USE_HTTPSRR_ARES
-  if(thrdd->rr.channel) {
-    ares_destroy(thrdd->rr.channel);
-    thrdd->rr.channel = NULL;
-  }
-  Curl_httpsrr_cleanup(&thrdd->rr.hinfo);
 #endif
 
-  if(thrdd->addr && (thrdd->addr->thread_hnd != curl_thread_t_null)) {
-    curl_bit done;
-
-    Curl_mutex_acquire(&addr->mutx);
-#ifndef CURL_DISABLE_SOCKETPAIR
-    if(!addr->do_abort)
-      Curl_multi_will_close(data, addr->sock_pair[0]);
-#endif
-    addr->do_abort = TRUE;
-    done = addr->thrd_done;
-    Curl_mutex_release(&addr->mutx);
-
-    if(done) {
-      Curl_thread_join(&addr->thread_hnd);
-      CURL_TRC_DNS(data, "async_thrdd_destroy, thread joined");
-    }
-    else {
-      /* thread is still running. Detach it. */
-      Curl_thread_destroy(&addr->thread_hnd);
-      CURL_TRC_DNS(data, "async_thrdd_destroy, thread detached");
-    }
-  }
-  /* release our reference to the shared context */
-  addr_ctx_unlink(&thrdd->addr, data);
+  return item;
 }
 
 #ifdef USE_HTTPSRR_ARES
@@ -332,25 +194,30 @@
                                 size_t timeouts,
                                 const ares_dns_record_t *dnsrec)
 {
-  struct Curl_easy *data = user_data;
-  struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
+  struct Curl_resolv_async *async = user_data;
+  struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL;
 
   (void)timeouts;
-  thrdd->rr.done = TRUE;
-  if((ARES_SUCCESS != status) || !dnsrec)
+  if(!thrdd)
     return;
-  thrdd->rr.result = Curl_httpsrr_from_ares(data, dnsrec, &thrdd->rr.hinfo);
+
+  async->dns_responses |= CURL_DNSQ_HTTPS;
+  async->queries_ongoing--;
+  async->done = !async->queries_ongoing;
+  if((ARES_SUCCESS == status) && dnsrec)
+    async->result = Curl_httpsrr_from_ares(dnsrec, &thrdd->rr.hinfo);
 }
 
-static CURLcode async_rr_start(struct Curl_easy *data, int port)
+static CURLcode async_rr_start(struct Curl_easy *data,
+                               struct Curl_resolv_async *async)
 {
-  struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
+  struct async_thrdd_ctx *thrdd = &async->thrdd;
   int status;
   char *rrname = NULL;
 
   DEBUGASSERT(!thrdd->rr.channel);
-  if(port != 443) {
-    rrname = curl_maprintf("_%d_.https.%s", port, data->conn->host.name);
+  if(async->port != 443) {
+    rrname = curl_maprintf("_%d_.https.%s", async->port, async->hostname);
     if(!rrname)
       return CURLE_OUT_OF_MEMORY;
   }
@@ -372,327 +239,82 @@
 #endif
 
   memset(&thrdd->rr.hinfo, 0, sizeof(thrdd->rr.hinfo));
-  thrdd->rr.hinfo.port = -1;
   thrdd->rr.hinfo.rrname = rrname;
+  async->queries_ongoing++;
   ares_query_dnsrec(thrdd->rr.channel,
-                    rrname ? rrname : data->conn->host.name, ARES_CLASS_IN,
+                    rrname ? rrname : async->hostname, ARES_CLASS_IN,
                     ARES_REC_TYPE_HTTPS,
-                    async_thrdd_rr_done, data, NULL);
-  CURL_TRC_DNS(data, "Issued HTTPS-RR request for %s", data->conn->host.name);
+                    async_thrdd_rr_done, async, NULL);
+  CURL_TRC_DNS(data, "[HTTPS-RR] initiated request for %s",
+               rrname ? rrname : async->hostname);
   return CURLE_OK;
 }
 #endif
 
-/*
- * async_thrdd_init() starts a new thread that performs the actual
- * resolve. This function returns before the resolve is done.
- *
- * Returns FALSE in case of failure, otherwise TRUE.
- */
-static bool async_thrdd_init(struct Curl_easy *data,
-                             const char *hostname, int port, int ip_version,
-                             const struct addrinfo *hints)
+void Curl_async_thrdd_shutdown(struct Curl_easy *data,
+                               struct Curl_resolv_async *async)
 {
-  struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
-  struct async_thrdd_addr_ctx *addr_ctx;
+  Curl_async_thrdd_destroy(data, async);
+}
 
-  /* !checksrc! disable ERRNOVAR 1 */
-  int err = ENOMEM;
+struct async_thrdd_match_ctx {
+  uint32_t mid;
+  uint32_t resolv_id;
+};
 
-  if(thrdd->addr
+static bool async_thrdd_match_item(void *qitem, void *match_data)
+{
+  const struct async_thrdd_match_ctx *ctx = match_data;
+  struct async_thrdd_item *item = qitem;
+  return (item->mid == ctx->mid) && (item->resolv_id == ctx->resolv_id);
+}
+
+void Curl_async_thrdd_destroy(struct Curl_easy *data,
+                              struct Curl_resolv_async *async)
+{
+  (void)data;
+  if(async->queries_ongoing && !async->done &&
+     data->multi && data->multi->resolv_thrdq) {
+    /* Remove any resolve items still queued */
+    struct async_thrdd_match_ctx mctx;
+    mctx.mid = data->mid;
+    mctx.resolv_id = async->id;
+    Curl_thrdq_clear(data->multi->resolv_thrdq,
+                     async_thrdd_match_item, &mctx);
+  }
 #ifdef USE_HTTPSRR_ARES
-     || thrdd->rr.channel
-#endif
-     ) {
-    CURL_TRC_DNS(data, "starting new resolve, with previous not cleaned up");
-    async_thrdd_destroy(data);
-    DEBUGASSERT(!thrdd->addr);
-#ifdef USE_HTTPSRR_ARES
-    DEBUGASSERT(!thrdd->rr.channel);
-#endif
+  if(async->thrdd.rr.channel) {
+    ares_destroy(async->thrdd.rr.channel);
+    async->thrdd.rr.channel = NULL;
   }
-
-  data->state.async.dns = NULL;
-  data->state.async.done = FALSE;
-  data->state.async.port = port;
-  data->state.async.ip_version = ip_version;
-  curlx_free(data->state.async.hostname);
-  data->state.async.hostname = curlx_strdup(hostname);
-  if(!data->state.async.hostname)
-    goto err_exit;
-
-  addr_ctx = addr_ctx_create(data, hostname, port, hints);
-  if(!addr_ctx)
-    goto err_exit;
-  thrdd->addr = addr_ctx;
-
-  /* passing addr_ctx to the thread adds a reference */
-  addr_ctx->ref_count = 2;
-  addr_ctx->start = *Curl_pgrs_now(data);
-
-#ifdef HAVE_GETADDRINFO
-  addr_ctx->thread_hnd = Curl_thread_create(getaddrinfo_thread, addr_ctx);
-#else
-  addr_ctx->thread_hnd = Curl_thread_create(gethostbyname_thread, addr_ctx);
+  Curl_httpsrr_cleanup(&async->thrdd.rr.hinfo);
 #endif
-
-  if(addr_ctx->thread_hnd == curl_thread_t_null) {
-    /* The thread never started */
-    addr_ctx->ref_count = 1;
-    addr_ctx->thrd_done = TRUE;
-    err = errno;
-    goto err_exit;
-  }
-
-#ifdef USE_HTTPSRR_ARES
-  if(async_rr_start(data, port))
-    infof(data, "Failed HTTPS RR operation");
-#endif
-  CURL_TRC_DNS(data, "resolve thread started for of %s:%d", hostname, port);
-  return TRUE;
-
-err_exit:
-  CURL_TRC_DNS(data, "resolve thread failed init: %d", err);
-  async_thrdd_destroy(data);
-  errno = err;
-  return FALSE;
-}
-
-static void async_thrdd_shutdown(struct Curl_easy *data)
-{
-  struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
-  struct async_thrdd_addr_ctx *addr_ctx = thrdd->addr;
-  curl_bit done;
-
-  if(!addr_ctx)
-    return;
-  if(addr_ctx->thread_hnd == curl_thread_t_null)
-    return;
-
-  Curl_mutex_acquire(&addr_ctx->mutx);
-#ifndef CURL_DISABLE_SOCKETPAIR
-  if(!addr_ctx->do_abort)
-    Curl_multi_will_close(data, addr_ctx->sock_pair[0]);
-#endif
-  addr_ctx->do_abort = TRUE;
-  done = addr_ctx->thrd_done;
-  Curl_mutex_release(&addr_ctx->mutx);
-
-  /* Wait for the thread to terminate if it is already marked done. If it is
-     not done yet we cannot do anything here. We had tried pthread_cancel but
-     it caused hanging and resource leaks (#18532). */
-  if(done && (addr_ctx->thread_hnd != curl_thread_t_null)) {
-    Curl_thread_join(&addr_ctx->thread_hnd);
-    CURL_TRC_DNS(data, "async_thrdd_shutdown, thread joined");
-  }
+  async_thrdd_item_destroy(async->thrdd.res_A);
+  async->thrdd.res_A = NULL;
+  async_thrdd_item_destroy(async->thrdd.res_AAAA);
+  async->thrdd.res_AAAA = NULL;
 }
 
 /*
- * 'entry' may be NULL and then no data is returned
- */
-static CURLcode asyn_thrdd_await(struct Curl_easy *data,
-                                 struct async_thrdd_addr_ctx *addr_ctx,
-                                 struct Curl_dns_entry **entry)
-{
-  CURLcode result = CURLE_OK;
-
-  if(addr_ctx->thread_hnd != curl_thread_t_null) {
-    /* not interested in result? cancel, if still running... */
-    if(!entry)
-      async_thrdd_shutdown(data);
-
-    if(addr_ctx->thread_hnd != curl_thread_t_null) {
-      CURL_TRC_DNS(data, "resolve, wait for thread to finish");
-      if(!Curl_thread_join(&addr_ctx->thread_hnd)) {
-        DEBUGASSERT(0);
-      }
-    }
-
-    if(entry)
-      result = Curl_async_is_resolved(data, entry);
-  }
-
-  data->state.async.done = TRUE;
-  if(entry)
-    *entry = data->state.async.dns;
-
-  return result;
-}
-
-/*
- * Until we gain a way to signal the resolver threads to stop early, we must
- * wait for them and ignore their results.
- */
-void Curl_async_thrdd_shutdown(struct Curl_easy *data)
-{
-  async_thrdd_shutdown(data);
-}
-
-void Curl_async_thrdd_destroy(struct Curl_easy *data)
-{
-  struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
-
-  if(thrdd->addr && !data->set.quick_exit) {
-    (void)asyn_thrdd_await(data, thrdd->addr, NULL);
-  }
-  async_thrdd_destroy(data);
-}
-
-/*
- * Curl_async_await()
- *
  * Waits for a resolve to finish. This function should be avoided since using
  * this risk getting the multi interface to "hang".
- *
- * If 'entry' is non-NULL, make it point to the resolved dns entry
- *
- * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
- * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
- *
- * This is the version for resolves-in-a-thread.
  */
-CURLcode Curl_async_await(struct Curl_easy *data,
-                          struct Curl_dns_entry **dns)
+CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id,
+                          struct Curl_dns_entry **pdns)
 {
-  struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
-  if(thrdd->addr)
-    return asyn_thrdd_await(data, thrdd->addr, dns);
-  return CURLE_FAILED_INIT;
-}
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
+  struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL;
+  timediff_t milli, ms;
 
-/*
- * Curl_async_is_resolved() is called repeatedly to check if a previous
- * name resolve request has completed. It should also make sure to time-out if
- * the operation seems to take too long.
- */
-CURLcode Curl_async_is_resolved(struct Curl_easy *data,
-                                struct Curl_dns_entry **dns)
-{
-  struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
-  curl_bit done = FALSE;
-
-  DEBUGASSERT(dns);
-  *dns = NULL;
-
-  if(data->state.async.done) {
-    *dns = data->state.async.dns;
-    CURL_TRC_DNS(data, "threaded: is_resolved(), already done, dns=%sfound",
-                 *dns ? "" : "not ");
-    return CURLE_OK;
-  }
-
-#ifdef USE_HTTPSRR_ARES
-  /* best effort, ignore errors */
-  if(thrdd->rr.channel)
-    (void)Curl_ares_perform(thrdd->rr.channel, 0);
-#endif
-
-  DEBUGASSERT(thrdd->addr);
-  if(!thrdd->addr)
+  if(!thrdd)
     return CURLE_FAILED_INIT;
 
-  Curl_mutex_acquire(&thrdd->addr->mutx);
-  done = thrdd->addr->thrd_done;
-  Curl_mutex_release(&thrdd->addr->mutx);
+  while(async->queries_ongoing && !async->done) {
+    Curl_async_thrdd_multi_process(data->multi);
+    if(async->done)
+      break;
 
-  if(done) {
-    CURLcode result = CURLE_OK;
-
-    data->state.async.done = TRUE;
-    Curl_resolv_unlink(data, &data->state.async.dns);
-    Curl_expire_done(data, EXPIRE_ASYNC_NAME);
-
-    if(thrdd->addr->res) {
-      data->state.async.dns =
-        Curl_dnscache_mk_entry(data, &thrdd->addr->res,
-                               data->state.async.hostname, 0,
-                               data->state.async.port, FALSE);
-      if(!data->state.async.dns)
-        result = CURLE_OUT_OF_MEMORY;
-
-#ifdef USE_HTTPSRR_ARES
-      if(thrdd->rr.channel) {
-        result = thrdd->rr.result;
-        if(!result) {
-          struct Curl_https_rrinfo *lhrr;
-          lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo);
-          if(!lhrr)
-            result = CURLE_OUT_OF_MEMORY;
-          else
-            data->state.async.dns->hinfo = lhrr;
-        }
-      }
-#endif
-      if(!result && data->state.async.dns)
-        result = Curl_dnscache_add(data, data->state.async.dns);
-    }
-
-    if(!result && !data->state.async.dns)
-      result = Curl_resolver_error(data, NULL);
-    if(result)
-      Curl_resolv_unlink(data, &data->state.async.dns);
-    *dns = data->state.async.dns;
-    CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound",
-                 result, *dns ? "" : "not ");
-    async_thrdd_shutdown(data);
-    return result;
-  }
-  else {
-    /* poll for name lookup done with exponential backoff up to 250ms */
-    /* should be fine even if this converts to 32-bit */
-    timediff_t elapsed = curlx_ptimediff_ms(Curl_pgrs_now(data),
-                                            &data->progress.t_startsingle);
-    if(elapsed < 0)
-      elapsed = 0;
-
-    if(thrdd->addr->poll_interval == 0)
-      /* Start at 1ms poll interval */
-      thrdd->addr->poll_interval = 1;
-    else if(elapsed >= thrdd->addr->interval_end)
-      /* Back-off exponentially if last interval expired */
-      thrdd->addr->poll_interval *= 2;
-
-    if(thrdd->addr->poll_interval > 250)
-      thrdd->addr->poll_interval = 250;
-
-    thrdd->addr->interval_end = elapsed + thrdd->addr->poll_interval;
-    Curl_expire(data, thrdd->addr->poll_interval, EXPIRE_ASYNC_NAME);
-    return CURLE_OK;
-  }
-}
-
-CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps)
-{
-  struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
-  CURLcode result = CURLE_OK;
-  curl_bit thrd_done;
-
-#if !defined(USE_HTTPSRR_ARES) && defined(CURL_DISABLE_SOCKETPAIR)
-  (void)ps;
-#endif
-
-#ifdef USE_HTTPSRR_ARES
-  if(thrdd->rr.channel) {
-    result = Curl_ares_pollset(data, thrdd->rr.channel, ps);
-    if(result)
-      return result;
-  }
-#endif
-  if(!thrdd->addr)
-    return result;
-
-  Curl_mutex_acquire(&thrdd->addr->mutx);
-  thrd_done = thrdd->addr->thrd_done;
-  Curl_mutex_release(&thrdd->addr->mutx);
-
-  if(!thrd_done) {
-#ifndef CURL_DISABLE_SOCKETPAIR
-    /* return read fd to client for polling the DNS resolution status */
-    result = Curl_pollset_add_in(data, ps, thrdd->addr->sock_pair[0]);
-#else
-    timediff_t milli;
-    timediff_t ms =
-      curlx_ptimediff_ms(Curl_pgrs_now(data), &thrdd->addr->start);
+    ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start);
     if(ms < 3)
       milli = 0;
     else if(ms <= 50)
@@ -701,68 +323,506 @@
       milli = 50;
     else
       milli = 200;
-    Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
+    CURL_TRC_DNS(data, "await, waiting %" FMT_TIMEDIFF_T "ms", milli);
+    curlx_wait_ms(milli);
+  }
+  return Curl_async_take_result(data, async, pdns);
+}
+
+#ifdef HAVE_GETADDRINFO
+
+/* Process the item, using Curl_getaddrinfo_ex() */
+static void async_thrdd_item_process(void *arg)
+{
+  struct async_thrdd_item *item = arg;
+  struct addrinfo hints;
+  char service[12];
+  int pf = PF_INET;
+  int rc;
+
+#ifdef DEBUGBUILD
+  if(item->delay_ms) {
+    curlx_wait_ms(item->delay_ms);
+  }
+  if(item->delay_fail_ms) {
+    curlx_wait_ms(item->delay_fail_ms);
+    return;
+  }
 #endif
+
+  memset(&hints, 0, sizeof(hints));
+#ifdef CURLRES_IPV6
+  if(item->dns_queries & CURL_DNSQ_AAAA) {
+    pf = (item->dns_queries & CURL_DNSQ_A) ? PF_UNSPEC : PF_INET6;
+  }
+#endif
+  hints.ai_family = pf;
+  hints.ai_socktype = Curl_socktype_for_transport(item->transport);
+  hints.ai_protocol = Curl_protocol_for_transport(item->transport);
+#ifdef __APPLE__
+  /* If we leave `ai_flags == 0` then macOS is looking for IPV4MAPPED
+   * when doing AAAA queries. We do not want this "help". */
+  hints.ai_flags = AI_ADDRCONFIG;
+#endif
+
+  curl_msnprintf(service, sizeof(service), "%u", item->port);
+#ifdef AI_NUMERICSERV
+  hints.ai_flags |= AI_NUMERICSERV;
+#endif
+
+  rc = Curl_getaddrinfo_ex(item->hostname, service, &hints, &item->res);
+  if(rc) {
+    item->sock_error = SOCKERRNO ? SOCKERRNO : rc;
+    if(item->sock_error == 0)
+      item->sock_error = RESOLVER_ENOMEM;
+  }
+  else {
+    Curl_addrinfo_set_port(item->res, item->port);
+  }
+}
+
+#else /* HAVE_GETADDRINFO */
+
+/* Process the item, using Curl_ipv4_resolve_r() */
+static void async_thrdd_item_process(void *arg)
+{
+  struct async_thrdd_item *item = arg;
+
+#ifdef DEBUGBUILD
+  if(item->delay_ms) {
+    curlx_wait_ms(item->delay_ms);
+  }
+  if(item->delay_fail_ms) {
+    curlx_wait_ms(item->delay_fail_ms);
+    return;
+  }
+#endif
+  item->res = Curl_ipv4_resolve_r(item->hostname, item->port);
+  if(!item->res) {
+    item->sock_error = SOCKERRNO;
+    if(item->sock_error == 0)
+      item->sock_error = RESOLVER_ENOMEM;
+  }
+}
+
+#endif /* HAVE_GETADDRINFO */
+
+#ifdef ENABLE_WAKEUP
+static void async_thrdd_event(const struct curl_thrdq *tqueue,
+                              Curl_thrdq_event ev,
+                              void *user_data)
+{
+  struct Curl_multi *multi = user_data;
+  (void)tqueue;
+  switch(ev) {
+  case CURL_THRDQ_EV_ITEM_DONE:
+    (void)curl_multi_wakeup(multi);
+    break;
+  default:
+    break;
+  }
+}
+#else
+#define async_thrdd_event   NULL
+#endif
+
+static void async_thrdd_item_free(void *item)
+{
+  async_thrdd_item_destroy(item);
+}
+
+/* Create a thread queue for processing resolv items */
+CURLcode Curl_async_thrdd_multi_init(struct Curl_multi *multi,
+                                     uint32_t min_threads,
+                                     uint32_t max_threads,
+                                     uint32_t idle_time_ms)
+{
+  CURLcode result;
+  DEBUGASSERT(!multi->resolv_thrdq);
+  result = Curl_thrdq_create(&multi->resolv_thrdq, "DNS", 0,
+                             min_threads, max_threads, idle_time_ms,
+                             async_thrdd_item_free,
+                             async_thrdd_item_process,
+                             async_thrdd_event,
+                             multi);
+#ifdef DEBUGBUILD
+  if(!result) {
+    const char *p = getenv("CURL_DBG_RESOLV_MAX_THREADS");
+    if(p) {
+      curl_off_t l;
+      if(!curlx_str_number(&p, &l, UINT32_MAX)) {
+        result = Curl_async_thrdd_multi_set_props(
+          multi, min_threads, (uint32_t)l, idle_time_ms);
+      }
+    }
+  }
+#endif
+  return result;
+}
+
+/* Tear down the thread queue, joining active threads or detaching them */
+void Curl_async_thrdd_multi_destroy(struct Curl_multi *multi, bool join)
+{
+  if(multi->resolv_thrdq) {
+#ifdef CURLVERBOSE
+    CURL_TRC_DNS(multi->admin, "destroy thread queue+pool, join=%d", join);
+    Curl_thrdq_trace(multi->resolv_thrdq, multi->admin);
+#endif
+    Curl_thrdq_destroy(multi->resolv_thrdq, join);
+    multi->resolv_thrdq = NULL;
+  }
+}
+
+#ifdef CURLVERBOSE
+static void async_thrdd_report_item(struct Curl_easy *data,
+                                    struct async_thrdd_item *item)
+{
+  char buf[MAX_IPADR_LEN];
+  struct dynbuf tmp;
+  const char *sep = "";
+  const struct Curl_addrinfo *ai = item->res;
+  int ai_family = (item->dns_queries & CURL_DNSQ_AAAA) ? AF_INET6 : AF_INET;
+  CURLcode result;
+
+  if(!CURL_TRC_DNS_is_verbose(data))
+    return;
+
+  curlx_dyn_init(&tmp, 1024);
+  for(; ai; ai = ai->ai_next) {
+    if(ai->ai_family == ai_family) {
+      Curl_printable_address(ai, buf, sizeof(buf));
+      result = curlx_dyn_addf(&tmp, "%s%s", sep, buf);
+      if(result) {
+        CURL_TRC_DNS(data, "too many IP, cannot show");
+        goto out;
+      }
+      sep = ", ";
+    }
+  }
+
+  CURL_TRC_DNS(data, "Host %s:%u resolved IPv%c: %s",
+               item->hostname, item->port,
+               (item->dns_queries & CURL_DNSQ_AAAA) ? '6' : '4',
+               (curlx_dyn_len(&tmp) ? curlx_dyn_ptr(&tmp) : "(none)"));
+out:
+  curlx_dyn_free(&tmp);
+}
+#endif /* CURLVERBOSE */
+
+/* Process the receiving end of the thread queue, dispatching
+ * processed items to their transfer when it can still be found
+ * and has an `async` state present. Otherwise, destroy the item. */
+void Curl_async_thrdd_multi_process(struct Curl_multi *multi)
+{
+  struct Curl_easy *data;
+  void *qitem;
+
+  while(!Curl_thrdq_recv(multi->resolv_thrdq, &qitem)) {
+    /* dispatch resolve result */
+    struct async_thrdd_item *item = qitem;
+    struct Curl_resolv_async *async = NULL;
+
+    data = Curl_multi_get_easy(multi, item->mid);
+    if(data)
+      async = Curl_async_get(data, item->resolv_id);
+    if(async) {
+      struct async_thrdd_item **pdest = &async->thrdd.res_A;
+
+      async->dns_responses |= item->dns_queries;
+      --async->queries_ongoing;
+      async->done = !async->queries_ongoing;
+
+#ifdef CURLRES_IPV6
+      if(item->dns_queries & CURL_DNSQ_AAAA)
+        pdest = &async->thrdd.res_AAAA;
+#endif
+      if(!*pdest) {
+        VERBOSE(async_thrdd_report_item(data, item));
+        *pdest = item;
+        item = NULL;
+      }
+      else
+        DEBUGASSERT(0); /* should not receive duplicates here */
+      Curl_multi_mark_dirty(data);
+    }
+    async_thrdd_item_free(item);
+  }
+#ifdef CURLVERBOSE
+  Curl_thrdq_trace(multi->resolv_thrdq, multi->admin);
+#endif
+}
+
+CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi,
+                                          uint32_t min_threads,
+                                          uint32_t max_threads,
+                                          uint32_t idle_time_ms)
+{
+  return Curl_thrdq_set_props(multi->resolv_thrdq, 0,
+                              min_threads, max_threads, idle_time_ms);
+}
+
+static CURLcode async_thrdd_query(struct Curl_easy *data,
+                                  struct Curl_resolv_async *async,
+                                  uint8_t dns_queries)
+{
+  struct async_thrdd_item *item;
+  CURLcode result;
+
+  item = async_thrdd_item_create(data, async->id, dns_queries,
+                                 async->hostname, async->port,
+                                 async->transport);
+  if(!item) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  CURL_TRC_DNS(data, "queueing query %s", item->description);
+  result = Curl_thrdq_send(data->multi->resolv_thrdq, item,
+                           async_item_description(item), async->timeout_ms);
+  if(result)
+    goto out;
+  item = NULL;
+  async->queries_ongoing++;
+
+out:
+  if(item)
+    async_thrdd_item_free(item);
+  return result;
+}
+
+CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
+                                struct Curl_resolv_async *async)
+{
+  CURLcode result = CURLE_FAILED_INIT;
+  void *resolver = NULL;
+
+  if(async->queries_ongoing || async->done)
+    return CURLE_FAILED_INIT;
+
+#ifdef USE_HTTPSRR_ARES
+  DEBUGASSERT(!async->thrdd.rr.channel);
+  if((async->dns_queries & CURL_DNSQ_HTTPS) && !async->is_ipaddr) {
+    result = async_rr_start(data, async);
+    if(result)
+      goto out;
+    resolver = async->thrdd.rr.channel;
+  }
+#endif
+
+  result = Curl_resolv_announce_start(data, resolver);
+  if(result)
+    return result;
+
+#ifdef CURLRES_IPV6
+  /* Do not start an AAAA query for an ipv4 address when
+   * we will start an A query for it. */
+  if((async->dns_queries & CURL_DNSQ_AAAA) &&
+     !(async->is_ipv4addr && (async->dns_queries & CURL_DNSQ_A))) {
+    result = async_thrdd_query(data, async, CURL_DNSQ_AAAA);
+    if(result)
+      goto out;
+  }
+#endif
+  if(async->dns_queries & CURL_DNSQ_A) {
+    result = async_thrdd_query(data, async, CURL_DNSQ_A);
+    if(result)
+      goto out;
+  }
+
+#ifdef CURLVERBOSE
+  Curl_thrdq_trace(data->multi->resolv_thrdq, data);
+#endif
+
+out:
+  if(result)
+    CURL_TRC_DNS(data, "error queueing query %s:%d -> %d",
+                 async->hostname, async->port, result);
+  return result;
+}
+
+CURLcode Curl_async_pollset(struct Curl_easy *data,
+                            struct Curl_resolv_async *async,
+                            struct easy_pollset *ps)
+{
+  timediff_t timeout_ms;
+
+  timeout_ms = Curl_async_timeleft_ms(data, async);
+#ifdef USE_HTTPSRR_ARES
+  if(async->thrdd.rr.channel) {
+    CURLcode result = Curl_ares_pollset(data, async->thrdd.rr.channel, ps);
+    if(result)
+      return result;
+    timeout_ms = Curl_ares_timeout_ms(data, async, async->thrdd.rr.channel);
+  }
+#else
+  (void)ps;
+#endif
+
+  if(!async->done) {
+#ifndef ENABLE_WAKEUP
+    timediff_t stutter_ms, elapsed_ms;
+    elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start);
+    if(elapsed_ms < 3)
+      stutter_ms = 1;
+    else if(elapsed_ms <= 50)
+      stutter_ms = elapsed_ms / 3;
+    else if(elapsed_ms <= 250)
+      stutter_ms = 50;
+    else
+      stutter_ms = 200;
+    timeout_ms = CURLMIN(stutter_ms, timeout_ms);
+#endif
+    Curl_expire(data, timeout_ms, EXPIRE_ASYNC_NAME);
+  }
+  return CURLE_OK;
+}
+
+/*
+ * Curl_async_take_result() is called repeatedly to check if a previous
+ * name resolve request has completed. It should also make sure to time-out if
+ * the operation seems to take too long.
+ */
+CURLcode Curl_async_take_result(struct Curl_easy *data,
+                                struct Curl_resolv_async *async,
+                                struct Curl_dns_entry **pdns)
+{
+  struct async_thrdd_ctx *thrdd = &async->thrdd;
+  struct Curl_dns_entry *dns = NULL;
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(pdns);
+  *pdns = NULL;
+  if(!async->queries_ongoing && !async->done) {
+    DEBUGASSERT(0);
+    return CURLE_FAILED_INIT;
+  }
+
+#ifdef USE_HTTPSRR_ARES
+  /* best effort, ignore errors */
+  if(thrdd->rr.channel)
+    (void)Curl_ares_perform(thrdd->rr.channel, 0);
+#endif
+
+  if(!async->done)
+    return CURLE_AGAIN;
+
+  Curl_expire_done(data, EXPIRE_ASYNC_NAME);
+  if(async->result) {
+    result = async->result;
+    goto out;
+  }
+
+  if((thrdd->res_A && thrdd->res_A->res) ||
+     (thrdd->res_AAAA && thrdd->res_AAAA->res)) {
+    dns = Curl_dnscache_mk_entry2(
+      data, async->dns_queries,
+      thrdd->res_A ? &thrdd->res_A->res : NULL,
+      thrdd->res_AAAA ? &thrdd->res_AAAA->res : NULL,
+      async->hostname, async->port);
+    if(!dns) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto out;
+    }
+
+#ifdef USE_HTTPSRR_ARES
+    if(thrdd->rr.channel) {
+      struct Curl_https_rrinfo *lhrr = NULL;
+      if(thrdd->rr.hinfo.complete) {
+        lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo);
+        if(!lhrr) {
+          result = CURLE_OUT_OF_MEMORY;
+          goto out;
+        }
+      }
+      Curl_httpsrr_trace(data, lhrr);
+      Curl_dns_entry_set_https_rr(dns, lhrr);
+    }
+#endif
+  }
+
+  if(dns) {
+    *pdns = dns;
+    dns = NULL;
+  }
+#ifdef CURLVERBOSE
+  Curl_thrdq_trace(data->multi->resolv_thrdq, data);
+#endif
+
+out:
+  Curl_dns_entry_unlink(data, &dns);
+  Curl_async_thrdd_shutdown(data, async);
+  if(!result && !*pdns)
+    result = Curl_async_failed(data, async, NULL);
+  if(result &&
+     (result != CURLE_COULDNT_RESOLVE_HOST) &&
+     (result != CURLE_COULDNT_RESOLVE_PROXY)) {
+    CURL_TRC_DNS(data, "Error %d resolving %s:%d",
+                 result, async->hostname, async->port);
   }
   return result;
 }
 
-#ifndef HAVE_GETADDRINFO
-/*
- * Curl_async_getaddrinfo() - for platforms without getaddrinfo
- */
-CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname,
-                                int port, int ip_version)
+static const struct Curl_addrinfo *async_thrdd_get_ai(
+  const struct Curl_addrinfo *ai,
+  int ai_family, unsigned int index)
 {
-  (void)ip_version;
-
-  /* fire up a new resolver thread! */
-  if(async_thrdd_init(data, hostname, port, ip_version, NULL)) {
-    return CURLE_OK;
+  unsigned int i = 0;
+  for(i = 0; ai; ai = ai->ai_next) {
+    if(ai->ai_family == ai_family) {
+      if(i == index)
+        return ai;
+      ++i;
+    }
   }
-
-  failf(data, "getaddrinfo() thread failed");
-  return CURLE_FAILED_INIT;
+  return NULL;
 }
 
-#else /* !HAVE_GETADDRINFO */
-
-/*
- * Curl_async_getaddrinfo() - for getaddrinfo
- */
-CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname,
-                                int port, int ip_version)
+const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data,
+                                              struct Curl_resolv_async *async,
+                                              int ai_family,
+                                              unsigned int index)
 {
-  struct addrinfo hints;
-  int pf = PF_INET;
+  struct async_thrdd_ctx *thrdd = &async->thrdd;
 
-  CURL_TRC_DNS(data, "init threaded resolve of %s:%d", hostname, port);
-#ifdef CURLRES_IPV6
-  if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
-    /* The stack seems to be IPv6-enabled */
-    if(ip_version == CURL_IPRESOLVE_V6)
-      pf = PF_INET6;
-    else
-      pf = PF_UNSPEC;
+  (void)data;
+  switch(ai_family) {
+  case AF_INET:
+    if(thrdd->res_A)
+      return async_thrdd_get_ai(thrdd->res_A->res, ai_family, index);
+    break;
+  case AF_INET6:
+    if(thrdd->res_AAAA)
+      return async_thrdd_get_ai(thrdd->res_AAAA->res, ai_family, index);
+    break;
+  default:
+    break;
   }
+  return NULL;
+}
+
+#ifdef USE_HTTPSRR
+const struct Curl_https_rrinfo *Curl_async_get_https(
+  struct Curl_easy *data,
+  struct Curl_resolv_async *async)
+{
+#ifdef USE_HTTPSRR_ARES
+  if(Curl_async_knows_https(data, async))
+    return &async->thrdd.rr.hinfo;
 #else
-  (void)ip_version;
-#endif /* CURLRES_IPV6 */
-
-  memset(&hints, 0, sizeof(hints));
-  hints.ai_family = pf;
-  hints.ai_socktype =
-    (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ?
-    SOCK_STREAM : SOCK_DGRAM;
-
-  /* fire up a new resolver thread! */
-  if(async_thrdd_init(data, hostname, port, ip_version, &hints))
-    return CURLE_OK;
-
-  failf(data, "getaddrinfo() thread failed to start");
-  return CURLE_FAILED_INIT;
+  (void)data;
+  (void)async;
+#endif
+  return NULL;
 }
 
-#endif /* !HAVE_GETADDRINFO */
+bool Curl_async_knows_https(struct Curl_easy *data,
+                            struct Curl_resolv_async *async)
+{
+  (void)data;
+  if(async->dns_queries & CURL_DNSQ_HTTPS)
+    return ((async->dns_responses & CURL_DNSQ_HTTPS) || async->done);
+  return TRUE; /* we know it will never come */
+}
 
-#endif /* CURLRES_THREADED */
+#endif /* USE_HTTPSRR */
+
+#endif /* USE_RESOLV_THREADED */
diff --git a/Utilities/cmcurl/lib/asyn.h b/Utilities/cmcurl/lib/asyn.h
index 68628f9..ed50933 100644
--- a/Utilities/cmcurl/lib/asyn.h
+++ b/Utilities/cmcurl/lib/asyn.h
@@ -31,6 +31,9 @@
 
 struct Curl_easy;
 struct Curl_dns_entry;
+struct Curl_resolv_async;
+struct Curl_multi;
+struct easy_pollset;
 
 #ifdef CURLRES_ASYNCH
 
@@ -40,8 +43,8 @@
 struct connectdata;
 struct easy_pollset;
 
-#if defined(CURLRES_ARES) && defined(CURLRES_THREADED)
-#error cannot have both CURLRES_ARES and CURLRES_THREADED defined
+#if defined(USE_RESOLV_ARES) && defined(USE_RESOLV_THREADED)
+#error cannot have both USE_RESOLV_ARES and USE_RESOLV_THREADED defined
 #endif
 
 /*
@@ -66,49 +69,6 @@
 void Curl_async_global_cleanup(void);
 
 /*
- * Curl_async_get_impl()
- * Get the resolver implementation instance (c-ares channel) or NULL
- * for passing to application callback.
- */
-CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl);
-
-/* Curl_async_pollset()
- *
- * This function is called from the Curl_multi_pollset() function.  'sock' is a
- * pointer to an array to hold the file descriptors, with 'numsock' being the
- * size of that array (in number of entries). This function is supposed to
- * return bitmask indicating what file descriptors (referring to array indexes
- * in the 'sock' array) to wait for, read/write.
- */
-CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps);
-
-/*
- * Curl_async_is_resolved()
- *
- * Called repeatedly to check if a previous name resolve request has
- * completed. It should also make sure to time-out if the operation seems to
- * take too long.
- *
- * Returns normal CURLcode errors.
- */
-CURLcode Curl_async_is_resolved(struct Curl_easy *data,
-                                struct Curl_dns_entry **dns);
-
-/*
- * Curl_async_await()
- *
- * Waits for a resolve to finish. This function should be avoided since using
- * this risk getting the multi interface to "hang".
- *
- * On return 'entry' is assigned the resolved dns (CURLE_OK or NULL otherwise.
- *
- * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
- * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
- */
-CURLcode Curl_async_await(struct Curl_easy *data,
-                          struct Curl_dns_entry **dns);
-
-/*
  * Curl_async_getaddrinfo() - when using this resolver
  *
  * Returns name information about the given hostname and port number. If
@@ -119,8 +79,21 @@
  * Each resolver backend must of course make sure to return data in the
  * correct format to comply with this.
  */
-CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, const char *hostname,
-                                int port, int ip_version);
+CURLcode Curl_async_getaddrinfo(struct Curl_easy *data,
+                                struct Curl_resolv_async *async);
+
+const struct Curl_addrinfo *Curl_async_get_ai(struct Curl_easy *data,
+                                              struct Curl_resolv_async *async,
+                                              int ai_family,
+                                              unsigned int index);
+
+#ifdef USE_HTTPSRR
+const struct Curl_https_rrinfo *Curl_async_get_https(
+  struct Curl_easy *data,
+  struct Curl_resolv_async *async);
+bool Curl_async_knows_https(struct Curl_easy *data,
+                            struct Curl_resolv_async *async);
+#endif /* USE_HTTPSRR */
 
 #ifdef USE_ARES
 /* common functions for c-ares and threaded resolver with HTTPSRR */
@@ -130,105 +103,119 @@
                            ares_channel channel,
                            struct easy_pollset *ps);
 
+timediff_t Curl_ares_timeout_ms(struct Curl_easy *data,
+                                struct Curl_resolv_async *async,
+                                ares_channel channel);
+
 int Curl_ares_perform(ares_channel channel, timediff_t timeout_ms);
 #endif
 
-#ifdef CURLRES_ARES
+#ifdef USE_RESOLV_ARES
 /* async resolving implementation using c-ares alone */
 struct async_ares_ctx {
   ares_channel channel;
-  int num_pending;               /* number of outstanding c-ares requests */
-  struct Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares
-                                    parts */
+  struct Curl_addrinfo *res_A;
+  struct Curl_addrinfo *res_AAAA;
   int ares_status;               /* ARES_SUCCESS, ARES_ENOTFOUND, etc. */
   CURLcode result;               /* CURLE_OK or error handling response */
-#ifndef HAVE_CARES_GETADDRINFO
   struct curltime happy_eyeballs_dns_time; /* when this timer started, or 0 */
-#endif
 #ifdef USE_HTTPSRR
   struct Curl_https_rrinfo hinfo;
 #endif
 };
 
-void Curl_async_ares_shutdown(struct Curl_easy *data);
-void Curl_async_ares_destroy(struct Curl_easy *data);
+void Curl_async_ares_shutdown(struct Curl_easy *data,
+                              struct Curl_resolv_async *async);
+void Curl_async_ares_destroy(struct Curl_easy *data,
+                             struct Curl_resolv_async *async);
 
-/* Set the DNS server to use by ares, from `data` settings. */
-CURLcode Curl_async_ares_set_dns_servers(struct Curl_easy *data);
+#endif /* USE_RESOLV_ARES */
 
-/* Set the DNS interfacer to use by ares, from `data` settings. */
-CURLcode Curl_async_ares_set_dns_interface(struct Curl_easy *data);
+#ifdef USE_RESOLV_THREADED
 
-/* Set the local ipv4 address to use by ares, from `data` settings. */
-CURLcode Curl_async_ares_set_dns_local_ip4(struct Curl_easy *data);
-
-/* Set the local ipv6 address to use by ares, from `data` settings. */
-CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data);
-
-#endif /* CURLRES_ARES */
-
-#ifdef CURLRES_THREADED
-/* async resolving implementation using POSIX threads */
-#include "curl_threads.h"
-
-/* Context for threaded address resolver */
-struct async_thrdd_addr_ctx {
-  curl_thread_t thread_hnd;
-  char *hostname;        /* hostname to resolve, Curl_async.hostname
-                            duplicate */
-  curl_mutex_t mutx;
-#ifndef CURL_DISABLE_SOCKETPAIR
-  curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */
-#endif
-  struct Curl_addrinfo *res;
-#ifdef HAVE_GETADDRINFO
-  struct addrinfo hints;
-#endif
-  struct curltime start;
-  timediff_t interval_end;
-  unsigned int poll_interval;
-  int port;
-  int sock_error;
-  int ref_count;
-  BIT(thrd_done);
-  BIT(do_abort);
-};
+struct async_thrdd_item;
 
 /* Context for threaded resolver */
 struct async_thrdd_ctx {
-  /* `addr` is a pointer since this memory is shared with a started
-   * thread. Since threads cannot be killed, we use reference counting
-   * so that we can "release" our pointer to this memory while the
-   * thread is still running. */
-  struct async_thrdd_addr_ctx *addr;
+  struct async_thrdd_item *res_A; /* ipv4 result */
+  struct async_thrdd_item *res_AAAA; /* ipv6 result */
 #if defined(USE_HTTPSRR) && defined(USE_ARES)
   struct {
     ares_channel channel;
     struct Curl_https_rrinfo hinfo;
-    CURLcode result;
-    BIT(done);
   } rr;
 #endif
 };
 
-void Curl_async_thrdd_shutdown(struct Curl_easy *data);
-void Curl_async_thrdd_destroy(struct Curl_easy *data);
+void Curl_async_thrdd_shutdown(struct Curl_easy *data,
+                               struct Curl_resolv_async *async);
+void Curl_async_thrdd_destroy(struct Curl_easy *data,
+                              struct Curl_resolv_async *async);
 
-#endif /* CURLRES_THREADED */
+CURLcode Curl_async_thrdd_multi_init(struct Curl_multi *multi,
+                                     uint32_t min_threads,
+                                     uint32_t max_threads,
+                                     uint32_t idle_time_ms);
+void Curl_async_thrdd_multi_destroy(struct Curl_multi *multi, bool join);
+void Curl_async_thrdd_multi_process(struct Curl_multi *multi);
+
+CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi,
+                                          uint32_t min_threads,
+                                          uint32_t max_threads,
+                                          uint32_t idle_time_ms);
+
+#endif /* USE_RESOLV_THREADED */
 
 #ifndef CURL_DISABLE_DOH
 struct doh_probes;
 #endif
 
+/*
+ * Curl_async_await()
+ *
+ * Waits for a resolve to finish. This function should be avoided since using
+ * this risk getting the multi interface to "hang".
+ *
+ * On return 'dns' is assigned the resolved dns (CURLE_OK or NULL otherwise.
+ *
+ * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
+ * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
+ */
+CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id,
+                          struct Curl_dns_entry **pdns);
+
+/*
+ * Take the result of an async resolve operation.
+ * Returns CURLE_OK with `*pdns` != NULL, CURLE_AGAIN while still
+ * ongoing or an error code for a failed resolve.
+ */
+CURLcode Curl_async_take_result(struct Curl_easy *data,
+                                struct Curl_resolv_async *async,
+                                struct Curl_dns_entry **pdns);
+
+/* Curl_async_pollset()
+ *
+ * This function is called from the Curl_multi_pollset() function.  'sock' is a
+ * pointer to an array to hold the file descriptors, with 'numsock' being the
+ * size of that array (in number of entries). This function is supposed to
+ * return bitmask indicating what file descriptors (referring to array indexes
+ * in the 'sock' array) to wait for, read/write.
+ */
+CURLcode Curl_async_pollset(struct Curl_easy *data,
+                            struct Curl_resolv_async *async,
+                            struct easy_pollset *ps);
+
 #else /* CURLRES_ASYNCH */
 
 /* convert these functions if an asynch resolver is not used */
-#define Curl_async_get_impl(x, y)    (*(y) = NULL, CURLE_OK)
-#define Curl_async_is_resolved(x, y) CURLE_COULDNT_RESOLVE_HOST
-#define Curl_async_await(x, y)       CURLE_COULDNT_RESOLVE_HOST
-#define Curl_async_global_init()     CURLE_OK
-#define Curl_async_global_cleanup()  Curl_nop_stmt
-
+#define Curl_async_global_init()        CURLE_OK
+#define Curl_async_global_cleanup()     Curl_nop_stmt
+#define Curl_async_get_ai(a, b, c, d)   NULL
+#define Curl_async_await(a, b, c)       CURLE_COULDNT_RESOLVE_HOST
+#define Curl_async_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST
+#define Curl_async_pollset(x, y, z)     CURLE_OK
+#define Curl_async_get_https(x, y)      NULL
+#define Curl_async_knows_https(x, y)    TRUE
 #endif /* !CURLRES_ASYNCH */
 
 #if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH)
@@ -236,38 +223,54 @@
 #endif
 
 #ifdef USE_CURL_ASYNC
-struct Curl_async {
-#ifdef CURLRES_ARES
+
+struct Curl_resolv_async {
+  struct Curl_resolv_async *next;
+#ifdef USE_RESOLV_ARES
   struct async_ares_ctx ares;
-#elif defined(CURLRES_THREADED)
+#elif defined(USE_RESOLV_THREADED)
   struct async_thrdd_ctx thrdd;
 #endif
 #ifndef CURL_DISABLE_DOH
   struct doh_probes *doh; /* DoH specific data for this request */
 #endif
-  struct Curl_dns_entry *dns; /* result of resolving on success */
-  char *hostname; /* copy of the params resolv started with */
-  int port;
-  int ip_version;
+  struct curltime start;
+  timediff_t interval_end;
+  timediff_t timeout_ms;
+  CURLcode result;
+  uint32_t poll_interval;
+  uint32_t id; /* unique id per easy handle of the resolve operation */
+  /* what is being resolved */
+  uint16_t port;
+  uint8_t dns_queries; /* what queries are being performed */
+  uint8_t dns_responses; /* what queries had responses so far. */
+  uint8_t transport;
+  uint8_t queries_ongoing;
+  BIT(is_ipaddr);
+  BIT(is_ipv4addr);
+  BIT(for_proxy);
   BIT(done);
+  BIT(shutdown);
+  char hostname[1];
 };
 
-/*
- * Curl_async_shutdown().
- *
- * This shuts down all ongoing operations.
- */
-void Curl_async_shutdown(struct Curl_easy *data);
+timediff_t Curl_async_timeleft_ms(struct Curl_easy *data,
+                                  struct Curl_resolv_async *async);
 
-/*
- * Curl_async_destroy().
- *
- * This frees the resources of any async resolve.
- */
-void Curl_async_destroy(struct Curl_easy *data);
+/* Shut down the given async resolve. */
+void Curl_async_shutdown(struct Curl_easy *data,
+                         struct Curl_resolv_async *async);
+
+/* Frees the resources of the given async resolve and the struct itself. */
+void Curl_async_destroy(struct Curl_easy *data,
+                        struct Curl_resolv_async *async);
+
+CURLcode Curl_async_failed(struct Curl_easy *data,
+                           struct Curl_resolv_async *async,
+                           const char *detail);
+
 #else /* !USE_CURL_ASYNC */
-#define Curl_async_shutdown(x) Curl_nop_stmt
-#define Curl_async_destroy(x)  Curl_nop_stmt
+#define Curl_async_shutdown(x, y) Curl_nop_stmt
 #endif /* USE_CURL_ASYNC */
 
 /********** end of generic resolver interface functions *****************/
diff --git a/Utilities/cmcurl/lib/bufq.h b/Utilities/cmcurl/lib/bufq.h
index 8dd186f..da411b5 100644
--- a/Utilities/cmcurl/lib/bufq.h
+++ b/Utilities/cmcurl/lib/bufq.h
@@ -132,8 +132,8 @@
                      size_t max_chunks, int opts);
 
 /**
- * Reset the buffer queue to be empty. Will keep any allocated buffer
- * chunks around.
+ * Reset the buffer queue to be empty. Keep any allocated buffer chunks
+ * around.
  */
 void Curl_bufq_reset(struct bufq *q);
 
@@ -243,7 +243,7 @@
 
 /**
  * Write buf to the end of the buffer queue.
- * Will write bufq content or passed `buf` directly using the `writer`
+ * 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.
diff --git a/Utilities/cmcurl/lib/cf-dns.c b/Utilities/cmcurl/lib/cf-dns.c
new file mode 100644
index 0000000..e763b8e
--- /dev/null
+++ b/Utilities/cmcurl/lib/cf-dns.c
@@ -0,0 +1,667 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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 "curl_addrinfo.h"
+#include "cfilters.h"
+#include "connect.h"
+#include "dnscache.h"
+#include "httpsrr.h"
+#include "curl_trc.h"
+#include "progress.h"
+#include "url.h"
+#include "cf-dns.h"
+
+
+struct cf_dns_ctx {
+  struct Curl_dns_entry *dns;
+  CURLcode resolv_result;
+  uint32_t resolv_id;
+  uint16_t port;
+  uint8_t dns_queries;
+  uint8_t transport;
+  BIT(started);
+  BIT(announced);
+  BIT(abstract_unix_socket);
+  BIT(complete_resolve);
+  BIT(for_proxy);
+  char hostname[1];
+};
+
+static struct cf_dns_ctx *cf_dns_ctx_create(struct Curl_easy *data,
+                                            uint8_t dns_queries,
+                                            const char *hostname,
+                                            uint16_t port, uint8_t transport,
+                                            bool abstract_unix_socket,
+                                            bool for_proxy,
+                                            bool complete_resolve,
+                                            struct Curl_dns_entry *dns)
+{
+  struct cf_dns_ctx *ctx;
+  size_t hlen = strlen(hostname);
+
+  ctx = curlx_calloc(1, sizeof(*ctx) + hlen);
+  if(!ctx)
+    return NULL;
+
+  ctx->port = port;
+  ctx->dns_queries = dns_queries;
+  ctx->transport = transport;
+  ctx->abstract_unix_socket = abstract_unix_socket;
+  ctx->for_proxy = for_proxy;
+  ctx->complete_resolve = complete_resolve;
+  ctx->dns = Curl_dns_entry_link(data, dns);
+  ctx->started = !!ctx->dns;
+  if(hlen)
+    memcpy(ctx->hostname, hostname, hlen);
+
+  CURL_TRC_DNS(data, "created DNS filter for %s:%u, transport=%x, queries=%x",
+               ctx->hostname, ctx->port, ctx->transport, ctx->dns_queries);
+  return ctx;
+}
+
+static void cf_dns_ctx_destroy(struct Curl_easy *data,
+                               struct cf_dns_ctx *ctx)
+{
+  if(ctx) {
+    Curl_dns_entry_unlink(data, &ctx->dns);
+    curlx_free(ctx);
+  }
+}
+
+#ifdef CURLVERBOSE
+static void cf_dns_report_addr(struct Curl_easy *data,
+                               struct dynbuf *tmp,
+                               const char *label,
+                               int ai_family,
+                               const struct Curl_addrinfo *ai)
+{
+  char buf[MAX_IPADR_LEN];
+  const char *sep = "";
+  CURLcode result;
+
+  curlx_dyn_reset(tmp);
+  for(; ai; ai = ai->ai_next) {
+    if(ai->ai_family == ai_family) {
+      Curl_printable_address(ai, buf, sizeof(buf));
+      result = curlx_dyn_addf(tmp, "%s%s", sep, buf);
+      if(result) {
+        infof(data, "too many IP, cannot show");
+        return;
+      }
+      sep = ", ";
+    }
+  }
+
+  infof(data, "%s%s", label,
+        (curlx_dyn_len(tmp) ? curlx_dyn_ptr(tmp) : "(none)"));
+}
+
+static void cf_dns_report(struct Curl_cfilter *cf,
+                          struct Curl_easy *data,
+                          struct Curl_dns_entry *dns)
+{
+  struct cf_dns_ctx *ctx = cf->ctx;
+  struct dynbuf tmp;
+
+  if(!Curl_trc_is_verbose(data) ||
+     /* ignore no name or numerical IP addresses */
+     !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname))
+    return;
+
+  switch(ctx->transport) {
+  case TRNSPRT_UNIX:
+#ifdef USE_UNIX_SOCKETS
+    CURL_TRC_CF(data, cf, "resolved unix domain %s",
+                Curl_conn_get_unix_path(data->conn));
+#else
+    DEBUGASSERT(0);
+#endif
+    break;
+  default:
+    curlx_dyn_init(&tmp, 1024);
+    infof(data, "Host %s:%u was resolved.", dns->hostname, dns->port);
+#ifdef CURLRES_IPV6
+    cf_dns_report_addr(data, &tmp, "IPv6: ", AF_INET6, dns->addr);
+#endif
+    cf_dns_report_addr(data, &tmp, "IPv4: ", AF_INET, dns->addr);
+#ifdef USE_HTTPSRR
+    if(!dns->hinfo)
+      infof(data, "HTTPS-RR: -");
+    else if(!Curl_httpsrr_applicable(data, dns->hinfo))
+      infof(data, "HTTPS-RR: not applicable");
+    else {
+      CURLcode result = Curl_httpsrr_print(&tmp, dns->hinfo);
+      if(!result)
+        infof(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp));
+      else
+        infof(data, "Error printing HTTPS-RR information");
+    }
+#endif
+    curlx_dyn_free(&tmp);
+    break;
+  }
+}
+#else
+#define cf_dns_report(x, y, z) Curl_nop_stmt
+#endif
+
+/*************************************************************
+ * Resolve the address of the server or proxy
+ *************************************************************/
+static CURLcode cf_dns_start(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             struct Curl_dns_entry **pdns)
+{
+  struct cf_dns_ctx *ctx = cf->ctx;
+  timediff_t timeout_ms = Curl_timeleft_ms(data);
+  CURLcode result;
+
+  *pdns = NULL;
+
+#ifdef USE_UNIX_SOCKETS
+  if(ctx->transport == TRNSPRT_UNIX) {
+    CURL_TRC_CF(data, cf, "resolve unix socket %s", ctx->hostname);
+    return Curl_resolv_unix(data, ctx->hostname,
+                            (bool)cf->conn->bits.abstract_unix_socket, pdns);
+  }
+#endif
+
+  /* Resolve target host right on */
+  CURL_TRC_CF(data, cf, "cf_dns_start host %s:%u", ctx->hostname, ctx->port);
+  if(Curl_is_ipv4addr(ctx->hostname))
+    ctx->dns_queries |= CURL_DNSQ_A;
+#ifdef USE_IPV6
+  else if(Curl_is_ipaddr(ctx->hostname)) /* not ipv4, must be ipv6 then */
+    ctx->dns_queries |= CURL_DNSQ_AAAA;
+#endif
+  result = Curl_resolv(data, ctx->dns_queries,
+                       ctx->hostname, ctx->port, ctx->transport,
+                       (bool)ctx->for_proxy, timeout_ms,
+                       &ctx->resolv_id, pdns);
+  DEBUGASSERT(!result || !*pdns);
+  if(!result) { /* resolved right away, either sync or from dnscache */
+    DEBUGASSERT(*pdns);
+    return CURLE_OK;
+  }
+  else if(result == CURLE_AGAIN) { /* async resolv in progress */
+    return CURLE_OK;
+  }
+  else if(result == CURLE_OPERATION_TIMEDOUT) { /* took too long */
+    failf(data, "Failed to resolve '%s' with timeout after %"
+          FMT_TIMEDIFF_T " ms", ctx->hostname,
+          curlx_ptimediff_ms(Curl_pgrs_now(data),
+                             &data->progress.t_startsingle));
+    return CURLE_OPERATION_TIMEDOUT;
+  }
+  else {
+    DEBUGASSERT(result);
+    failf(data, "Could not resolve: %s", ctx->hostname);
+    return result;
+  }
+}
+
+#define CURL_HEV3_RESOLVE_DELAY_MS    50
+
+static bool cf_dns_ready_to_connect(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data)
+{
+  struct cf_dns_ctx *ctx = cf->ctx;
+
+  if(ctx->resolv_result)
+    return TRUE;
+  else if(ctx->dns)
+    return TRUE;
+#ifdef USE_CURL_ASYNC
+  else {
+    /* We want AAAA answer as we prefer ipv6. If a sub-filter desires
+    * HTTPS-RR, we check for that query as well. */
+    uint8_t wanted_answers = CURL_DNSQ_AAAA;
+    if(Curl_conn_cf_wants_httpsrr(cf, data))
+      wanted_answers |= CURL_DNSQ_HTTPS;
+
+    /* Note: if a query was never started, it is considered to have
+     * an answer (e.g. a negative one). */
+    if(Curl_resolv_has_answers(data, ctx->resolv_id, wanted_answers))
+      return TRUE;
+    /* If the wanted answers are not available after a delay,
+     * we let the connect attempts start anyway. */
+    return Curl_resolv_elapsed_ms(data, ctx->resolv_id) >=
+           CURL_HEV3_RESOLVE_DELAY_MS;
+  }
+#else
+  (void)data;
+  DEBUGASSERT(0); /* We should not come here */
+  return FALSE;
+#endif /* USE_CURL_ASYNC */
+}
+
+static CURLcode cf_dns_connect(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               bool *done)
+{
+  struct cf_dns_ctx *ctx = cf->ctx;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  *done = FALSE;
+  if(!ctx->started) {
+    ctx->started = TRUE;
+    ctx->resolv_result = cf_dns_start(cf, data, &ctx->dns);
+  }
+
+  if(!ctx->dns && !ctx->resolv_result) {
+    ctx->resolv_result =
+      Curl_resolv_take_result(data, ctx->resolv_id, &ctx->dns);
+  }
+
+  if(ctx->resolv_result) {
+    CURL_TRC_CF(data, cf, "error resolving: %d", ctx->resolv_result);
+    return ctx->resolv_result;
+  }
+
+  if(ctx->dns && !ctx->announced) {
+    ctx->announced = TRUE;
+    if(cf->sockindex == FIRSTSOCKET) {
+      cf->conn->bits.dns_resolved = TRUE;
+      Curl_pgrsTime(data, TIMER_NAMELOOKUP);
+    }
+    cf_dns_report(cf, data, ctx->dns);
+  }
+
+  if(!cf_dns_ready_to_connect(cf, data)) {
+    return CURLE_OK;
+  }
+
+  if(cf->next && !cf->next->connected) {
+    bool sub_done;
+    CURLcode result = Curl_conn_cf_connect(cf->next, data, &sub_done);
+    if(result || !sub_done)
+      return result;
+    DEBUGASSERT(sub_done);
+  }
+
+  /* sub filter chain is connected */
+  CURL_TRC_CF(data, cf, "connected filter chain below");
+  if(ctx->complete_resolve && !ctx->dns && !ctx->resolv_result) {
+    /* This filter only connects when it has resolved everything. */
+    CURL_TRC_CF(data, cf, "delay connect until resolve complete");
+    return CURLE_OK;
+  }
+  *done = TRUE;
+  cf->connected = TRUE;
+  Curl_resolv_destroy(data, ctx->resolv_id);
+  return CURLE_OK;
+}
+
+static void cf_dns_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_dns_ctx *ctx = cf->ctx;
+
+  CURL_TRC_CF(data, cf, "destroy");
+  cf_dns_ctx_destroy(data, ctx);
+}
+
+static void cf_dns_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  cf->connected = FALSE;
+  if(cf->next)
+    cf->next->cft->do_close(cf->next, data);
+}
+
+static CURLcode cf_dns_adjust_pollset(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      struct easy_pollset *ps)
+{
+#ifdef USE_CURL_ASYNC
+  if(!cf->connected)
+    return Curl_resolv_pollset(data, ps);
+#else
+  (void)cf;
+  (void)data;
+  (void)ps;
+#endif
+  return CURLE_OK;
+}
+
+static CURLcode cf_dns_cntrl(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             int event, int arg1, void *arg2)
+{
+  struct cf_dns_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_DATA_DONE:
+    if(ctx->dns) {
+      /* Should only come here when the connect attempt failed and
+       * `data` is giving up on it. On a successful connect, we already
+       * unlinked the DNS entry. */
+      Curl_dns_entry_unlink(data, &ctx->dns);
+    }
+    break;
+  default:
+    break;
+  }
+  return result;
+}
+
+struct Curl_cftype Curl_cft_dns = {
+  "DNS",
+  CF_TYPE_SETUP,
+  CURL_LOG_LVL_NONE,
+  cf_dns_destroy,
+  cf_dns_connect,
+  cf_dns_close,
+  Curl_cf_def_shutdown,
+  cf_dns_adjust_pollset,
+  Curl_cf_def_data_pending,
+  Curl_cf_def_send,
+  Curl_cf_def_recv,
+  cf_dns_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
+};
+
+static CURLcode cf_dns_create(struct Curl_cfilter **pcf,
+                              struct Curl_easy *data,
+                              uint8_t dns_queries,
+                              const char *hostname,
+                              uint16_t port,
+                              uint8_t transport,
+                              bool abstract_unix_socket,
+                              bool for_proxy,
+                              bool complete_resolve,
+                              struct Curl_dns_entry *dns)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_dns_ctx *ctx;
+  CURLcode result = CURLE_OK;
+
+  (void)data;
+  ctx = cf_dns_ctx_create(data, dns_queries, hostname, port, transport,
+                          abstract_unix_socket, for_proxy,
+                          complete_resolve, dns);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  result = Curl_cf_create(&cf, &Curl_cft_dns, ctx);
+
+out:
+  *pcf = result ? NULL : cf;
+  if(result)
+    cf_dns_ctx_destroy(data, ctx);
+  return result;
+}
+
+/* Create a "resolv" filter for the transfer's connection. Figures
+ * out the hostname/path and port where to connect to. */
+static CURLcode cf_dns_conn_create(struct Curl_cfilter **pcf,
+                                   struct Curl_easy *data,
+                                   uint8_t dns_queries,
+                                   uint8_t transport,
+                                   bool complete_resolve,
+                                   struct Curl_dns_entry *dns)
+{
+  struct connectdata *conn = data->conn;
+  const char *hostname = NULL;
+  uint16_t port = 0;
+  bool abstract_unix_socket = FALSE, for_proxy = FALSE;
+
+#ifdef USE_UNIX_SOCKETS
+  {
+    const char *unix_path = Curl_conn_get_unix_path(conn);
+    if(unix_path) {
+      DEBUGASSERT(transport == TRNSPRT_UNIX);
+      hostname = unix_path;
+      abstract_unix_socket = (bool)conn->bits.abstract_unix_socket;
+    }
+  }
+#endif
+
+#ifndef CURL_DISABLE_PROXY
+  if(!hostname && conn->bits.proxy) {
+    for_proxy = TRUE;
+    hostname = conn->bits.socksproxy ?
+      conn->socks_proxy.host.name : conn->http_proxy.host.name;
+    port = conn->bits.socksproxy ?
+      conn->socks_proxy.port : conn->http_proxy.port;
+  }
+#endif
+  if(!hostname) {
+    struct hostname *ehost;
+    ehost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host;
+    /* If not connecting via a proxy, extract the port from the URL, if it is
+     * there, thus overriding any defaults that might have been set above. */
+    hostname = ehost->name;
+    port = conn->bits.conn_to_port ?
+      conn->conn_to_port : (uint16_t)conn->remote_port;
+  }
+
+  if(!hostname) {
+    DEBUGASSERT(0);
+    return CURLE_FAILED_INIT;
+  }
+  return cf_dns_create(pcf, data, dns_queries,
+                       hostname, port, transport,
+                       abstract_unix_socket, for_proxy,
+                       complete_resolve, dns);
+}
+
+/* Adds a "resolv" filter at the top of the connection's filter chain.
+ * For FIRSTSOCKET, the `dns` parameter may be NULL. The filter will
+ * figure out hostname and port to connect to and start the DNS resolve
+ * on the first connect attempt.
+ * For SECONDARYSOCKET, the `dns` parameter must be given.
+ */
+CURLcode Curl_cf_dns_add(struct Curl_easy *data,
+                         struct connectdata *conn,
+                         int sockindex,
+                         uint8_t dns_queries,
+                         uint8_t transport,
+                         struct Curl_dns_entry *dns)
+{
+  struct Curl_cfilter *cf = NULL;
+  CURLcode result;
+
+  DEBUGASSERT(data);
+  if(sockindex == FIRSTSOCKET)
+    result = cf_dns_conn_create(&cf, data, dns_queries, transport, FALSE, dns);
+  else if(dns) {
+    result = cf_dns_create(&cf, data, dns_queries,
+                           dns->hostname, dns->port, transport,
+                           FALSE, FALSE, FALSE, dns);
+  }
+  else {
+    DEBUGASSERT(0);
+    result = CURLE_FAILED_INIT;
+  }
+  if(result)
+    goto out;
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+out:
+  return result;
+}
+
+/* Insert a new "resolv" filter directly after `cf`. It will
+ * start a DNS resolve for the given hostnmae and port on the
+ * first connect attempt.
+ * See socks.c on how this is used to make a non-blocking DNS
+ * resolve during connect.
+ */
+CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data,
+                                  uint8_t dns_queries,
+                                  const char *hostname,
+                                  uint16_t port,
+                                  uint8_t transport,
+                                  bool complete_resolve)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  result = cf_dns_create(&cf, data, dns_queries,
+                         hostname, port, transport,
+                         FALSE, FALSE, complete_resolve, NULL);
+  if(result)
+    return result;
+
+  Curl_conn_cf_insert_after(cf_at, cf);
+  return CURLE_OK;
+}
+
+/* Return the resolv result from the first "resolv" filter, starting
+ * the given filter `cf` downwards.
+ */
+static CURLcode cf_dns_result(struct Curl_cfilter *cf)
+{
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_dns) {
+      struct cf_dns_ctx *ctx = cf->ctx;
+      if(ctx->dns || ctx->resolv_result)
+        return ctx->resolv_result;
+      return CURLE_AGAIN;
+    }
+  }
+  return CURLE_FAILED_INIT;
+}
+
+/* Return the result of the DNS resolution. Searches for a "resolv"
+ * filter from the top of the filter chain down. Returns
+ * - CURLE_AGAIN when not done yet
+ * - CURLE_OK when DNS was successfully resolved
+ * - CURLR_FAILED_INIT when no resolv filter was found
+ * - error returned by the DNS resolv
+ */
+CURLcode Curl_conn_dns_result(struct connectdata *conn, int sockindex)
+{
+  return cf_dns_result(conn->cfilter[sockindex]);
+}
+
+static const struct Curl_addrinfo *cf_dns_get_nth_ai(
+  struct Curl_cfilter *cf,
+  const struct Curl_addrinfo *ai,
+  int ai_family, unsigned int index)
+{
+  struct cf_dns_ctx *ctx = cf->ctx;
+  unsigned int i = 0;
+
+  if((ai_family == AF_INET) && !(ctx->dns_queries & CURL_DNSQ_A))
+    return NULL;
+#ifdef USE_IPV6
+  if((ai_family == AF_INET6) && !(ctx->dns_queries & CURL_DNSQ_AAAA))
+    return NULL;
+#endif
+  for(i = 0; ai; ai = ai->ai_next) {
+    if(ai->ai_family == ai_family) {
+      if(i == index)
+        return ai;
+      ++i;
+    }
+  }
+  return NULL;
+}
+
+/* Return the addrinfo at `index` for the given `family` from the
+ * first "resolve" filter underneath `cf`. If the DNS resolving is
+ * not done yet or if no address for the family exists, returns NULL.
+ */
+const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf,
+                                               struct Curl_easy *data,
+                                               int ai_family,
+                                               unsigned int index)
+{
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_dns) {
+      struct cf_dns_ctx *ctx = cf->ctx;
+      if(ctx->resolv_result)
+        return NULL;
+      else if(ctx->dns)
+        return cf_dns_get_nth_ai(cf, ctx->dns->addr, ai_family, index);
+      else
+        return Curl_resolv_get_ai(data, ctx->resolv_id, ai_family, index);
+    }
+  }
+  return NULL;
+}
+
+/* Return the addrinfo at `index` for the given `family` from the
+ * first "resolve" filter at the connection. If the DNS resolving is
+ * not done yet or if no address for the family exists, returns NULL.
+ */
+const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data,
+                                                 int sockindex, int ai_family,
+                                                 unsigned int index)
+{
+  struct connectdata *conn = data->conn;
+  return Curl_cf_dns_get_ai(conn->cfilter[sockindex], data, ai_family, index);
+}
+
+#ifdef USE_HTTPSRR
+/* Return the HTTPS-RR info from the first "resolve" filter at the
+ * connection. If the DNS resolving is not done yet or if there
+ * is no HTTPS-RR info, returns NULL.
+ */
+const struct Curl_https_rrinfo *Curl_conn_dns_get_https(struct Curl_easy *data,
+                                                        int sockindex)
+{
+  struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_dns) {
+      struct cf_dns_ctx *ctx = cf->ctx;
+      if(ctx->dns)
+        return ctx->dns->hinfo;
+      else
+        return Curl_resolv_get_https(data, ctx->resolv_id);
+    }
+  }
+  return NULL;
+}
+
+bool Curl_conn_dns_resolved_https(struct Curl_easy *data, int sockindex)
+{
+  struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_dns) {
+      struct cf_dns_ctx *ctx = cf->ctx;
+      if(ctx->dns)
+        return TRUE;
+      else
+        return Curl_resolv_knows_https(data, ctx->resolv_id);
+    }
+  }
+  return FALSE;
+}
+
+#endif /* USE_HTTPSRR */
diff --git a/Utilities/cmcurl/lib/cf-dns.h b/Utilities/cmcurl/lib/cf-dns.h
new file mode 100644
index 0000000..3c46b1b
--- /dev/null
+++ b/Utilities/cmcurl/lib/cf-dns.h
@@ -0,0 +1,71 @@
+#ifndef HEADER_CURL_CF_DNS_H
+#define HEADER_CURL_CF_DNS_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"
+
+struct Curl_easy;
+struct connectdata;
+struct Curl_dns_entry;
+struct Curl_addrinfo;
+
+CURLcode Curl_cf_dns_add(struct Curl_easy *data,
+                         struct connectdata *conn,
+                         int sockindex,
+                         uint8_t dns_queries,
+                         uint8_t transport,
+                         struct Curl_dns_entry *dns);
+
+CURLcode Curl_cf_dns_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data,
+                                  uint8_t dns_queries,
+                                  const char *hostname,
+                                  uint16_t port,
+                                  uint8_t transport,
+                                  bool complete_resolve);
+
+CURLcode Curl_conn_dns_result(struct connectdata *conn, int sockindex);
+
+const struct Curl_addrinfo *Curl_conn_dns_get_ai(struct Curl_easy *data,
+                                                 int sockindex,
+                                                 int ai_family,
+                                                 unsigned int index);
+
+const struct Curl_addrinfo *Curl_cf_dns_get_ai(struct Curl_cfilter *cf,
+                                               struct Curl_easy *data,
+                                               int ai_family,
+                                               unsigned int index);
+
+#ifdef USE_HTTPSRR
+const struct Curl_https_rrinfo *Curl_conn_dns_get_https(struct Curl_easy *data,
+                                                        int sockindex);
+bool Curl_conn_dns_resolved_https(struct Curl_easy *data, int sockindex);
+#else
+#define Curl_conn_dns_get_https(a, b)        NULL
+#define Curl_conn_dns_resolved_https(a, b)   TRUE
+#endif
+
+extern struct Curl_cftype Curl_cft_dns;
+
+#endif /* HEADER_CURL_CF_DNS_H */
diff --git a/Utilities/cmcurl/lib/cf-h1-proxy.c b/Utilities/cmcurl/lib/cf-h1-proxy.c
index 69e050b..3c2c837 100644
--- a/Utilities/cmcurl/lib/cf-h1-proxy.c
+++ b/Utilities/cmcurl/lib/cf-h1-proxy.c
@@ -171,8 +171,8 @@
                                 proxy */
     /* If a proxy-authorization header was used for the proxy, then we should
        make sure that it is not accidentally used for the document request
-       after we have connected. So let's free and clear it here. */
-    Curl_safefree(data->state.aptr.proxyuserpwd);
+       after we have connected. Let's thus free and clear it here. */
+    curlx_safefree(data->req.proxyuserpwd);
     break;
   }
 }
@@ -208,7 +208,7 @@
 
   /* This only happens if we have looped here due to authentication reasons,
      and we do not really use the newly cloned URL here then. Free it. */
-  Curl_safefree(data->req.newurl);
+  curlx_safefree(data->req.newurl);
 
   result = Curl_http_proxy_create_CONNECT(&req, cf, data, 1);
   if(result)
@@ -449,7 +449,7 @@
 
     if(!nread) {
       if(data->set.proxyauth && data->state.authproxy.avail &&
-         data->state.aptr.proxyuserpwd) {
+         data->req.proxyuserpwd) {
         /* proxy auth was requested and there was proxy auth available,
            then deem this as "mere" proxy disconnect */
         ts->close_connection = TRUE;
@@ -465,17 +465,7 @@
 
     if(ts->keepon == KEEPON_IGNORE) {
       /* This means we are currently ignoring a response-body */
-
-      if(ts->cl) {
-        /* A Content-Length based body: count down the counter
-           and make sure to break out of the loop when we are done! */
-        ts->cl--;
-        if(ts->cl <= 0) {
-          ts->keepon = KEEPON_DONE;
-          break;
-        }
-      }
-      else if(ts->chunked_encoding) {
+      if(ts->chunked_encoding) {
         /* chunked-encoded body, so we need to do the chunked dance
            properly to know when the end of the body is reached */
         size_t consumed = 0;
@@ -491,6 +481,15 @@
           ts->keepon = KEEPON_DONE;
         }
       }
+      else if(ts->cl) {
+        /* A Content-Length based body: count down the counter
+           and make sure to break out of the loop when we are done! */
+        ts->cl--;
+        if(ts->cl <= 0) {
+          ts->keepon = KEEPON_DONE;
+          break;
+        }
+      }
       continue;
     }
 
@@ -597,6 +596,8 @@
       /* read what is there */
       CURL_TRC_CF(data, cf, "CONNECT receive");
       result = recv_CONNECT_resp(cf, data, ts, &done);
+      if(result)
+        CURL_TRC_CF(data, cf, "error receiving CONNECT response: %d", result);
       if(!result)
         result = Curl_pgrsUpdate(data);
       /* error or not complete yet. return for more multi-multi */
@@ -642,10 +643,10 @@
   DEBUGASSERT(ts->tunnel_state == H1_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);
+    curlx_safefree(data->req.newurl);
     h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
     failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
-    return CURLE_RECV_ERROR;
+    return CURLE_COULDNT_CONNECT;
   }
   /* 2xx response, SUCCESS! */
   h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data);
@@ -689,7 +690,7 @@
   result = H1_CONNECT(cf, data, ts);
   if(result)
     goto out;
-  Curl_safefree(data->state.aptr.proxyuserpwd);
+  curlx_safefree(data->req.proxyuserpwd);
 
 out:
   *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
diff --git a/Utilities/cmcurl/lib/cf-h2-proxy.c b/Utilities/cmcurl/lib/cf-h2-proxy.c
index 4275e1e..2f8cc41 100644
--- a/Utilities/cmcurl/lib/cf-h2-proxy.c
+++ b/Utilities/cmcurl/lib/cf-h2-proxy.c
@@ -80,7 +80,7 @@
                                    struct tunnel_stream *ts)
 {
   const char *hostname;
-  int port;
+  uint16_t port;
   bool ipv6_ip;
 
   ts->state = H2_TUNNEL_INIT;
@@ -92,7 +92,7 @@
   Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip);
 
   /* host:port with IPv6 support */
-  ts->authority = curl_maprintf("%s%s%s:%d", ipv6_ip ? "[" : "", hostname,
+  ts->authority = curl_maprintf("%s%s%s:%u", ipv6_ip ? "[" : "", hostname,
                                 ipv6_ip ? "]" : "", port);
   if(!ts->authority)
     return CURLE_OUT_OF_MEMORY;
@@ -105,7 +105,7 @@
   Curl_http_resp_free(ts->resp);
   Curl_bufq_free(&ts->recvbuf);
   Curl_bufq_free(&ts->sendbuf);
-  Curl_safefree(ts->authority);
+  curlx_safefree(ts->authority);
   memset(ts, 0, sizeof(*ts));
   ts->state = H2_TUNNEL_INIT;
 }
@@ -157,8 +157,8 @@
     ts->state = new_state;
     /* If a proxy-authorization header was used for the proxy, then we should
        make sure that it is not accidentally used for the document request
-       after we have connected. So let's free and clear it here. */
-    Curl_safefree(data->state.aptr.proxyuserpwd);
+       after we have connected. Let's thus free and clear it here. */
+    curlx_safefree(data->req.proxyuserpwd);
     break;
   }
 }
@@ -477,7 +477,7 @@
        * window and *assume* that we treat this like a WINDOW_UPDATE. Some
        * servers send an explicit WINDOW_UPDATE, but not all seem to do that.
        * To be safe, we UNHOLD a stream in order not to stall. */
-      if(CURL_WANT_SEND(data)) {
+      if(CURL_REQ_WANT_SEND(data)) {
         drain_tunnel(cf, data, &ctx->tunnel);
       }
       break;
@@ -512,7 +512,7 @@
     }
     break;
   case NGHTTP2_WINDOW_UPDATE:
-    if(CURL_WANT_SEND(data)) {
+    if(CURL_REQ_WANT_SEND(data)) {
       drain_tunnel(cf, data, &ctx->tunnel);
     }
     break;
@@ -627,7 +627,7 @@
   if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf))
     *data_flags = NGHTTP2_DATA_FLAG_EOF;
 
-  CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zd",
+  CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zu",
               ts->stream_id, nread);
   return (nread  > SSIZE_MAX) ?
     NGHTTP2_ERR_CALLBACK_FAILURE : (ssize_t)nread;
@@ -675,7 +675,7 @@
   if(stream_id != ctx->tunnel.stream_id)
     return 0;
 
-  CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %d)",
+  CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %u)",
               stream_id, nghttp2_http2_strerror(error_code), error_code);
   ctx->tunnel.closed = TRUE;
   ctx->tunnel.error = error_code;
@@ -685,15 +685,16 @@
   return 0;
 }
 
-static CURLcode proxy_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)
+static CURLcode proxy_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;
@@ -804,14 +805,14 @@
       return result;
     if(data->req.newurl) {
       /* Indicator that we should try again */
-      Curl_safefree(data->req.newurl);
+      curlx_safefree(data->req.newurl);
       h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data);
       return CURLE_OK;
     }
   }
 
   /* Seems to have failed */
-  return CURLE_RECV_ERROR;
+  return CURLE_COULDNT_CONNECT;
 }
 
 static CURLcode H2_CONNECT(struct Curl_cfilter *cf,
@@ -1158,10 +1159,9 @@
 
   *pnread = 0;
   if(ctx->tunnel.error) {
-    failf(data, "HTTP/2 stream %" PRIu32 " reset by %s (error 0x%" PRIx32
-          " %s)", ctx->tunnel.stream_id,
-           ctx->tunnel.reset ? "server" : "curl",
-           ctx->tunnel.error, nghttp2_http2_strerror(ctx->tunnel.error));
+    failf(data, "HTTP/2 stream %d reset by %s (error 0x%x %s)",
+          ctx->tunnel.stream_id, ctx->tunnel.reset ? "server" : "curl",
+          ctx->tunnel.error, nghttp2_http2_strerror(ctx->tunnel.error));
     return CURLE_RECV_ERROR;
   }
 
@@ -1269,7 +1269,7 @@
   }
 
   result = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, pnwritten);
-  CURL_TRC_CF(data, cf, "cf_send(), bufq_write %d, %zd", result, *pnwritten);
+  CURL_TRC_CF(data, cf, "cf_send(), bufq_write %d, %zu", result, *pnwritten);
   if(result && (result != CURLE_AGAIN))
     goto out;
 
diff --git a/Utilities/cmcurl/lib/cf-haproxy.c b/Utilities/cmcurl/lib/cf-haproxy.c
index 782253e..9ee5e79 100644
--- a/Utilities/cmcurl/lib/cf-haproxy.c
+++ b/Utilities/cmcurl/lib/cf-haproxy.c
@@ -28,6 +28,7 @@
 #include "urldata.h"
 #include "cfilters.h"
 #include "cf-haproxy.h"
+#include "curl_addrinfo.h"
 #include "curl_trc.h"
 #include "select.h"
 
@@ -61,9 +62,16 @@
 static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter *cf,
                                         struct Curl_easy *data)
 {
+  /* We fake a client connection report to the upstream server
+   * with the HAProxy protocol, reporting the client's source
+   * and destination IP addresses and ports.
+   * addresses: either the ones used to talk to the upstream
+   *            OR the value supplied by the user
+   * ports: the ports used in the upstream connection */
+  const char *client_source_ip;
+  const char *client_dest_ip;
   struct cf_haproxy_ctx *ctx = cf->ctx;
   CURLcode result;
-  const char *client_ip;
   struct ip_quadruple ipquad;
   bool is_ipv6;
 
@@ -79,15 +87,19 @@
   if(result)
     return result;
 
-  /* Emit the correct prefix for IPv6 */
-  if(data->set.str[STRING_HAPROXY_CLIENT_IP])
-    client_ip = data->set.str[STRING_HAPROXY_CLIENT_IP];
-  else
-    client_ip = ipquad.local_ip;
+  if(data->set.str[STRING_HAPROXY_CLIENT_IP]) {
+    client_source_ip = data->set.str[STRING_HAPROXY_CLIENT_IP];
+    client_dest_ip = client_source_ip;
+    is_ipv6 = !Curl_is_ipv4addr(client_source_ip);
+  }
+  else {
+    client_source_ip = ipquad.local_ip;
+    client_dest_ip = ipquad.remote_ip;
+  }
 
   result = curlx_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n",
                           is_ipv6 ? "TCP6" : "TCP4",
-                          client_ip, ipquad.remote_ip,
+                          client_source_ip, client_dest_ip,
                           ipquad.local_port, ipquad.remote_port);
 
 #ifdef USE_UNIX_SOCKETS
@@ -185,7 +197,7 @@
 
 struct Curl_cftype Curl_cft_haproxy = {
   "HAPROXY",
-  CF_TYPE_PROXY,
+  CF_TYPE_PROXY | CF_TYPE_SETUP,
   0,
   cf_haproxy_destroy,
   cf_haproxy_connect,
diff --git a/Utilities/cmcurl/lib/cf-https-connect.c b/Utilities/cmcurl/lib/cf-https-connect.c
index cabc6ab..d1d8f51 100644
--- a/Utilities/cmcurl/lib/cf-https-connect.c
+++ b/Utilities/cmcurl/lib/cf-https-connect.c
@@ -28,6 +28,7 @@
 #include "urldata.h"
 #include "curl_trc.h"
 #include "cfilters.h"
+#include "cf-dns.h"
 #include "connect.h"
 #include "hostip.h"
 #include "httpsrr.h"
@@ -39,6 +40,7 @@
 #include "vquic/vquic.h"
 
 typedef enum {
+  CF_HC_RESOLV,
   CF_HC_INIT,
   CF_HC_CONNECT,
   CF_HC_SUCCESS,
@@ -56,19 +58,17 @@
   BIT(shutdown);
 };
 
-static void cf_hc_baller_reset(struct cf_hc_baller *b,
-                               struct Curl_easy *data)
+static void cf_hc_baller_discard(struct cf_hc_baller *b,
+                                 struct Curl_easy *data)
 {
   if(b->cf) {
     Curl_conn_cf_close(b->cf, data);
     Curl_conn_cf_discard_chain(&b->cf, data);
     b->cf = NULL;
   }
-  b->result = CURLE_OK;
-  b->reply_ms = -1;
 }
 
-static bool cf_hc_baller_is_active(struct cf_hc_baller *b)
+static bool cf_hc_baller_is_connecting(struct cf_hc_baller *b)
 {
   return b->cf && !b->result;
 }
@@ -112,18 +112,46 @@
   cf_hc_state state;
   struct curltime started;  /* when connect started */
   CURLcode result;          /* overall result */
+  CURLcode check_h3_result;
   struct cf_hc_baller ballers[2];
   size_t baller_count;
   timediff_t soft_eyeballs_timeout_ms;
   timediff_t hard_eyeballs_timeout_ms;
+  uint8_t def_transport;
+  BIT(httpsrr_resolved);
+  BIT(checked_h3);
+  BIT(ballers_complete);
 };
 
+static void cf_hc_ctx_close(struct Curl_easy *data,
+                            struct cf_hc_ctx *ctx)
+{
+  if(ctx) {
+    size_t i;
+    for(i = 0; i < ctx->baller_count; ++i)
+      cf_hc_baller_discard(&ctx->ballers[i], data);
+  }
+}
+
+static void cf_hc_ctx_destroy(struct Curl_easy *data,
+                              struct cf_hc_ctx *ctx)
+{
+  if(ctx) {
+    cf_hc_ctx_close(data, ctx);
+    curlx_free(ctx);
+  }
+}
+
 static void cf_hc_baller_assign(struct cf_hc_baller *b,
                                 enum alpnid alpn_id,
                                 uint8_t def_transport)
 {
   b->alpn_id = alpn_id;
   b->transport = def_transport;
+  b->cf = NULL;
+  b->result = CURLE_OK;
+  b->reply_ms = -1;
+  b->shutdown = FALSE;
   switch(b->alpn_id) {
   case ALPN_h3:
     b->name = "h3";
@@ -135,6 +163,9 @@
   case ALPN_h1:
     b->name = "h1";
     break;
+  case ALPN_none:
+    b->name = "no-alpn";
+    break;
   default:
     b->result = CURLE_FAILED_INIT;
     break;
@@ -143,24 +174,14 @@
 
 static void cf_hc_baller_init(struct cf_hc_baller *b,
                               struct Curl_cfilter *cf,
-                              struct Curl_easy *data,
-                              uint8_t transport)
+                              struct Curl_easy *data)
 {
   struct Curl_cfilter *save = cf->next;
 
   cf->next = NULL;
   b->started = *Curl_pgrs_now(data);
-  switch(b->alpn_id) {
-  case ALPN_h3:
-    transport = TRNSPRT_QUIC;
-    break;
-  default:
-    break;
-  }
-
-  if(!b->result)
-    b->result = Curl_cf_setup_insert_after(cf, data, transport,
-                                           CURL_CF_SSL_ENABLE);
+  b->result = Curl_cf_setup_insert_after(cf, data, b->transport,
+                                         CURL_CF_SSL_ENABLE);
   b->cf = cf->next;
   cf->next = save;
 }
@@ -179,59 +200,39 @@
   return b->result;
 }
 
-static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data)
-{
-  struct cf_hc_ctx *ctx = cf->ctx;
-  size_t i;
-
-  if(ctx) {
-    for(i = 0; i < ctx->baller_count; ++i)
-      cf_hc_baller_reset(&ctx->ballers[i], data);
-    ctx->state = CF_HC_INIT;
-    ctx->result = CURLE_OK;
-    ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout;
-    ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 4;
-  }
-}
-
 static CURLcode baller_connected(struct Curl_cfilter *cf,
                                  struct Curl_easy *data,
                                  struct cf_hc_baller *winner)
 {
   struct cf_hc_ctx *ctx = cf->ctx;
-  CURLcode result = CURLE_OK;
-  int reply_ms;
-  size_t i;
 
-  DEBUGASSERT(winner->cf);
-  for(i = 0; i < ctx->baller_count; ++i)
-    if(winner != &ctx->ballers[i])
-      cf_hc_baller_reset(&ctx->ballers[i], data);
+  /* Make the winner's connection filter out own sub-filter, check, move,
+   * close all remaining. */
+  if(cf->next) {
+    DEBUGASSERT(0);
+    return CURLE_FAILED_INIT;
+  }
+  if(!winner->cf) {
+    DEBUGASSERT(0);
+    return CURLE_FAILED_INIT;
+  }
 
-  reply_ms = cf_hc_baller_reply_ms(winner, data);
-  if(reply_ms >= 0)
-    CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms",
-                winner->name,
-                (int)curlx_ptimediff_ms(Curl_pgrs_now(data),
-                                        &winner->started), reply_ms);
-  else
-    CURL_TRC_CF(data, cf, "deferred handshake %s: %dms",
-                winner->name, (int)curlx_ptimediff_ms(Curl_pgrs_now(data),
-                                                      &winner->started));
-
-  /* install the winning filter below this one. */
   cf->next = winner->cf;
   winner->cf = NULL;
-  /* whatever errors where reported by ballers, clear our errorbuf */
+  ctx->state = CF_HC_SUCCESS;
+  cf->connected = TRUE;
+
+  cf_hc_ctx_close(data, ctx);
+  /* ballers may have failf()'d, the winner resets it, so our
+   * errorbuf is clean again. */
   Curl_reset_fail(data);
 
 #ifdef USE_NGHTTP2
   {
-    /* Using nghttp2, we add the filter "below" us, so when the conn
-     * closes, we tear it down for a fresh reconnect */
+    /* For a negotiated HTTP/2 connection insert the h2 filter. */
     const char *alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data);
     if(alpn && !strcmp("h2", alpn)) {
-      result = Curl_http2_switch_at(cf, data);
+      CURLcode result = Curl_http2_switch_at(cf, data);
       if(result) {
         ctx->state = CF_HC_FAILURE;
         ctx->result = result;
@@ -240,63 +241,249 @@
     }
   }
 #endif
-
-  ctx->state = CF_HC_SUCCESS;
-  cf->connected = TRUE;
-  return result;
+  return CURLE_OK;
 }
 
-static bool time_to_start_next(struct Curl_cfilter *cf,
-                               struct Curl_easy *data,
-                               size_t idx, struct curltime now)
+static bool time_to_start_baller2(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data)
 {
   struct cf_hc_ctx *ctx = cf->ctx;
   timediff_t elapsed_ms;
-  size_t i;
 
-  if(idx >= ctx->baller_count)
+  if(ctx->baller_count < 2)
     return FALSE;
-  if(cf_hc_baller_has_started(&ctx->ballers[idx]))
+  else if(cf_hc_baller_has_started(&ctx->ballers[1]))
     return FALSE;
-  for(i = 0; i < idx; i++) {
-    if(!ctx->ballers[i].result)
-      break;
-  }
-  if(i == idx) {
-    CURL_TRC_CF(data, cf, "all previous attempts failed, starting %s",
-                ctx->ballers[idx].name);
+  else if(ctx->ballers[0].result) {
+    CURL_TRC_CF(data, cf, "%s baller failed, starting %s",
+                ctx->ballers[0].name, ctx->ballers[1].name);
     return TRUE;
   }
-  elapsed_ms = curlx_ptimediff_ms(&now, &ctx->started);
+
+  elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->started);
   if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) {
-    CURL_TRC_CF(data, cf, "hard timeout of %" FMT_TIMEDIFF_T "ms reached, "
-                "starting %s",
-                ctx->hard_eyeballs_timeout_ms, ctx->ballers[idx].name);
+    CURL_TRC_CF(data, cf, "%s inconclusive after %" FMT_TIMEDIFF_T ", "
+                "starting %s", ctx->ballers[0].name,
+                ctx->hard_eyeballs_timeout_ms, ctx->ballers[1].name);
     return TRUE;
   }
-
-  if((idx > 0) && (elapsed_ms >= ctx->soft_eyeballs_timeout_ms)) {
-    if(cf_hc_baller_reply_ms(&ctx->ballers[idx - 1], data) < 0) {
-      CURL_TRC_CF(data, cf, "soft timeout of %" FMT_TIMEDIFF_T "ms reached, "
-                  "%s has not seen any data, starting %s",
-                  ctx->soft_eyeballs_timeout_ms,
-                  ctx->ballers[idx - 1].name, ctx->ballers[idx].name);
+  else if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) {
+    if(cf_hc_baller_reply_ms(&ctx->ballers[0], data) < 0) {
+      CURL_TRC_CF(data, cf, "%s has not seen any data after %"
+                  FMT_TIMEDIFF_T "ms, starting %s",
+                  ctx->ballers[0].name, ctx->soft_eyeballs_timeout_ms,
+                  ctx->ballers[1].name);
       return TRUE;
     }
-    /* set the effective hard timeout again */
-    Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms,
-                EXPIRE_ALPN_EYEBALLS);
   }
   return FALSE;
 }
 
+static bool cf_hc_may_h3(struct Curl_cfilter *cf,
+                         struct Curl_easy *data)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  if(!ctx->checked_h3) {
+    ctx->check_h3_result =
+      Curl_conn_may_http3(data, cf->conn, ctx->def_transport);
+    ctx->checked_h3 = TRUE;
+  }
+  return !ctx->check_h3_result;
+}
+
+static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf,
+                                          struct Curl_easy *data,
+                                          enum alpnid not_this_one)
+{
+#ifdef USE_HTTPSRR
+  /* Is there an HTTPSRR use its ALPNs here.
+   * We are here after having selected a connection to a host+port and
+   * can no longer change that. Any HTTPSRR advice for other hosts and ports
+   * we need to ignore. */
+  const struct Curl_https_rrinfo *rr;
+  size_t i;
+
+  /* Do we have HTTPS-RR information? */
+  rr = Curl_conn_dns_get_https(data, cf->sockindex);
+
+  /* We do not support `rr->no_def_alpn`. */
+  if(Curl_httpsrr_applicable(data, rr) && !rr->no_def_alpn) {
+    for(i = 0; i < CURL_ARRAYSIZE(rr->alpns); ++i) {
+      enum alpnid alpn_rr = (enum alpnid)rr->alpns[i];
+      if(alpn_rr == not_this_one) /* don't want this one */
+        continue;
+      switch(alpn_rr) {
+      case ALPN_h3:
+        if((data->state.http_neg.allowed & CURL_HTTP_V3x) &&
+           cf_hc_may_h3(cf, data)) {
+          return alpn_rr;
+        }
+        break;
+      case ALPN_h2:
+        if(data->state.http_neg.allowed & CURL_HTTP_V2x) {
+          return alpn_rr;
+        }
+        break;
+      case ALPN_h1:
+        if(data->state.http_neg.allowed & CURL_HTTP_V1x) {
+          return alpn_rr;
+        }
+        break;
+      default: /* ignore */
+        break;
+      }
+    }
+  }
+#else
+  (void)cf;
+  (void)data;
+  (void)not_this_one;
+#endif
+  return ALPN_none;
+}
+
+static enum alpnid cf_hc_get_pref_alpn(struct Curl_cfilter *cf,
+                                       struct Curl_easy *data,
+                                       enum alpnid not_this_one)
+{
+  if((data->state.http_neg.preferred & data->state.http_neg.allowed)) {
+    switch(data->state.http_neg.preferred) {
+    case CURL_HTTP_V3x:
+      if(cf_hc_may_h3(cf, data) && (ALPN_h3 != not_this_one))
+        return ALPN_h3;
+      break;
+    case CURL_HTTP_V2x:
+      if(ALPN_h2 != not_this_one)
+        return ALPN_h2;
+      break;
+    case CURL_HTTP_V1x:
+      /* If we are trying h2 already, h1 is already used as fallback */
+      if((ALPN_h1 != not_this_one) && (ALPN_h2 != not_this_one))
+        return ALPN_h1;
+      break;
+    default:
+      break;
+    }
+  }
+  return ALPN_none;
+}
+
+static enum alpnid cf_hc_get_first_alpn(struct Curl_cfilter *cf,
+                                        struct Curl_easy *data,
+                                        http_majors choices,
+                                        enum alpnid not_this_one)
+{
+  /* When told to not try h2, we also do not try h1 and vice versa */
+  bool allow_h1_or_h2 = (not_this_one != ALPN_h1) &&
+                        (not_this_one != ALPN_h2);
+  if((ALPN_h3 != not_this_one) && (choices & CURL_HTTP_V3x) &&
+     cf_hc_may_h3(cf, data)) {
+    return ALPN_h3;
+  }
+  if(allow_h1_or_h2 && (choices & CURL_HTTP_V2x)) {
+    return ALPN_h2;
+  }
+  if(allow_h1_or_h2 && (choices & CURL_HTTP_V1x)) {
+    return ALPN_h1;
+  }
+  return ALPN_none;
+}
+
+static CURLcode cf_hc_set_baller1(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  enum alpnid alpn1 = ALPN_none;
+  VERBOSE(const char *source = "HTTPS-RR");
+
+  DEBUGASSERT(cf->conn->bits.tls_enable_alpn);
+
+  alpn1 = cf_hc_get_httpsrr_alpn(cf, data, ALPN_none);
+  if(alpn1 == ALPN_none) {
+    /* preference is configured and allowed, can we use it? */
+    VERBOSE(source = "preferred version");
+    alpn1 = cf_hc_get_pref_alpn(cf, data, ALPN_none);
+  }
+  if(alpn1 == ALPN_none) {
+    VERBOSE(source = "wanted versions");
+    alpn1 = cf_hc_get_first_alpn(cf, data,
+                                 data->state.http_neg.wanted,
+                                 ALPN_none);
+  }
+  if(alpn1 == ALPN_none) {
+    VERBOSE(source = "allowed versions");
+    alpn1 = cf_hc_get_first_alpn(cf, data,
+                                 data->state.http_neg.allowed,
+                                 ALPN_none);
+  }
+
+  if(alpn1 == ALPN_none) {
+    /* None of the wanted/allowed HTTP versions could be chosen */
+    if(ctx->check_h3_result) {
+      CURL_TRC_CF(data, cf, "unable to use HTTP/3");
+      return ctx->check_h3_result;
+    }
+    CURL_TRC_CF(data, cf, "unable to select HTTP version");
+    return CURLE_FAILED_INIT;
+  }
+
+  cf_hc_baller_assign(&ctx->ballers[0], alpn1, ctx->def_transport);
+  ctx->baller_count = 1;
+  CURL_TRC_CF(data, cf, "1st attempt uses %s from %s",
+              ctx->ballers[0].name, source);
+
+  switch(alpn1) {
+  case ALPN_h1:
+    /* We really want h1, switch off h2 to make it disappear in ALPN */
+    data->state.http_neg.wanted &= (uint8_t)~CURL_HTTP_V2x;
+    break;
+  default:
+    break;
+  }
+
+  return CURLE_OK;
+}
+
+static void cf_hc_set_baller2(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  enum alpnid alpn2 = ALPN_none, alpn1 = ctx->ballers[0].alpn_id;
+  VERBOSE(const char *source = "HTTPS-RR");
+
+  if(ctx->ballers_complete)
+    return; /* already done */
+  if(!ctx->httpsrr_resolved)
+    return; /* HTTPS-RR pending */
+
+  alpn2 = cf_hc_get_httpsrr_alpn(cf, data, alpn1);
+  if(alpn2 == ALPN_none) {
+    /* preference is configured and allowed, can we use it? */
+    VERBOSE(source = "preferred version");
+    alpn2 = cf_hc_get_pref_alpn(cf, data, alpn1);
+  }
+  if(alpn2 == ALPN_none) {
+    VERBOSE(source = "wanted versions");
+    alpn2 = cf_hc_get_first_alpn(cf, data,
+                                 data->state.http_neg.wanted,
+                                 alpn1);
+  }
+
+  if(alpn2 != ALPN_none) {
+    cf_hc_baller_assign(&ctx->ballers[1], alpn2, ctx->def_transport);
+    ctx->baller_count = 2;
+    CURL_TRC_CF(data, cf, "2nd attempt uses %s from %s",
+                ctx->ballers[1].name, source);
+  }
+  ctx->ballers_complete = TRUE;
+}
+
 static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
                               struct Curl_easy *data,
                               bool *done)
 {
   struct cf_hc_ctx *ctx = cf->ctx;
   CURLcode result = CURLE_OK;
-  size_t i, failed_ballers;
 
   if(cf->connected) {
     *done = TRUE;
@@ -304,24 +491,45 @@
   }
 
   *done = FALSE;
+
+  if(!ctx->httpsrr_resolved) {
+    ctx->httpsrr_resolved = Curl_conn_dns_resolved_https(data, cf->sockindex);
+#ifdef DEBUGBUILD
+    if(!ctx->httpsrr_resolved && getenv("CURL_DBG_AWAIT_HTTPSRR")) {
+      CURL_TRC_CF(data, cf, "awaiting HTTPS-RR");
+      return CURLE_OK;
+    }
+#endif
+  }
+
   switch(ctx->state) {
+  case CF_HC_RESOLV:
+    ctx->state = CF_HC_INIT;
+    FALLTHROUGH();
+
   case CF_HC_INIT:
     DEBUGASSERT(!cf->next);
-    for(i = 0; i < ctx->baller_count; i++)
-      DEBUGASSERT(!ctx->ballers[i].cf);
     CURL_TRC_CF(data, cf, "connect, init");
+    result = cf_hc_set_baller1(cf, data);
+    if(result) {
+      ctx->result = result;
+      ctx->state = CF_HC_FAILURE;
+      goto out;
+    }
+    cf_hc_set_baller2(cf, data);
     ctx->started = *Curl_pgrs_now(data);
-    cf_hc_baller_init(&ctx->ballers[0], cf, data, ctx->ballers[0].transport);
-    if(ctx->baller_count > 1) {
+    cf_hc_baller_init(&ctx->ballers[0], cf, data);
+    if((ctx->baller_count > 1) || !ctx->ballers_complete) {
       Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS);
-      CURL_TRC_CF(data, cf, "set next attempt to start in %" FMT_TIMEDIFF_T
-                  "ms", ctx->soft_eyeballs_timeout_ms);
     }
     ctx->state = CF_HC_CONNECT;
     FALLTHROUGH();
 
   case CF_HC_CONNECT:
-    if(cf_hc_baller_is_active(&ctx->ballers[0])) {
+    if(!ctx->ballers_complete)
+      cf_hc_set_baller2(cf, data);
+
+    if(cf_hc_baller_is_connecting(&ctx->ballers[0])) {
       result = cf_hc_baller_connect(&ctx->ballers[0], cf, data, done);
       if(!result && *done) {
         result = baller_connected(cf, data, &ctx->ballers[0]);
@@ -329,12 +537,11 @@
       }
     }
 
-    if(time_to_start_next(cf, data, 1, *Curl_pgrs_now(data))) {
-      cf_hc_baller_init(&ctx->ballers[1], cf, data, ctx->ballers[1].transport);
+    if(time_to_start_baller2(cf, data)) {
+      cf_hc_baller_init(&ctx->ballers[1], cf, data);
     }
 
-    if((ctx->baller_count > 1) && cf_hc_baller_is_active(&ctx->ballers[1])) {
-      CURL_TRC_CF(data, cf, "connect, check %s", ctx->ballers[1].name);
+    if(cf_hc_baller_is_connecting(&ctx->ballers[1])) {
       result = cf_hc_baller_connect(&ctx->ballers[1], cf, data, done);
       if(!result && *done) {
         result = baller_connected(cf, data, &ctx->ballers[1]);
@@ -342,21 +549,12 @@
       }
     }
 
-    failed_ballers = 0;
-    for(i = 0; i < ctx->baller_count; i++) {
-      if(ctx->ballers[i].result)
-        ++failed_ballers;
-    }
-
-    if(failed_ballers == ctx->baller_count) {
+    if(ctx->ballers[0].result &&
+       (ctx->ballers[1].result ||
+        (ctx->ballers_complete && (ctx->baller_count < 2)))) {
       /* all have failed. we give up */
       CURL_TRC_CF(data, cf, "connect, all attempts failed");
-      for(i = 0; i < ctx->baller_count; i++) {
-        if(ctx->ballers[i].result) {
-          result = ctx->ballers[i].result;
-          break;
-        }
-      }
+      ctx->result = result = ctx->ballers[0].result;
       ctx->state = CF_HC_FAILURE;
       goto out;
     }
@@ -400,7 +598,7 @@
   for(i = 0; i < ctx->baller_count; i++) {
     struct cf_hc_baller *b = &ctx->ballers[i];
     bool bdone = FALSE;
-    if(!cf_hc_baller_is_active(b) || b->shutdown)
+    if(!cf_hc_baller_is_connecting(b) || b->shutdown)
       continue;
     b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone);
     if(b->result || bdone)
@@ -433,11 +631,11 @@
 
     for(i = 0; (i < ctx->baller_count) && !result; i++) {
       struct cf_hc_baller *b = &ctx->ballers[i];
-      if(!cf_hc_baller_is_active(b))
+      if(!cf_hc_baller_is_connecting(b))
         continue;
       result = Curl_conn_cf_adjust_pollset(b->cf, data, ps);
     }
-    CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %d socks", result, ps->n);
+    CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %u socks", result, ps->n);
   }
   return result;
 }
@@ -536,7 +734,7 @@
 static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   CURL_TRC_CF(data, cf, "close");
-  cf_hc_reset(cf, data);
+  cf_hc_ctx_close(data, cf->ctx);
   cf->connected = FALSE;
 
   if(cf->next) {
@@ -550,13 +748,12 @@
   struct cf_hc_ctx *ctx = cf->ctx;
 
   CURL_TRC_CF(data, cf, "destroy");
-  cf_hc_reset(cf, data);
-  Curl_safefree(ctx);
+  cf_hc_ctx_destroy(data, ctx);
 }
 
 struct Curl_cftype Curl_cft_http_connect = {
   "HTTPS-CONNECT",
-  0,
+  CF_TYPE_SETUP | CF_TYPE_HTTPSRR,
   CURL_LOG_LVL_NONE,
   cf_hc_destroy,
   cf_hc_connect,
@@ -574,59 +771,42 @@
 
 static CURLcode cf_hc_create(struct Curl_cfilter **pcf,
                              struct Curl_easy *data,
-                             enum alpnid *alpnids, size_t alpn_count,
                              uint8_t def_transport)
 {
   struct Curl_cfilter *cf = NULL;
   struct cf_hc_ctx *ctx;
   CURLcode result = CURLE_OK;
-  size_t i;
 
   ctx = curlx_calloc(1, sizeof(*ctx));
   if(!ctx) {
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
-
-  DEBUGASSERT(alpnids);
-  DEBUGASSERT(alpn_count);
-  DEBUGASSERT(alpn_count <= CURL_ARRAYSIZE(ctx->ballers));
-  if(!alpn_count || (alpn_count > CURL_ARRAYSIZE(ctx->ballers))) {
-    failf(data, "https-connect filter create with unsupported %zu ALPN ids",
-          alpn_count);
-    result = CURLE_FAILED_INIT;
-    goto out;
-  }
-
-  for(i = 0; i < alpn_count; ++i)
-    cf_hc_baller_assign(&ctx->ballers[i], alpnids[i], def_transport);
-  for(; i < CURL_ARRAYSIZE(ctx->ballers); ++i)
-    ctx->ballers[i].alpn_id = ALPN_none;
-  ctx->baller_count = alpn_count;
+  ctx->def_transport = def_transport;
+  ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout;
+  ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2;
 
   result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx);
   if(result)
     goto out;
   ctx = NULL;
-  cf_hc_reset(cf, data);
 
 out:
   *pcf = result ? NULL : cf;
-  curlx_free(ctx);
+  cf_hc_ctx_destroy(data, ctx);
   return result;
 }
 
-static CURLcode cf_http_connect_add(struct Curl_easy *data,
-                                    struct connectdata *conn,
-                                    int sockindex,
-                                    enum alpnid *alpn_ids, size_t alpn_count,
-                                    uint8_t def_transport)
+static CURLcode cf_hc_add(struct Curl_easy *data,
+                          struct connectdata *conn,
+                          int sockindex,
+                          uint8_t def_transport)
 {
   struct Curl_cfilter *cf;
   CURLcode result = CURLE_OK;
 
   DEBUGASSERT(data);
-  result = cf_hc_create(&cf, data, alpn_ids, alpn_count, def_transport);
+  result = cf_hc_create(&cf, data, def_transport);
   if(result)
     goto out;
   Curl_conn_cf_add(data, conn, sockindex, cf);
@@ -634,138 +814,19 @@
   return result;
 }
 
-static bool cf_https_alpns_contain(enum alpnid id,
-                                   enum alpnid *list, size_t len)
-{
-  size_t i;
-  for(i = 0; i < len; ++i) {
-    if(id == list[i])
-      return TRUE;
-  }
-  return FALSE;
-}
-
 CURLcode Curl_cf_https_setup(struct Curl_easy *data,
                              struct connectdata *conn,
                              int sockindex)
 {
-  enum alpnid alpn_ids[2];
-  size_t alpn_count = 0;
   CURLcode result = CURLE_OK;
-  struct Curl_cfilter cf_fake, *cf = NULL;
 
-  (void)sockindex;
-  /* we want to log for the filter before we create it, fake it. */
-  memset(&cf_fake, 0, sizeof(cf_fake));
-  cf_fake.cft = &Curl_cft_http_connect;
-  cf = &cf_fake;
+  DEBUGASSERT(conn->scheme->protocol == CURLPROTO_HTTPS);
 
-  if(conn->bits.tls_enable_alpn) {
-#ifdef USE_HTTPSRR
-    /* Is there an HTTPSRR use its ALPNs here.
-     * We are here after having selected a connection to a host+port and
-     * can no longer change that. Any HTTPSRR advice for other hosts and ports
-     * we need to ignore. */
-    struct Curl_dns_entry *dns = data->state.dns[sockindex];
-    struct Curl_https_rrinfo *rr = dns ? dns->hinfo : NULL;
-    if(rr && !rr->no_def_alpn &&  /* ALPNs are defaults */
-       (!rr->target ||      /* for same host */
-        !rr->target[0] ||
-        (rr->target[0] == '.' &&
-         !rr->target[1])) &&
-       (rr->port < 0 ||    /* for same port */
-        rr->port == conn->remote_port)) {
-      size_t i;
-      for(i = 0; i < CURL_ARRAYSIZE(rr->alpns) &&
-                 alpn_count < CURL_ARRAYSIZE(alpn_ids); ++i) {
-        enum alpnid alpn = rr->alpns[i];
-        if(cf_https_alpns_contain(alpn, alpn_ids, alpn_count))
-          continue;
-        switch(alpn) {
-        case ALPN_h3:
-          if(Curl_conn_may_http3(data, conn, conn->transport_wanted))
-            break;  /* not possible */
-          if(data->state.http_neg.allowed & CURL_HTTP_V3x) {
-            CURL_TRC_CF(data, cf, "adding h3 via HTTPS-RR");
-            alpn_ids[alpn_count++] = alpn;
-          }
-          break;
-        case ALPN_h2:
-          if(data->state.http_neg.allowed & CURL_HTTP_V2x) {
-            CURL_TRC_CF(data, cf, "adding h2 via HTTPS-RR");
-            alpn_ids[alpn_count++] = alpn;
-          }
-          break;
-        case ALPN_h1:
-          if(data->state.http_neg.allowed & CURL_HTTP_V1x) {
-            CURL_TRC_CF(data, cf, "adding h1 via HTTPS-RR");
-            alpn_ids[alpn_count++] = alpn;
-          }
-          break;
-        default: /* ignore */
-          break;
-        }
-      }
-    }
-#endif
+  if((conn->scheme->protocol != CURLPROTO_HTTPS) ||
+     !conn->bits.tls_enable_alpn)
+     goto out;
 
-    /* Add preferred HTTP version ALPN first */
-    if(data->state.http_neg.preferred &&
-       (alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&
-       (data->state.http_neg.preferred & data->state.http_neg.allowed)) {
-      enum alpnid alpn_pref = ALPN_none;
-      switch(data->state.http_neg.preferred) {
-      case CURL_HTTP_V3x:
-        if(!Curl_conn_may_http3(data, conn, conn->transport_wanted))
-          alpn_pref = ALPN_h3;
-        break;
-      case CURL_HTTP_V2x:
-        alpn_pref = ALPN_h2;
-        break;
-      case CURL_HTTP_V1x:
-        alpn_pref = ALPN_h1;
-        break;
-      default:
-        break;
-      }
-      if(alpn_pref &&
-         !cf_https_alpns_contain(alpn_pref, alpn_ids, alpn_count)) {
-        alpn_ids[alpn_count++] = alpn_pref;
-      }
-    }
-
-    if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&
-       (data->state.http_neg.wanted & CURL_HTTP_V3x) &&
-       !cf_https_alpns_contain(ALPN_h3, alpn_ids, alpn_count)) {
-      result = Curl_conn_may_http3(data, conn, conn->transport_wanted);
-      if(!result) {
-        CURL_TRC_CF(data, cf, "adding wanted h3");
-        alpn_ids[alpn_count++] = ALPN_h3;
-      }
-      else if(data->state.http_neg.wanted == CURL_HTTP_V3x)
-        goto out; /* only h3 allowed, not possible, error out */
-    }
-    if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&
-       (data->state.http_neg.wanted & CURL_HTTP_V2x) &&
-       !cf_https_alpns_contain(ALPN_h2, alpn_ids, alpn_count)) {
-      CURL_TRC_CF(data, cf, "adding wanted h2");
-      alpn_ids[alpn_count++] = ALPN_h2;
-    }
-    else if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&
-            (data->state.http_neg.wanted & CURL_HTTP_V1x) &&
-            !cf_https_alpns_contain(ALPN_h1, alpn_ids, alpn_count)) {
-      CURL_TRC_CF(data, cf, "adding wanted h1");
-      alpn_ids[alpn_count++] = ALPN_h1;
-    }
-  }
-
-  /* If we identified ALPNs to use, install our filter. Otherwise,
-   * install nothing, so our call will use a default connect setup. */
-  if(alpn_count) {
-    result = cf_http_connect_add(data, conn, sockindex,
-                                 alpn_ids, alpn_count,
-                                 conn->transport_wanted);
-  }
+  result = cf_hc_add(data, conn, sockindex, conn->transport_wanted);
 
 out:
   return result;
diff --git a/Utilities/cmcurl/lib/cf-https-connect.h b/Utilities/cmcurl/lib/cf-https-connect.h
index df29c8d..3160c03 100644
--- a/Utilities/cmcurl/lib/cf-https-connect.h
+++ b/Utilities/cmcurl/lib/cf-https-connect.h
@@ -31,15 +31,9 @@
 struct Curl_easy;
 struct connectdata;
 struct Curl_cftype;
-struct Curl_dns_entry;
 
 extern struct Curl_cftype Curl_cft_http_connect;
 
-CURLcode Curl_cf_http_connect_add(struct Curl_easy *data,
-                                  struct connectdata *conn,
-                                  int sockindex,
-                                  bool try_h3, bool try_h21);
-
 CURLcode Curl_cf_https_setup(struct Curl_easy *data,
                              struct connectdata *conn,
                              int sockindex);
diff --git a/Utilities/cmcurl/lib/cf-ip-happy.c b/Utilities/cmcurl/lib/cf-ip-happy.c
index dfbf4ae..f67273e 100644
--- a/Utilities/cmcurl/lib/cf-ip-happy.c
+++ b/Utilities/cmcurl/lib/cf-ip-happy.c
@@ -49,6 +49,7 @@
 #include "urldata.h"
 #include "connect.h"
 #include "cfilters.h"
+#include "cf-dns.h"
 #include "cf-ip-happy.h"
 #include "curl_addrinfo.h"
 #include "curl_trc.h"
@@ -91,9 +92,11 @@
 }
 
 #ifdef UNITTESTS
-/* used by unit2600.c */
-void Curl_debug_set_transport_provider(uint8_t transport,
-                                       cf_ip_connect_create *cf_create)
+/* @unittest 2600 */
+UNITTEST void debug_set_transport_provider(
+  uint8_t transport, cf_ip_connect_create *cf_create);
+UNITTEST void debug_set_transport_provider(
+  uint8_t transport, cf_ip_connect_create *cf_create)
 {
   size_t i;
   for(i = 0; i < CURL_ARRAYSIZE(transport_providers); ++i) {
@@ -106,59 +109,46 @@
 #endif /* UNITTESTS */
 
 struct cf_ai_iter {
-  const struct Curl_addrinfo *head;
-  const struct Curl_addrinfo *last;
+  struct Curl_cfilter *cf;
   int ai_family;
-  int n;
+  unsigned int n;
 };
 
 static void cf_ai_iter_init(struct cf_ai_iter *iter,
-                            const struct Curl_addrinfo *list,
+                            struct Curl_cfilter *cf,
                             int ai_family)
 {
-  iter->head = list;
+  iter->cf = cf;
   iter->ai_family = ai_family;
-  iter->last = NULL;
-  iter->n = -1;
+  iter->n = 0;
 }
 
-static const struct Curl_addrinfo *cf_ai_iter_next(struct cf_ai_iter *iter)
+static const struct Curl_addrinfo *cf_ai_iter_next(struct cf_ai_iter *iter,
+                                                   struct Curl_easy *data)
 {
   const struct Curl_addrinfo *addr;
-  if(iter->n < 0) {
+
+  if(!iter->cf)
+    return NULL;
+
+  addr = Curl_conn_dns_get_ai(data, iter->cf->sockindex,
+                              iter->ai_family, iter->n);
+  if(addr)
     iter->n++;
-    for(addr = iter->head; addr; addr = addr->ai_next) {
-      if(addr->ai_family == iter->ai_family)
-        break;
-    }
-    iter->last = addr;
-  }
-  else if(iter->last) {
-    iter->n++;
-    for(addr = iter->last->ai_next; addr; addr = addr->ai_next) {
-      if(addr->ai_family == iter->ai_family)
-        break;
-    }
-    iter->last = addr;
-  }
-  return iter->last;
+  return addr;
 }
 
-static bool cf_ai_iter_has_more(struct cf_ai_iter *iter)
+static bool cf_ai_iter_has_more(struct cf_ai_iter *iter,
+                                struct Curl_easy *data)
 {
-  const struct Curl_addrinfo *addr = iter->last ? iter->last->ai_next :
-    ((iter->n < 0) ? iter->head : NULL);
-  while(addr) {
-    if(addr->ai_family == iter->ai_family)
-      return TRUE;
-    addr = addr->ai_next;
-  }
-  return FALSE;
+  return (iter->cf &&
+          !!Curl_conn_dns_get_ai(data, iter->cf->sockindex,
+                                 iter->ai_family, iter->n));
 }
 
 struct cf_ip_attempt {
   struct cf_ip_attempt *next;
-  const struct Curl_addrinfo *addr;  /* List of addresses to try, not owned */
+  struct Curl_sockaddr_ex addr;
   struct Curl_cfilter *cf;           /* current sub-cfilter connecting */
   cf_ip_connect_create *cf_create;
   struct curltime started;           /* start of current attempt */
@@ -185,7 +175,7 @@
 static CURLcode cf_ip_attempt_new(struct cf_ip_attempt **pa,
                                   struct Curl_cfilter *cf,
                                   struct Curl_easy *data,
-                                  const struct Curl_addrinfo *addr,
+                                  struct Curl_sockaddr_ex *addr,
                                   int ai_family,
                                   uint8_t transport,
                                   cf_ip_connect_create *cf_create)
@@ -199,14 +189,14 @@
   if(!a)
     return CURLE_OUT_OF_MEMORY;
 
-  a->addr = addr;
+  a->addr = *addr;
   a->ai_family = ai_family;
   a->transport = transport;
   a->result = CURLE_OK;
   a->cf_create = cf_create;
   *pa = a;
 
-  result = a->cf_create(&a->cf, data, cf->conn, a->addr, transport);
+  result = a->cf_create(&a->cf, data, cf->conn, &a->addr, a->transport);
   if(result)
     goto out;
 
@@ -238,8 +228,12 @@
         a->connected = TRUE;
       }
     }
-    else if(a->result == CURLE_WEIRD_SERVER_REPLY)
-      a->inconclusive = TRUE;
+    else {
+      if(a->result == CURLE_WEIRD_SERVER_REPLY)
+        a->inconclusive = TRUE;
+      if(a->cf)
+        Curl_conn_cf_discard_chain(&a->cf, data);
+    }
   }
   return a->result;
 }
@@ -256,6 +250,7 @@
   struct curltime last_attempt_started;
   timediff_t attempt_delay_ms;
   int last_attempt_ai_family;
+  uint32_t max_concurrent;
   uint8_t transport;
 };
 
@@ -263,19 +258,18 @@
                                       struct Curl_cfilter *cf,
                                       struct Curl_easy *data)
 {
-  struct Curl_cfilter *cf_prev = a->cf;
   struct Curl_cfilter *wcf;
   CURLcode result;
 
-  /* When restarting, we tear down and existing filter *after* we
-   * started up the new one. This gives us a new socket number and
-   * probably a new local port. Which may prevent confusion. */
+  if(a->cf)
+    Curl_conn_cf_discard_chain(&a->cf, data);
+
   a->result = CURLE_OK;
   a->connected = FALSE;
   a->inconclusive = FALSE;
   a->cf = NULL;
 
-  result = a->cf_create(&a->cf, data, cf->conn, a->addr, a->transport);
+  result = a->cf_create(&a->cf, data, cf->conn, &a->addr, a->transport);
   if(!result) {
     bool dummy;
     /* the new filter might have sub-filters */
@@ -285,8 +279,6 @@
     }
     a->result = cf_ip_attempt_connect(a, data, &dummy);
   }
-  if(cf_prev)
-    Curl_conn_cf_discard_chain(&cf_prev, data);
   return result;
 }
 
@@ -304,54 +296,76 @@
   bs->winner = NULL;
 }
 
-static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs, int ip_version,
-                                   const struct Curl_addrinfo *addr_list,
+static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs,
+                                   struct Curl_cfilter *cf,
                                    cf_ip_connect_create *cf_create,
                                    uint8_t transport,
-                                   timediff_t attempt_delay_ms)
+                                   timediff_t attempt_delay_ms,
+                                   uint32_t max_concurrent)
 {
   memset(bs, 0, sizeof(*bs));
   bs->cf_create = cf_create;
   bs->transport = transport;
   bs->attempt_delay_ms = attempt_delay_ms;
+  bs->max_concurrent = max_concurrent;
   bs->last_attempt_ai_family = AF_INET; /* so AF_INET6 is next */
 
   if(transport == TRNSPRT_UNIX) {
 #ifdef USE_UNIX_SOCKETS
-    cf_ai_iter_init(&bs->addr_iter, addr_list, AF_UNIX);
+    cf_ai_iter_init(&bs->addr_iter, cf, AF_UNIX);
 #else
     return CURLE_UNSUPPORTED_PROTOCOL;
 #endif
   }
   else { /* TCP/UDP/QUIC */
 #ifdef USE_IPV6
-    if(ip_version == CURL_IPRESOLVE_V6)
-      cf_ai_iter_init(&bs->addr_iter, NULL, AF_INET);
-    else
-      cf_ai_iter_init(&bs->addr_iter, addr_list, AF_INET);
-
-    if(ip_version == CURL_IPRESOLVE_V4)
-      cf_ai_iter_init(&bs->ipv6_iter, NULL, AF_INET6);
-    else
-      cf_ai_iter_init(&bs->ipv6_iter, addr_list, AF_INET6);
-#else
-    (void)ip_version;
-    cf_ai_iter_init(&bs->addr_iter, addr_list, AF_INET);
+    cf_ai_iter_init(&bs->ipv6_iter, cf, AF_INET6);
 #endif
+    cf_ai_iter_init(&bs->addr_iter, cf, AF_INET);
   }
   return CURLE_OK;
 }
 
+static void cf_ip_ballers_prune(struct cf_ip_ballers *bs,
+                                struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                uint32_t max_concurrent)
+{
+  struct cf_ip_attempt *a = NULL, **panchor;
+  uint32_t ongoing = 0;
+
+  for(a = bs->running; a; a = a->next) {
+    if(!a->result && !a->connected)
+      ++ongoing;
+  }
+
+  panchor = &bs->running;
+  while(*panchor && (ongoing > max_concurrent)) {
+    a = *panchor;
+    if(!a->result && !a->connected) {
+      *panchor = a->next;
+      a->next = NULL;
+      cf_ip_attempt_free(a, data);
+      --ongoing;
+      CURL_TRC_CF(data, cf, "discarding oldest attempt to keep limit");
+    }
+    else {
+      panchor = &a->next;
+    }
+  }
+}
+
 static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs,
                                   struct Curl_cfilter *cf,
                                   struct Curl_easy *data,
+                                  bool dns_resolved,
                                   bool *connected)
 {
   CURLcode result = CURLE_OK;
   struct cf_ip_attempt *a = NULL, **panchor;
   bool do_more;
   timediff_t next_expire_ms;
-  int inconclusive, ongoing;
+  uint32_t inconclusive, ongoing;
   VERBOSE(int i);
 
   if(bs->winner)
@@ -389,7 +403,7 @@
   }
   if(bs->running)
     CURL_TRC_CF(data, cf, "checked connect attempts: "
-                "%d ongoing, %d inconclusive", ongoing, inconclusive);
+                "%u ongoing, %u inconclusive", ongoing, inconclusive);
 
   /* no attempt connected yet, start another one? */
   if(!ongoing) {
@@ -398,10 +412,10 @@
     do_more = TRUE;
   }
   else {
-    bool more_possible = cf_ai_iter_has_more(&bs->addr_iter);
+    bool more_possible = cf_ai_iter_has_more(&bs->addr_iter, data);
 #ifdef USE_IPV6
     if(!more_possible)
-      more_possible = cf_ai_iter_has_more(&bs->ipv6_iter);
+      more_possible = cf_ai_iter_has_more(&bs->ipv6_iter, data);
 #endif
     do_more = more_possible &&
       (curlx_ptimediff_ms(Curl_pgrs_now(data), &bs->last_attempt_started) >=
@@ -414,27 +428,42 @@
   if(do_more) {
     /* start the next attempt if there is another ip address to try.
      * Alternate between address families when possible. */
-    const struct Curl_addrinfo *addr = NULL;
+    const struct Curl_addrinfo *ai = NULL;
     int ai_family = 0;
+    CURL_TRC_CF(data, cf, "want to do more");
 #ifdef USE_IPV6
     if((bs->last_attempt_ai_family == AF_INET) ||
-       !cf_ai_iter_has_more(&bs->addr_iter)) {
-      addr = cf_ai_iter_next(&bs->ipv6_iter);
+       !cf_ai_iter_has_more(&bs->addr_iter, data)) {
+      ai = cf_ai_iter_next(&bs->ipv6_iter, data);
       ai_family = bs->ipv6_iter.ai_family;
+      CURL_TRC_CF(data, cf, "check for next AAAA address: %s",
+                  ai ? "found" : "none");
     }
 #endif
-    if(!addr) {
-      addr = cf_ai_iter_next(&bs->addr_iter);
+    if(!ai) {
+      ai = cf_ai_iter_next(&bs->addr_iter, data);
       ai_family = bs->addr_iter.ai_family;
+      CURL_TRC_CF(data, cf, "check for next A address: %s",
+                  ai ? "found" : "none");
     }
     /* We are (re-)starting attempts. We are not interested in
      * keeping old failure information. The new attempt will either
      * succeed or persist new failure. */
     Curl_reset_fail(data);
 
-    if(addr) {  /* try another address */
-      result = cf_ip_attempt_new(&a, cf, data, addr, ai_family,
-                                bs->transport, bs->cf_create);
+    if(ai) {  /* try another address */
+      struct Curl_sockaddr_ex addr;
+
+      /* Discard oldest to make room for new attempt */
+      if(bs->max_concurrent)
+        cf_ip_ballers_prune(bs, cf, data, bs->max_concurrent - 1);
+
+      result = Curl_socket_addr_from_ai(&addr, ai, bs->transport);
+      if(result)
+        goto out;
+
+      result = cf_ip_attempt_new(&a, cf, data, &addr, ai_family,
+                                 bs->transport, bs->cf_create);
       CURL_TRC_CF(data, cf, "starting %s attempt for ipv%s -> %d",
                   bs->running ? "next" : "first",
                   (ai_family == AF_INET) ? "4" : "6", result);
@@ -483,7 +512,7 @@
       /* attempt timeout for restart has not expired yet */
       goto out;
     }
-    else if(!ongoing) {
+    else if(!ongoing && dns_resolved) {
       /* no more addresses, no inconclusive attempts */
       CURL_TRC_CF(data, cf, "no more attempts to try");
       result = CURLE_COULDNT_CONNECT;
@@ -509,10 +538,10 @@
       return CURLE_OPERATION_TIMEDOUT;
     }
 
-    more_possible = cf_ai_iter_has_more(&bs->addr_iter);
+    more_possible = cf_ai_iter_has_more(&bs->addr_iter, data);
 #ifdef USE_IPV6
     if(!more_possible)
-      more_possible = cf_ai_iter_has_more(&bs->ipv6_iter);
+      more_possible = cf_ai_iter_has_more(&bs->ipv6_iter, data);
 #endif
     if(more_possible) {
       timediff_t expire_ms, elapsed_ms;
@@ -543,7 +572,7 @@
   *done = TRUE;
   for(a = bs->running; a; a = a->next) {
     bool bdone = FALSE;
-    if(a->shutdown)
+    if(a->shutdown || !a->cf)
       continue;
     a->result = a->cf->cft->do_shutdown(a->cf, data, &bdone);
     if(a->result || bdone)
@@ -576,7 +605,7 @@
   for(a = bs->running; a; a = a->next) {
     if(a->result)
       continue;
-    if(a->cf->cft->has_data_pending(a->cf, data))
+    if(a->cf && a->cf->cft->has_data_pending(a->cf, data))
       return TRUE;
   }
   return FALSE;
@@ -592,7 +621,7 @@
   memset(&tmax, 0, sizeof(tmax));
   for(a = bs->running; a; a = a->next) {
     memset(&t, 0, sizeof(t));
-    if(!a->cf->cft->query(a->cf, data, query, NULL, &t)) {
+    if(a->cf && !a->cf->cft->query(a->cf, data, query, NULL, &t)) {
       if((t.tv_sec || t.tv_usec) && curlx_ptimediff_us(&t, &tmax) > 0)
         tmax = t;
     }
@@ -607,8 +636,8 @@
   struct cf_ip_attempt *a;
 
   for(a = bs->running; a; a = a->next) {
-    if(!a->cf->cft->query(a->cf, data, CF_QUERY_CONNECT_REPLY_MS,
-                          &breply_ms, NULL)) {
+    if(a->cf && !a->cf->cft->query(a->cf, data, CF_QUERY_CONNECT_REPLY_MS,
+                                   &breply_ms, NULL)) {
       if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms))
         reply_ms = breply_ms;
     }
@@ -628,6 +657,7 @@
   cf_connect_state state;
   struct cf_ip_ballers ballers;
   struct curltime started;
+  BIT(dns_resolved);
 };
 
 static CURLcode is_connected(struct Curl_cfilter *cf,
@@ -638,12 +668,12 @@
   struct connectdata *conn = cf->conn;
   CURLcode result;
 
-  result = cf_ip_ballers_run(&ctx->ballers, cf, data, connected);
+  result = cf_ip_ballers_run(&ctx->ballers, cf, data,
+                             (bool)ctx->dns_resolved, connected);
 
   if(!result)
     return CURLE_OK;
-
-  {
+  else {
     const char *hostname, *proxy_name = NULL;
     char viamsg[160];
 #ifndef CURL_DISABLE_PROXY
@@ -662,14 +692,14 @@
     else
 #endif
     {
-      int port;
+      uint16_t port;
       if(cf->sockindex == SECONDARYSOCKET)
         port = conn->secondary_port;
       else if(cf->conn->bits.conn_to_port)
         port = conn->conn_to_port;
       else
         port = conn->remote_port;
-      curl_msnprintf(viamsg, sizeof(viamsg), "port %u", port);
+      curl_msnprintf(viamsg, sizeof(viamsg), "port %d", port);
     }
 
     failf(data, "Failed to connect to %s %s %s%s%safter "
@@ -681,28 +711,22 @@
           curlx_ptimediff_ms(Curl_pgrs_now(data),
                              &data->progress.t_startsingle),
           curl_easy_strerror(result));
-  }
 
 #ifdef SOCKETIMEDOUT
-  if(SOCKETIMEDOUT == data->state.os_errno)
-    result = CURLE_OPERATION_TIMEDOUT;
+    if(SOCKETIMEDOUT == data->state.os_errno)
+      result = CURLE_OPERATION_TIMEDOUT;
 #endif
 
-  return result;
+    return result;
+  }
 }
 
-/*
- * Connect to the given host with timeout, proxy or remote does not matter.
- * There might be more than one IP address to try out.
- */
-static CURLcode start_connect(struct Curl_cfilter *cf,
-                              struct Curl_easy *data)
+#define IP_HE_MAX_CONCURRENT_ATTEMPTS     6
+
+static CURLcode cf_ip_happy_init(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
 {
   struct cf_ip_happy_ctx *ctx = cf->ctx;
-  struct Curl_dns_entry *dns = data->state.dns[cf->sockindex];
-
-  if(!dns)
-    return CURLE_FAILED_INIT;
 
   if(Curl_timeleft_ms(data) < 0) {
     /* a precaution, no need to continue if time already is up */
@@ -712,9 +736,10 @@
 
   CURL_TRC_CF(data, cf, "init ip ballers for transport %u", ctx->transport);
   ctx->started = *Curl_pgrs_now(data);
-  return cf_ip_ballers_init(&ctx->ballers, cf->conn->ip_version,
-                            dns->addr, ctx->cf_create, ctx->transport,
-                            data->set.happy_eyeballs_timeout);
+  return cf_ip_ballers_init(&ctx->ballers, cf,
+                            ctx->cf_create, ctx->transport,
+                            data->set.happy_eyeballs_timeout,
+                            IP_HE_MAX_CONCURRENT_ATTEMPTS);
 }
 
 static void cf_ip_happy_ctx_clear(struct Curl_cfilter *cf,
@@ -727,6 +752,12 @@
   cf_ip_ballers_clear(cf, data, &ctx->ballers);
 }
 
+static void cf_ip_happy_ctx_destroy(struct cf_ip_happy_ctx *ctx)
+{
+  if(ctx)
+    curlx_free(ctx);
+}
+
 static CURLcode cf_ip_happy_shutdown(struct Curl_cfilter *cf,
                                      struct Curl_easy *data,
                                      bool *done)
@@ -754,7 +785,7 @@
 
   if(!cf->connected) {
     result = cf_ip_ballers_pollset(&ctx->ballers, data, ps);
-    CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %d socks", result, ps->n);
+    CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %u socks", result, ps->n);
   }
   return result;
 }
@@ -766,6 +797,10 @@
   struct cf_ip_happy_ctx *ctx = cf->ctx;
   CURLcode result = CURLE_OK;
 
+  /* -Werror=null-dereference finds false positives suddenly. */
+  if(!data)
+    return CURLE_FAILED_INIT;
+
   if(cf->connected) {
     *done = TRUE;
     return CURLE_OK;
@@ -774,13 +809,24 @@
   DEBUGASSERT(ctx);
   *done = FALSE;
 
+  if(!ctx->dns_resolved) {
+    result = Curl_conn_dns_result(cf->conn, cf->sockindex);
+    if(!result)
+      ctx->dns_resolved = TRUE;
+    else if(result == CURLE_AGAIN) {
+      result = CURLE_OK;
+    }
+    else /* real error */
+      goto out;
+  }
+
   switch(ctx->state) {
   case SCFST_INIT:
     DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data));
     DEBUGASSERT(!cf->connected);
-    result = start_connect(cf, data);
+    result = cf_ip_happy_init(cf, data);
     if(result)
-      return result;
+      goto out;
     ctx->state = SCFST_WAITING;
     FALLTHROUGH();
   case SCFST_WAITING:
@@ -808,8 +854,7 @@
         bool is_ipv6;
         if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad)) {
           const char *host;
-          int port;
-          Curl_conn_get_current_host(data, cf->sockindex, &host, &port);
+          Curl_conn_get_current_host(data, cf->sockindex, &host, NULL);
           CURL_TRC_CF(data, cf, "Connected to %s (%s) port %u",
                       host, ipquad.remote_ip, ipquad.remote_port);
         }
@@ -822,6 +867,7 @@
     *done = TRUE;
     break;
   }
+out:
   return result;
 }
 
@@ -895,14 +941,13 @@
   CURL_TRC_CF(data, cf, "destroy");
   if(ctx) {
     cf_ip_happy_ctx_clear(cf, data);
+    cf_ip_happy_ctx_destroy(ctx);
   }
-  /* release any resources held in state */
-  Curl_safefree(ctx);
 }
 
 struct Curl_cftype Curl_cft_ip_happy = {
   "HAPPY-EYEBALLS",
-  0,
+  CF_TYPE_SETUP,
   CURL_LOG_LVL_NONE,
   cf_ip_happy_destroy,
   cf_ip_happy_connect,
@@ -952,8 +997,8 @@
 
 out:
   if(result) {
-    Curl_safefree(*pcf);
-    curlx_free(ctx);
+    curlx_safefree(*pcf);
+    cf_ip_happy_ctx_destroy(ctx);
   }
   return result;
 }
diff --git a/Utilities/cmcurl/lib/cf-ip-happy.h b/Utilities/cmcurl/lib/cf-ip-happy.h
index 71f2122..547ee4b 100644
--- a/Utilities/cmcurl/lib/cf-ip-happy.h
+++ b/Utilities/cmcurl/lib/cf-ip-happy.h
@@ -25,6 +25,12 @@
  ***************************************************************************/
 #include "curl_setup.h"
 
+struct connectdata;
+struct Curl_addrinfo;
+struct Curl_cfilter;
+struct Curl_easy;
+struct Curl_sockaddr_ex;
+
 /**
  * Create a cfilter for making an "ip" connection to the
  * given address, using parameters from `conn`. The "ip" connection
@@ -39,7 +45,7 @@
 typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf,
                                       struct Curl_easy *data,
                                       struct connectdata *conn,
-                                      const struct Curl_addrinfo *ai,
+                                      struct Curl_sockaddr_ex *addr,
                                       uint8_t transport);
 
 CURLcode cf_ip_happy_insert_after(struct Curl_cfilter *cf_at,
@@ -48,9 +54,4 @@
 
 extern struct Curl_cftype Curl_cft_ip_happy;
 
-#ifdef UNITTESTS
-void Curl_debug_set_transport_provider(uint8_t transport,
-                                       cf_ip_connect_create *cf_create);
-#endif
-
 #endif /* HEADER_CURL_IP_HAPPY_H */
diff --git a/Utilities/cmcurl/lib/cf-socket.c b/Utilities/cmcurl/lib/cf-socket.c
index 2ebbe4d..b99bcde 100644
--- a/Utilities/cmcurl/lib/cf-socket.c
+++ b/Utilities/cmcurl/lib/cf-socket.c
@@ -71,6 +71,7 @@
 #include "curlx/strdup.h"
 #include "system_win32.h"
 #include "curlx/nonblock.h"
+#include "curlx/strcopy.h"
 #include "curlx/version_win32.h"
 #include "curlx/strerr.h"
 #include "curlx/strparse.h"
@@ -100,9 +101,9 @@
   defined(TCP_KEEPALIVE) || defined(TCP_KEEPALIVE_THRESHOLD) || \
   defined(TCP_KEEPINTVL) || defined(TCP_KEEPALIVE_ABORT_THRESHOLD)
 #if defined(USE_WINSOCK) || \
-   (defined(__sun) && !defined(TCP_KEEPIDLE)) || \
-   (defined(__DragonFly__) && __DragonFly_version < 500702) || \
-   (defined(_WIN32) && !defined(TCP_KEEPIDLE))
+  (defined(__sun) && !defined(TCP_KEEPIDLE)) || \
+  (defined(__DragonFly__) && __DragonFly_version < 500702) || \
+  (defined(_WIN32) && !defined(TCP_KEEPIDLE))
 /* Solaris < 11.4, DragonFlyBSD < 500702 and Windows < 10.0.16299
  * use millisecond units. */
 #define KEEPALIVE_FACTOR(x) ((x) *= 1000)
@@ -236,8 +237,15 @@
      * Note that the consequent probes will not be sent
      * at equal intervals on Solaris, but will be sent
      * using the exponential backoff algorithm. */
-    optval = curlx_sltosi(data->set.tcp_keepcnt) *
-             curlx_sltosi(data->set.tcp_keepintvl);
+    {
+      int keepcnt = curlx_sltosi(data->set.tcp_keepcnt);
+      int keepintvl = curlx_sltosi(data->set.tcp_keepintvl);
+
+      if(keepcnt > 0 && keepintvl > (INT_MAX / keepcnt))
+        optval = INT_MAX;
+      else
+        optval = keepcnt * keepintvl;
+    }
     KEEPALIVE_FACTOR(optval);
     if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD,
                   (void *)&optval, sizeof(optval)) < 0) {
@@ -258,42 +266,30 @@
 }
 
 /**
- * Assign the address `ai` to the Curl_sockaddr_ex `dest` and
- * set the transport used.
+ * Assign the addrinfo `ai` to the Curl_sockaddr_ex `addr` with
+ * transport determining socktype and protocol.
  */
-static CURLcode sock_assign_addr(struct Curl_sockaddr_ex *dest,
-                                 const struct Curl_addrinfo *ai,
-                                 uint8_t transport)
+CURLcode Curl_socket_addr_from_ai(struct Curl_sockaddr_ex *addr,
+                                  const struct Curl_addrinfo *ai,
+                                  uint8_t transport)
 {
   /*
-   * The Curl_sockaddr_ex structure is libcurl's external API curl_sockaddr
-   * structure with enough space available to directly hold any
-   * protocol-specific address structures. The variable declared here will be
-   * used to pass / receive data to/from the fopensocket callback if this has
-   * been set, before that, it is initialized from parameters.
+   * The Curl_sockaddr_ex structure is libcurl's external API
+   * curl_sockaddr structure with enough space available to directly hold
+   * any protocol-specific address structures. The variable declared here
+   * will be used to pass / receive data to/from the fopensocket callback
+   * if this has been set, before that, it is initialized from parameters.
    */
-  dest->family = ai->ai_family;
-  switch(transport) {
-  case TRNSPRT_TCP:
-    dest->socktype = SOCK_STREAM;
-    dest->protocol = IPPROTO_TCP;
-    break;
-  case TRNSPRT_UNIX:
-    dest->socktype = SOCK_STREAM;
-    dest->protocol = IPPROTO_IP;
-    break;
-  default: /* UDP and QUIC */
-    dest->socktype = SOCK_DGRAM;
-    dest->protocol = IPPROTO_UDP;
-    break;
-  }
-  dest->addrlen = (unsigned int)ai->ai_addrlen;
+  addr->family = ai->ai_family;
+  addr->socktype = Curl_socktype_for_transport(transport);
+  addr->protocol = Curl_protocol_for_transport(transport);
+  addr->addrlen = (unsigned int)ai->ai_addrlen;
 
-  DEBUGASSERT(dest->addrlen <= sizeof(dest->curl_sa_addrbuf));
-  if(dest->addrlen > sizeof(dest->curl_sa_addrbuf))
+  DEBUGASSERT(addr->addrlen <= sizeof(addr->curl_sa_addrbuf));
+  if(addr->addrlen > sizeof(addr->curl_sa_addrbuf))
     return CURLE_TOO_LARGE;
 
-  memcpy(&dest->curl_sa_addrbuf, ai->ai_addr, dest->addrlen);
+  memcpy(&addr->curl_sa_addrbuf, ai->ai_addr, addr->addrlen);
   return CURLE_OK;
 }
 
@@ -405,7 +401,7 @@
     /* if the caller does not want info back, use a local temp copy */
     addr = &dummy;
 
-  result = sock_assign_addr(addr, ai, transport);
+  result = Curl_socket_addr_from_ai(addr, ai, transport);
   if(result)
     return result;
 
@@ -530,7 +526,8 @@
 
 #ifndef CURL_DISABLE_BINDLOCAL
 static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
-                          curl_socket_t sockfd, int af, unsigned int scope)
+                          curl_socket_t sockfd, int af, unsigned int scope,
+                          uint8_t transport)
 {
   struct Curl_sockaddr_storage sa;
   struct sockaddr *sock = (struct sockaddr *)&sa;  /* bind to this address */
@@ -641,21 +638,21 @@
        * of the connection. The resolve functions should really be changed
        * to take a type parameter instead.
        */
-      int ip_version = (af == AF_INET) ?
-                       CURL_IPRESOLVE_V4 : CURL_IPRESOLVE_WHATEVER;
+      uint8_t dns_queries = (af == AF_INET) ?
+                            CURL_DNSQ_A : (CURL_DNSQ_A | CURL_DNSQ_AAAA);
 #ifdef USE_IPV6
       if(af == AF_INET6)
-        ip_version = CURL_IPRESOLVE_V6;
+        dns_queries = CURL_DNSQ_AAAA;
 #endif
 
-      (void)Curl_resolv_blocking(data, host, 80, ip_version, &h);
+      (void)Curl_resolv_blocking(data, dns_queries, host, 80, transport, &h);
       if(h) {
         int h_af = h->addr->ai_family;
         /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */
         Curl_printable_address(h->addr, myhost, sizeof(myhost));
         infof(data, "Name '%s' family %i resolved to '%s' family %i",
               host, af, myhost, h_af);
-        Curl_resolv_unlink(data, &h); /* this will NULL, potential free h */
+        Curl_dns_entry_unlink(data, &h); /* this will NULL, potential free h */
         if(af != h_af) {
           /* bad IP version combo, signal the caller to try another address
              family if available */
@@ -688,8 +685,8 @@
           if(scope_ptr) {
             /* The "myhost" string either comes from Curl_if2ip or from
                Curl_printable_address. The latter returns only numeric scope
-               IDs and the former returns none at all. So the scope ID, if
-               present, is known to be numeric */
+               IDs and the former returns none at all. Making the scope ID,
+               if present, known to be numeric */
             curl_off_t scope_id;
             if(curlx_str_number((const char **)CURL_UNCONST(&scope_ptr),
                                 &scope_id, UINT_MAX))
@@ -892,18 +889,13 @@
 };
 
 static CURLcode cf_socket_ctx_init(struct cf_socket_ctx *ctx,
-                                   const struct Curl_addrinfo *ai,
+                                   struct Curl_sockaddr_ex *addr,
                                    uint8_t transport)
 {
-  CURLcode result;
-
   memset(ctx, 0, sizeof(*ctx));
   ctx->sock = CURL_SOCKET_BAD;
   ctx->transport = transport;
-
-  result = sock_assign_addr(&ctx->addr, ai, transport);
-  if(result)
-    return result;
+  ctx->addr = *addr;
 
 #ifdef DEBUGBUILD
   {
@@ -934,7 +926,7 @@
   }
 #endif
 
-  return result;
+  return CURLE_OK;
 }
 
 static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
@@ -1143,7 +1135,8 @@
 #endif
     ) {
     result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family,
-                       Curl_ipv6_scope(&ctx->addr.curl_sa_addr));
+                       Curl_ipv6_scope(&ctx->addr.curl_sa_addr),
+                       ctx->transport);
     if(result) {
       if(result == CURLE_UNSUPPORTED_PROTOCOL) {
         /* The address family is not supported on this interface.
@@ -1313,26 +1306,31 @@
     }
   }
   else if(rc & CURL_CSELECT_ERR) {
+    CURL_TRC_CF(data, cf, "poll/select error on fd=%" FMT_SOCKET_T, ctx->sock);
     (void)verifyconnect(ctx->sock, &ctx->error);
     result = CURLE_COULDNT_CONNECT;
   }
 
 out:
   if(result) {
+    VERBOSE(char buffer[STRERROR_LEN]);
+    set_local_ip(cf, data);
     if(ctx->error) {
-      VERBOSE(char buffer[STRERROR_LEN]);
-      set_local_ip(cf, data);
       data->state.os_errno = ctx->error;
       SET_SOCKERRNO(ctx->error);
-      infof(data, "connect to %s port %u from %s port %d failed: %s",
-            ctx->ip.remote_ip, ctx->ip.remote_port,
-            ctx->ip.local_ip, ctx->ip.local_port,
-            curlx_strerror(ctx->error, buffer, sizeof(buffer)));
+      VERBOSE(curlx_strerror(ctx->error, buffer, sizeof(buffer)));
+    }
+    else {
+      VERBOSE(curlx_strcopy(buffer, sizeof(buffer), STRCONST("peer closed")));
     }
     if(ctx->sock != CURL_SOCKET_BAD) {
       socket_close(data, cf->conn, TRUE, ctx->sock);
       ctx->sock = CURL_SOCKET_BAD;
     }
+    infof(data, "connect to %s port %u from %s port %d failed: %s",
+          ctx->ip.remote_ip, ctx->ip.remote_port,
+          ctx->ip.local_ip, ctx->ip.local_port,
+          curlx_strerror(ctx->error, buffer, sizeof(buffer)));
     *done = FALSE;
   }
   return result;
@@ -1713,7 +1711,7 @@
 CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
                             struct Curl_easy *data,
                             struct connectdata *conn,
-                            const struct Curl_addrinfo *ai,
+                            struct Curl_sockaddr_ex *addr,
                             uint8_t transport)
 {
   struct cf_socket_ctx *ctx = NULL;
@@ -1723,7 +1721,7 @@
   (void)data;
   (void)conn;
   DEBUGASSERT(transport == TRNSPRT_TCP);
-  if(!ai) {
+  if(!addr) {
     result = CURLE_BAD_FUNCTION_ARGUMENT;
     goto out;
   }
@@ -1734,7 +1732,7 @@
     goto out;
   }
 
-  result = cf_socket_ctx_init(ctx, ai, transport);
+  result = cf_socket_ctx_init(ctx, addr, transport);
   if(result)
     goto out;
 
@@ -1743,8 +1741,8 @@
 out:
   *pcf = (!result) ? cf : NULL;
   if(result) {
-    Curl_safefree(cf);
-    Curl_safefree(ctx);
+    curlx_safefree(cf);
+    curlx_safefree(ctx);
   }
 
   return result;
@@ -1879,7 +1877,7 @@
 CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
                             struct Curl_easy *data,
                             struct connectdata *conn,
-                            const struct Curl_addrinfo *ai,
+                            struct Curl_sockaddr_ex *addr,
                             uint8_t transport)
 {
   struct cf_socket_ctx *ctx = NULL;
@@ -1895,7 +1893,7 @@
     goto out;
   }
 
-  result = cf_socket_ctx_init(ctx, ai, transport);
+  result = cf_socket_ctx_init(ctx, addr, transport);
   if(result)
     goto out;
 
@@ -1904,8 +1902,8 @@
 out:
   *pcf = (!result) ? cf : NULL;
   if(result) {
-    Curl_safefree(cf);
-    Curl_safefree(ctx);
+    curlx_safefree(cf);
+    curlx_safefree(ctx);
   }
 
   return result;
@@ -1933,7 +1931,7 @@
 CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
                              struct Curl_easy *data,
                              struct connectdata *conn,
-                             const struct Curl_addrinfo *ai,
+                             struct Curl_sockaddr_ex *addr,
                              uint8_t transport)
 {
   struct cf_socket_ctx *ctx = NULL;
@@ -1949,7 +1947,7 @@
     goto out;
   }
 
-  result = cf_socket_ctx_init(ctx, ai, transport);
+  result = cf_socket_ctx_init(ctx, addr, transport);
   if(result)
     goto out;
 
@@ -1958,8 +1956,8 @@
 out:
   *pcf = (!result) ? cf : NULL;
   if(result) {
-    Curl_safefree(cf);
-    Curl_safefree(ctx);
+    curlx_safefree(cf);
+    curlx_safefree(ctx);
   }
 
   return result;
@@ -2195,8 +2193,8 @@
 
 out:
   if(result) {
-    Curl_safefree(cf);
-    Curl_safefree(ctx);
+    curlx_safefree(cf);
+    curlx_safefree(ctx);
   }
   return result;
 }
diff --git a/Utilities/cmcurl/lib/cf-socket.h b/Utilities/cmcurl/lib/cf-socket.h
index 014f5f7..40c001c 100644
--- a/Utilities/cmcurl/lib/cf-socket.h
+++ b/Utilities/cmcurl/lib/cf-socket.h
@@ -60,6 +60,10 @@
 CURLcode Curl_parse_interface(const char *input,
                               char **dev, char **iface, char **host);
 
+CURLcode Curl_socket_addr_from_ai(struct Curl_sockaddr_ex *addr,
+                                  const struct Curl_addrinfo *ai,
+                                  uint8_t transport);
+
 /*
  * Create a socket based on info from 'conn' and 'ai'.
  *
@@ -91,7 +95,7 @@
 CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
                             struct Curl_easy *data,
                             struct connectdata *conn,
-                            const struct Curl_addrinfo *ai,
+                            struct Curl_sockaddr_ex *addr,
                             uint8_t transport);
 
 /**
@@ -104,7 +108,7 @@
 CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
                             struct Curl_easy *data,
                             struct connectdata *conn,
-                            const struct Curl_addrinfo *ai,
+                            struct Curl_sockaddr_ex *addr,
                             uint8_t transport);
 
 /**
@@ -117,7 +121,7 @@
 CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
                              struct Curl_easy *data,
                              struct connectdata *conn,
-                             const struct Curl_addrinfo *ai,
+                             struct Curl_sockaddr_ex *addr,
                              uint8_t transport);
 
 /**
diff --git a/Utilities/cmcurl/lib/cfilters.c b/Utilities/cmcurl/lib/cfilters.c
index 27b0306..6d7d8ef 100644
--- a/Utilities/cmcurl/lib/cfilters.c
+++ b/Utilities/cmcurl/lib/cfilters.c
@@ -34,8 +34,9 @@
 #include "curlx/strparse.h"
 
 #ifdef UNITTESTS
-/* used by unit2600.c */
-void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+/* @unittest 2600 */
+UNITTEST void cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data);
+UNITTEST void cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   cf->connected = FALSE;
   if(cf->next)
@@ -115,6 +116,37 @@
     CURLE_UNKNOWN_OPTION;
 }
 
+#ifdef CURLVERBOSE
+static void conn_trc_filters(struct Curl_easy *data,
+                                  int sockindex,
+                                  const char *info)
+{
+  if(CURL_TRC_M_is_verbose(data) && data->conn) {
+    struct Curl_cfilter *cf = data->conn->cfilter[sockindex];
+
+    if(cf) {
+      struct dynbuf msg;
+      CURLcode result = CURLE_OK;
+
+      curlx_dyn_init(&msg, 1024);
+      result = curlx_dyn_addf(&msg, "%s [%d]", info, sockindex);
+      for(; cf && !result; cf = cf->next) {
+        result = curlx_dyn_addf(&msg, "[%s%s]",
+                                cf->connected ? "" : "!", cf->cft->name);
+      }
+      if(!result)
+        CURL_TRC_M(data, "%s", curlx_dyn_ptr(&msg));
+      else
+        CURL_TRC_M(data, "%s [%d] error %d tracing chain",
+                   info, sockindex, result);
+      curlx_dyn_free(&msg);
+    }
+    else
+      CURL_TRC_M(data, "%s [%d][-]", info, sockindex);
+  }
+}
+#endif /* CURLVERBOSE */
+
 void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf,
                                 struct Curl_easy *data)
 {
@@ -488,6 +520,24 @@
   }
 }
 
+static void conn_remove_setup_filters(struct Curl_easy *data,
+                                      int sockindex)
+{
+  struct Curl_cfilter **anchor = &data->conn->cfilter[sockindex];
+  while(*anchor) {
+    struct Curl_cfilter *cf = *anchor;
+    if(cf->connected && (cf->cft->flags & CF_TYPE_SETUP)) {
+      *anchor = cf->next;
+      cf->next = NULL;
+      CURL_TRC_CF(data, cf, "removing connected setup filter");
+      cf->cft->destroy(cf, data);
+      curlx_free(cf);
+    }
+    else
+      anchor = &cf->next;
+  }
+}
+
 CURLcode Curl_conn_connect(struct Curl_easy *data,
                            int sockindex,
                            bool blocking,
@@ -505,6 +555,11 @@
   if(!CONN_SOCK_IDX_VALID(sockindex))
     return CURLE_BAD_FUNCTION_ARGUMENT;
 
+  if(data->conn->scheme->flags & PROTOPT_NONETWORK) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
   cf = data->conn->cfilter[sockindex];
   if(!cf) {
     *done = FALSE;
@@ -536,10 +591,14 @@
       conn_report_connect_stats(cf, data);
       data->conn->keepalive = *Curl_pgrs_now(data);
       VERBOSE(result = cf_verboseconnect(data, cf));
+      VERBOSE(conn_trc_filters(data, sockindex, "connected"));
+      conn_remove_setup_filters(data, sockindex);
+      VERBOSE(conn_trc_filters(data, sockindex, "reduced to"));
       goto out;
     }
     else if(result) {
       CURL_TRC_CF(data, cf, "Curl_conn_connect(), filter returned %d", result);
+      VERBOSE(conn_trc_filters(data, sockindex, "failed to connect"));
       conn_report_connect_stats(cf, data);
       goto out;
     }
@@ -649,14 +708,16 @@
 
 bool Curl_conn_get_ssl_info(struct Curl_easy *data,
                             struct connectdata *conn, int sockindex,
+                            int query,
                             struct curl_tlssessioninfo *info)
 {
   if(!CONN_SOCK_IDX_VALID(sockindex))
     return FALSE;
   if(Curl_conn_is_ssl(conn, sockindex)) {
     struct Curl_cfilter *cf = conn->cfilter[sockindex];
-    CURLcode result = cf ? cf->cft->query(cf, data, CF_QUERY_SSL_INFO,
-                               NULL, (void *)info) : CURLE_UNKNOWN_OPTION;
+    CURLcode result = cf ?
+      cf->cft->query(cf, data, query, NULL, (void *)info) :
+      CURLE_UNKNOWN_OPTION;
     return !result;
   }
   return FALSE;
@@ -697,6 +758,41 @@
   return Curl_conn_cf_get_transport(cf, data);
 }
 
+int Curl_socktype_for_transport(uint8_t transport)
+{
+  switch(transport) {
+  case TRNSPRT_TCP:
+    return SOCK_STREAM;
+  case TRNSPRT_UNIX:
+    return SOCK_STREAM;
+  default: /* UDP and QUIC */
+    return SOCK_DGRAM;
+  }
+}
+
+int Curl_protocol_for_transport(uint8_t transport)
+{
+  switch(transport) {
+  case TRNSPRT_TCP:
+    return IPPROTO_TCP;
+  case TRNSPRT_UNIX:
+    return IPPROTO_IP;
+  default: /* UDP and QUIC */
+    return IPPROTO_UDP;
+  }
+}
+
+bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft->flags & CF_TYPE_HTTPSRR)
+      return TRUE;
+  }
+  return FALSE;
+}
+
 const char *Curl_conn_get_alpn_negotiated(struct Curl_easy *data,
                                           struct connectdata *conn)
 {
@@ -800,12 +896,19 @@
   return result;
 }
 
+/*
+ * Return values:
+ *   -1 = error
+ *    0 = timeout
+ *    N = number of structures with non zero revent fields
+ */
 int Curl_conn_cf_poll(struct Curl_cfilter *cf,
                       struct Curl_easy *data,
                       timediff_t timeout_ms)
 {
   struct easy_pollset ps;
-  int result;
+  int rc;
+  CURLcode result;
 
   DEBUGASSERT(cf);
   DEBUGASSERT(data);
@@ -814,20 +917,24 @@
 
   result = Curl_conn_cf_adjust_pollset(cf, data, &ps);
   if(!result)
-    result = Curl_pollset_poll(data, &ps, timeout_ms);
+    rc = Curl_pollset_poll(data, &ps, timeout_ms);
+  else
+    rc = -1;
   Curl_pollset_cleanup(&ps);
-  return result;
+  return rc;
 }
 
 void Curl_conn_get_current_host(struct Curl_easy *data, int sockindex,
                                 const char **phost, int *pport)
 {
   struct Curl_cfilter *cf, *cf_proxy = NULL;
+  int portarg = -1;
 
   if(!data->conn) {
     DEBUGASSERT(0);
     *phost = "";
-    *pport = -1;
+    if(pport)
+      *pport = -1;
     return;
   }
 
@@ -843,12 +950,14 @@
    * to an interim host and any authentication or other things apply
    * to this interim host and port. */
   if(!cf_proxy || cf_proxy->cft->query(cf_proxy, data, CF_QUERY_HOST_PORT,
-                                       pport, CURL_UNCONST(phost))) {
+                                       &portarg, CURL_UNCONST(phost))) {
     /* Everything connected or query unsuccessful, the overall
      * connection's destination is the answer */
     *phost = data->conn->host.name;
-    *pport = data->conn->remote_port;
+    portarg = data->conn->remote_port;
   }
+  if(pport)
+    *pport = portarg;
 }
 
 CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf,
@@ -1029,7 +1138,7 @@
   result = cf ? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT,
                                &n, NULL) : CURLE_UNKNOWN_OPTION;
   /* If no filter answered the query, the default is a non-multiplexed
-   * connection with limit 1. Otherwise, the the query may return 0
+   * connection with limit 1. Otherwise, the query may return 0
    * for connections that are in shutdown, e.g. server HTTP/2 GOAWAY. */
   return (result || n < 0) ? 1 : (size_t)n;
 }
diff --git a/Utilities/cmcurl/lib/cfilters.h b/Utilities/cmcurl/lib/cfilters.h
index c754634..ac56737 100644
--- a/Utilities/cmcurl/lib/cfilters.h
+++ b/Utilities/cmcurl/lib/cfilters.h
@@ -199,12 +199,17 @@
  * CF_TYPE_MULTIPLEX:  provides multiplexing of easy handles
  * CF_TYPE_PROXY       provides proxying
  * CF_TYPE_HTTP        implement a version of the HTTP protocol
+ * CF_TYPE_SETUP       filter is only needed for connection setup and
+ *                     can be removed once connected
+ * CF_TYPE_HTTPSRR     filter that wants HTTPS-RR information
  */
 #define CF_TYPE_IP_CONNECT  (1 << 0)
 #define CF_TYPE_SSL         (1 << 1)
 #define CF_TYPE_MULTIPLEX   (1 << 2)
 #define CF_TYPE_PROXY       (1 << 3)
 #define CF_TYPE_HTTP        (1 << 4)
+#define CF_TYPE_SETUP       (1 << 5)
+#define CF_TYPE_HTTPSRR     (1 << 6)
 
 /* A connection filter type, e.g. specific implementation. */
 struct Curl_cftype {
@@ -345,9 +350,16 @@
 unsigned char Curl_conn_cf_get_transport(struct Curl_cfilter *cf,
                                          struct Curl_easy *data);
 
+int Curl_socktype_for_transport(uint8_t transport);
+int Curl_protocol_for_transport(uint8_t transport);
+
 const char *Curl_conn_cf_get_alpn_negotiated(struct Curl_cfilter *cf,
                                              struct Curl_easy *data);
 
+/* The filter (or one of its sub-filters) wants HTTPS-RR information. */
+bool Curl_conn_cf_wants_httpsrr(struct Curl_cfilter *cf,
+                                struct Curl_easy *data);
+
 #define CURL_CF_SSL_DEFAULT  (-1)
 #define CURL_CF_SSL_DISABLE  0
 #define CURL_CF_SSL_ENABLE   1
@@ -388,12 +400,13 @@
 bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex);
 
 /*
- * Fill `info` with information about the TLS instance securing
- * the connection when available, otherwise e.g. when
- * Curl_conn_is_ssl() is FALSE, return FALSE.
+ * Fill `info` with information about the TLS instance securing the connection
+ * when available, otherwise e.g. when Curl_conn_is_ssl() is FALSE, return
+ * FALSE. 'query' should be CF_QUERY_SSL_INFO or CF_QUERY_SSL_CTX_INFO.
  */
 bool Curl_conn_get_ssl_info(struct Curl_easy *data,
                             struct connectdata *conn, int sockindex,
+                            int query,
                             struct curl_tlssessioninfo *info);
 
 CURLcode Curl_conn_get_ip_info(struct Curl_easy *data,
@@ -429,7 +442,7 @@
 /**
  * Shutdown the connection at `sockindex` non-blocking, using timeout
  * from `data->set.shutdowntimeout`, default DEFAULT_SHUTDOWN_TIMEOUT_MS.
- * Will return CURLE_OK and *done == FALSE if not finished.
+ * Return CURLE_OK and *done == FALSE if not finished.
  */
 CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done);
 
@@ -565,10 +578,6 @@
                               struct connectdata *conn,
                               int sockindex);
 
-#ifdef UNITTESTS
-void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data);
-#endif
-
 /**
  * Get the remote hostname and port that the connection is currently
  * talking to (or will talk to).
@@ -604,14 +613,14 @@
 
 /*
  * Receive data on the connection, using FIRSTSOCKET/SECONDARYSOCKET.
- * Will return CURLE_AGAIN iff blocked on receiving.
+ * Return CURLE_AGAIN iff blocked on receiving.
  */
 CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex,
                         char *buf, size_t len, size_t *pnread);
 
 /*
  * Send data on the connection, using FIRSTSOCKET/SECONDARYSOCKET.
- * Will return CURLE_AGAIN iff blocked on sending.
+ * Return CURLE_AGAIN iff blocked on sending.
  */
 CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex,
                         const void *buf, size_t len, bool eos,
diff --git a/Utilities/cmcurl/lib/conncache.c b/Utilities/cmcurl/lib/conncache.c
index a865959..33fb68b 100644
--- a/Utilities/cmcurl/lib/conncache.c
+++ b/Utilities/cmcurl/lib/conncache.c
@@ -206,7 +206,7 @@
 
   /* treat the connection as aborted in CONNECT_ONLY situations, we do
    * not know what the APP did with it. */
-  if(conn->connect_only)
+  if(conn->bits.connect_only)
     aborted = TRUE;
   conn->bits.aborted = aborted;
 
@@ -354,7 +354,7 @@
     for(curr = Curl_llist_head(&bundle->conns); curr;
         curr = Curl_node_next(curr)) {
       conn = Curl_node_elem(curr);
-      if(CONN_INUSE(conn) || conn->bits.close || conn->connect_only)
+      if(CONN_INUSE(conn) || conn->bits.close || conn->bits.connect_only)
         continue;
       /* Set higher score for the age passed since the connection was used */
       score = curlx_ptimediff_ms(pnow, &conn->lastused);
@@ -375,7 +375,7 @@
   size_t dest_limit = 0;
   size_t total_limit = 0;
   size_t shutdowns;
-  int result = CPOOL_LIMIT_OK;
+  int res = CPOOL_LIMIT_OK;
 
   if(!cpool)
     return CPOOL_LIMIT_OK;
@@ -425,7 +425,7 @@
       shutdowns = Curl_cshutdn_dest_count(cpool->idata, conn->destination);
     }
     if((live + shutdowns) >= dest_limit) {
-      result = CPOOL_LIMIT_DEST;
+      res = CPOOL_LIMIT_DEST;
       goto out;
     }
   }
@@ -453,14 +453,14 @@
       shutdowns = Curl_cshutdn_count(cpool->idata);
     }
     if((cpool->num_conn + shutdowns) >= total_limit) {
-      result = CPOOL_LIMIT_TOTAL;
+      res = CPOOL_LIMIT_TOTAL;
       goto out;
     }
   }
 
 out:
   CPOOL_UNLOCK(cpool, cpool->idata);
-  return result;
+  return res;
 }
 
 CURLcode Curl_cpool_add(struct Curl_easy *data,
@@ -558,7 +558,7 @@
   struct cpool *cpool = cpool_get_instance(data);
   bool kept = TRUE;
 
-  if(!data)
+  if(!data || !data->multi)
     return kept;
 
   if(!data->multi->maxconnects) {
@@ -600,7 +600,7 @@
 {
   struct cpool *cpool = cpool_get_instance(data);
   struct cpool_bundle *bundle;
-  bool result = FALSE;
+  bool found = FALSE;
 
   DEBUGASSERT(cpool);
   DEBUGASSERT(conn_cb);
@@ -619,17 +619,17 @@
       curr = Curl_node_next(curr);
 
       if(conn_cb(conn, userdata)) {
-        result = TRUE;
+        found = TRUE;
         break;
       }
     }
   }
 
   if(done_cb) {
-    result = done_cb(result, userdata);
+    found = done_cb(userdata);
   }
   CPOOL_UNLOCK(cpool, data);
-  return result;
+  return found;
 }
 
 void Curl_conn_terminate(struct Curl_easy *data,
@@ -665,7 +665,7 @@
 
   /* treat the connection as aborted in CONNECT_ONLY situations,
    * so no graceful shutdown is attempted. */
-  if(conn->connect_only)
+  if(conn->bits.connect_only)
     aborted = TRUE;
 
   if(data->multi) {
diff --git a/Utilities/cmcurl/lib/conncache.h b/Utilities/cmcurl/lib/conncache.h
index d8626ae..7cee4d4 100644
--- a/Utilities/cmcurl/lib/conncache.h
+++ b/Utilities/cmcurl/lib/conncache.h
@@ -84,8 +84,8 @@
 
 /**
  * Return if the pool has reached its configured limits for adding
- * the given connection. Will try to discard the oldest, idle
- * connections to make space.
+ * the given connection. Try to discard the oldest, idle connections
+ * to make space.
  */
 #define CPOOL_LIMIT_OK     0
 #define CPOOL_LIMIT_DEST   1
@@ -98,7 +98,7 @@
                                       void *userdata);
 
 /* Act on the result of the find, may override it. */
-typedef bool Curl_cpool_done_match_cb(bool result, void *userdata);
+typedef bool Curl_cpool_done_match_cb(void *userdata);
 
 /**
  * Find a connection in the pool matching `destination`.
diff --git a/Utilities/cmcurl/lib/connect.c b/Utilities/cmcurl/lib/connect.c
index 78c9370..b13d496 100644
--- a/Utilities/cmcurl/lib/connect.c
+++ b/Utilities/cmcurl/lib/connect.c
@@ -54,6 +54,7 @@
 #include "strerror.h"
 #include "cfilters.h"
 #include "connect.h"
+#include "cf-dns.h"
 #include "cf-haproxy.h"
 #include "cf-https-connect.h"
 #include "cf-ip-happy.h"
@@ -96,14 +97,17 @@
 #endif
 
 /*
- * Curl_timeleft_ms() returns the amount of milliseconds left allowed for the
+ * timeleft_now_ms() returns the amount of milliseconds left allowed for the
  * transfer/connection. If the value is 0, there is no timeout (ie there is
  * infinite time left). If the value is negative, the timeout time has already
  * elapsed.
- * @unittest: 1303
+ *
+ * @unittest 1303
  */
-timediff_t Curl_timeleft_now_ms(struct Curl_easy *data,
-                                const struct curltime *pnow)
+UNITTEST timediff_t timeleft_now_ms(struct Curl_easy *data,
+                                    const struct curltime *pnow);
+UNITTEST timediff_t timeleft_now_ms(struct Curl_easy *data,
+                                    const struct curltime *pnow)
 {
   timediff_t timeleft_ms = 0;
   timediff_t ctimeleft_ms = 0;
@@ -138,7 +142,7 @@
 
 timediff_t Curl_timeleft_ms(struct Curl_easy *data)
 {
-  return Curl_timeleft_now_ms(data, Curl_pgrs_now(data));
+  return timeleft_now_ms(data, Curl_pgrs_now(data));
 }
 
 void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
@@ -343,7 +347,6 @@
 {
   struct cf_setup_ctx *ctx = cf->ctx;
   CURLcode result = CURLE_OK;
-  struct Curl_dns_entry *dns = data->state.dns[cf->sockindex];
 
   if(cf->connected) {
     *done = TRUE;
@@ -352,8 +355,6 @@
 
   /* connect current sub-chain */
 connect_sub_chain:
-  if(!dns)
-    return CURLE_FAILED_INIT;
 
   if(cf->next && !cf->next->connected) {
     result = Curl_conn_cf_connect(cf->next, data, done);
@@ -373,7 +374,27 @@
   /* sub-chain connected, do we need to add more? */
 #ifndef CURL_DISABLE_PROXY
   if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) {
-    result = Curl_cf_socks_proxy_insert_after(cf, data);
+    /* for the secondary socket (FTP), use the "connect to host"
+     * but ignore the "connect to port" (use the secondary port)
+     */
+    const char *hostname =
+      cf->conn->bits.httpproxy ?
+      cf->conn->http_proxy.host.name :
+      cf->conn->bits.conn_to_host ?
+      cf->conn->conn_to_host.name :
+      cf->sockindex == SECONDARYSOCKET ?
+      cf->conn->secondaryhostname : cf->conn->host.name;
+    uint16_t port =
+      cf->conn->bits.httpproxy ? cf->conn->http_proxy.port :
+      cf->sockindex == SECONDARYSOCKET ? cf->conn->secondary_port :
+      cf->conn->bits.conn_to_port ? cf->conn->conn_to_port :
+      cf->conn->remote_port;
+    const char *user = cf->conn->socks_proxy.user;
+    const char *passwd = cf->conn->socks_proxy.passwd;
+
+    result = Curl_cf_socks_proxy_insert_after(
+      cf, data, hostname, port, cf->conn->ip_version,
+      cf->conn->socks_proxy.proxytype, user, passwd);
     if(result)
       return result;
     ctx->state = CF_SETUP_CNNCT_SOCKS;
@@ -408,7 +429,7 @@
 #ifndef CURL_DISABLE_PROXY
     if(data->set.haproxyprotocol) {
       if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
-        failf(data, "haproxy protocol not support with SSL "
+        failf(data, "haproxy protocol not supported with SSL "
               "encryption in place (QUIC?)");
         return CURLE_UNSUPPORTED_PROTOCOL;
       }
@@ -464,12 +485,12 @@
   struct cf_setup_ctx *ctx = cf->ctx;
 
   CURL_TRC_CF(data, cf, "destroy");
-  Curl_safefree(ctx);
+  curlx_safefree(ctx);
 }
 
 struct Curl_cftype Curl_cft_setup = {
   "SETUP",
-  0,
+  CF_TYPE_SETUP,
   CURL_LOG_LVL_NONE,
   cf_setup_destroy,
   cf_setup_connect,
@@ -559,13 +580,11 @@
                          int ssl_mode)
 {
   CURLcode result = CURLE_OK;
+  uint8_t dns_queries;
 
   DEBUGASSERT(data);
   DEBUGASSERT(conn->scheme);
-  DEBUGASSERT(dns);
-
-  Curl_resolv_unlink(data, &data->state.dns[sockindex]);
-  data->state.dns[sockindex] = dns;
+  DEBUGASSERT(!conn->cfilter[sockindex]);
 
 #ifndef CURL_DISABLE_HTTP
   if(!conn->cfilter[sockindex] &&
@@ -585,13 +604,34 @@
       goto out;
   }
 
+  dns_queries = Curl_resolv_dns_queries(data, conn->ip_version);
+#ifdef USE_HTTPSRR
+  if(sockindex == FIRSTSOCKET)
+    dns_queries |= CURL_DNSQ_HTTPS;
+#endif
+  result = Curl_cf_dns_add(data, conn, sockindex, dns_queries,
+                           conn->transport_wanted, dns);
   DEBUGASSERT(conn->cfilter[sockindex]);
 out:
-  if(result)
-    Curl_resolv_unlink(data, &data->state.dns[sockindex]);
   return result;
 }
 
+#ifdef USE_UNIX_SOCKETS
+const char *Curl_conn_get_unix_path(struct connectdata *conn)
+{
+  const char *unix_path = conn->unix_domain_socket;
+
+#ifndef CURL_DISABLE_PROXY
+  if(!unix_path && conn->bits.proxy && conn->socks_proxy.host.name &&
+     !strncmp(UNIX_SOCKET_PREFIX "/",
+              conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX)))
+    unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1;
+#endif
+
+  return unix_path;
+}
+#endif /* USE_UNIX_SOCKETS */
+
 void Curl_conn_set_multiplex(struct connectdata *conn)
 {
   if(!conn->bits.multiplex) {
diff --git a/Utilities/cmcurl/lib/connect.h b/Utilities/cmcurl/lib/connect.h
index 071b432..40f1c9c 100644
--- a/Utilities/cmcurl/lib/connect.h
+++ b/Utilities/cmcurl/lib/connect.h
@@ -25,6 +25,7 @@
  ***************************************************************************/
 #include "curl_setup.h"
 
+#include "hostip.h"
 #include "curlx/timeval.h"
 
 struct Curl_dns_entry;
@@ -37,8 +38,6 @@
 /* generic function that returns how much time there is left to run, according
    to the timeouts set */
 timediff_t Curl_timeleft_ms(struct Curl_easy *data);
-timediff_t Curl_timeleft_now_ms(struct Curl_easy *data,
-                                const struct curltime *pnow);
 
 #define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */
 
@@ -127,6 +126,15 @@
 /* Set conn to allow multiplexing. */
 void Curl_conn_set_multiplex(struct connectdata *conn);
 
+#ifdef USE_UNIX_SOCKETS
+#ifndef CURL_DISABLE_PROXY
+#define UNIX_SOCKET_PREFIX "localhost"
+#endif
+const char *Curl_conn_get_unix_path(struct connectdata *conn);
+#else
+#define Curl_conn_get_unix_path(c)      NULL
+#endif
+
 extern struct Curl_cftype Curl_cft_setup;
 
 #endif /* HEADER_CURL_CONNECT_H */
diff --git a/Utilities/cmcurl/lib/content_encoding.c b/Utilities/cmcurl/lib/content_encoding.c
index b8dbd7d..dea9a0f 100644
--- a/Utilities/cmcurl/lib/content_encoding.c
+++ b/Utilities/cmcurl/lib/content_encoding.c
@@ -31,13 +31,13 @@
 #endif
 
 #ifdef HAVE_BROTLI
-#if defined(__GNUC__) || defined(__clang__)
+#ifdef CURL_HAVE_DIAG
 /* Ignore -Wvla warnings in brotli headers */
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wvla"
 #endif
 #include <brotli/decode.h>
-#if defined(__GNUC__) || defined(__clang__)
+#ifdef CURL_HAVE_DIAG
 #pragma GCC diagnostic pop
 #endif
 #endif
@@ -751,7 +751,7 @@
       }
 
       if(Curl_cwriter_count(data, phase) + 1 >= MAX_ENCODE_STACK) {
-        failf(data, "Reject response due to more than %u content encodings",
+        failf(data, "Reject response due to more than %d content encodings",
               MAX_ENCODE_STACK);
         return CURLE_BAD_CONTENT_ENCODING;
       }
diff --git a/Utilities/cmcurl/lib/cookie.c b/Utilities/cmcurl/lib/cookie.c
index 4d53cc4..0f822ea 100644
--- a/Utilities/cmcurl/lib/cookie.c
+++ b/Utilities/cmcurl/lib/cookie.c
@@ -347,7 +347,7 @@
 
   cookie-octet    = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
 
-  But Firefox and Chrome as of June 2022 accept space, comma and double-quotes
+  Yet, Firefox and Chrome as of June 2022 accept space, comma and double-quotes
   fine. The prime reason for filtering out control bytes is that some HTTP
   servers return 400 for requests that contain such.
 */
@@ -423,18 +423,17 @@
 
 /* this function return errors on OOM etc, not on plain cookie format
    problems */
-static CURLcode
-parse_cookie_header(struct Curl_easy *data,
-                    struct Cookie *co,
-                    struct CookieInfo *ci,
-                    bool *okay, /* if the cookie was fine */
-                    const char *ptr,
-                    const char *domain, /* default domain */
-                    const char *path,   /* full path used when this cookie is
-                                           set, used to get default path for
-                                           the cookie unless set */
-                    bool secure)  /* TRUE if connection is over secure
-                                     origin */
+static CURLcode parse_cookie_header(
+  struct Curl_easy *data,
+  struct Cookie *co,
+  struct CookieInfo *ci,
+  bool *okay,         /* if the cookie was fine */
+  const char *ptr,
+  const char *domain, /* default domain */
+  const char *path,   /* full path used when this cookie is
+                         set, used to get default path for
+                         the cookie unless set */
+  bool secure)        /* TRUE if connection is over secure origin */
 {
   /* This line was read off an HTTP-header */
   time_t now = 0;
@@ -461,6 +460,13 @@
         sep = TRUE; /* a '=' was used */
         if(!curlx_str_cspn(&ptr, &val, ";\r\n"))
           curlx_str_trimblanks(&val);
+
+        /* Reject cookies with a TAB inside the value */
+        if(curlx_strlen(&val) &&
+           memchr(curlx_str(&val), '\t', curlx_strlen(&val))) {
+          infof(data, "cookie contains TAB, dropping");
+          return CURLE_OK;
+        }
       }
       else
         curlx_str_init(&val);
@@ -489,13 +495,6 @@
           return CURLE_OK;
         }
 
-        /* Reject cookies with a TAB inside the value */
-        if(curlx_strlen(&val) &&
-           memchr(curlx_str(&val), '\t', curlx_strlen(&val))) {
-          infof(data, "cookie contains TAB, dropping");
-          return CURLE_OK;
-        }
-
         /* Check if we have a reserved prefix set. */
         if(!strncmp("__Secure-", curlx_str(&name), 9))
           co->prefix_secure = TRUE;
@@ -930,17 +929,16 @@
  * IPv6 address.
  *
  */
-CURLcode
-Curl_cookie_add(struct Curl_easy *data,
-                struct CookieInfo *ci,
-                bool httpheader, /* TRUE if HTTP header-style line */
-                bool noexpire, /* if TRUE, skip remove_expired() */
-                const char *lineptr,   /* first character of the line */
-                const char *domain, /* default domain */
-                const char *path,   /* full path used when this cookie is set,
-                                       used to get default path for the cookie
-                                       unless set */
-                bool secure)  /* TRUE if connection is over secure origin */
+CURLcode Curl_cookie_add(
+  struct Curl_easy *data,
+  struct CookieInfo *ci,
+  bool httpheader,     /* TRUE if HTTP header-style line */
+  bool noexpire,       /* if TRUE, skip remove_expired() */
+  const char *lineptr, /* first character of the line */
+  const char *domain,  /* default domain */
+  const char *path,    /* full path used when this cookie is set, used
+                          to get default path for the cookie unless set */
+  bool secure)         /* TRUE if connection is over secure origin */
 {
   struct Cookie comem;
   struct Cookie *co;
@@ -1106,8 +1104,17 @@
       fp = curlx_fopen(file, "rb");
       if(!fp)
         infof(data, "WARNING: failed to open cookie file \"%s\"", file);
-      else
-        handle = fp;
+      else {
+        curlx_struct_stat stat;
+        if((curlx_fstat(fileno(fp), &stat) != -1) && S_ISDIR(stat.st_mode)) {
+          curlx_fclose(fp);
+          fp = NULL;
+          infof(data, "WARNING: cookie filename points to a directory: \"%s\"",
+                file);
+        }
+        else
+          handle = fp;
+      }
     }
   }
 
@@ -1464,7 +1471,7 @@
   FILE *out = NULL;
   bool use_stdout = FALSE;
   char *tempstore = NULL;
-  CURLcode error = CURLE_OK;
+  CURLcode result = CURLE_OK;
 
   if(!ci)
     /* no cookie engine alive */
@@ -1479,8 +1486,8 @@
     use_stdout = TRUE;
   }
   else {
-    error = Curl_fopen(data, filename, &out, &tempstore);
-    if(error)
+    result = Curl_fopen(data, filename, &out, &tempstore);
+    if(result)
       goto error;
   }
 
@@ -1497,7 +1504,7 @@
 
     array = curlx_calloc(1, sizeof(struct Cookie *) * ci->numcookies);
     if(!array) {
-      error = CURLE_OUT_OF_MEMORY;
+      result = CURLE_OUT_OF_MEMORY;
       goto error;
     }
 
@@ -1517,7 +1524,7 @@
       char *format_ptr = get_netscape_format(array[i]);
       if(!format_ptr) {
         curlx_free(array);
-        error = CURLE_OUT_OF_MEMORY;
+        result = CURLE_OUT_OF_MEMORY;
         goto error;
       }
       curl_mfprintf(out, "%s\n", format_ptr);
@@ -1531,7 +1538,7 @@
     curlx_fclose(out);
     out = NULL;
     if(tempstore && curlx_rename(tempstore, filename)) {
-      error = CURLE_WRITE_ERROR;
+      result = CURLE_WRITE_ERROR;
       goto error;
     }
   }
@@ -1551,7 +1558,7 @@
     unlink(tempstore);
     curlx_free(tempstore);
   }
-  return error;
+  return result;
 }
 
 static struct curl_slist *cookie_list(struct Curl_easy *data)
diff --git a/Utilities/cmcurl/lib/cookie.h b/Utilities/cmcurl/lib/cookie.h
index fabd04d..f66e0ef 100644
--- a/Utilities/cmcurl/lib/cookie.h
+++ b/Utilities/cmcurl/lib/cookie.h
@@ -125,11 +125,11 @@
 void Curl_cookie_clearsess(struct CookieInfo *ci);
 
 #if defined(CURL_DISABLE_HTTP) || defined(CURL_DISABLE_COOKIES)
-#define Curl_cookie_list(x) NULL
+#define Curl_cookie_list(x)      NULL
 #define Curl_cookie_loadfiles(x) CURLE_OK
-#define Curl_cookie_init() NULL
-#define Curl_cookie_run(x) Curl_nop_stmt
-#define Curl_cookie_cleanup(x) Curl_nop_stmt
+#define Curl_cookie_init()       NULL
+#define Curl_cookie_run(x)       Curl_nop_stmt
+#define Curl_cookie_cleanup(x)   Curl_nop_stmt
 #define Curl_flush_cookies(x, y) Curl_nop_stmt
 #else
 void Curl_flush_cookies(struct Curl_easy *data, bool cleanup);
diff --git a/Utilities/cmcurl/lib/cshutdn.c b/Utilities/cmcurl/lib/cshutdn.c
index 308f2aa..27b4a9f 100644
--- a/Utilities/cmcurl/lib/cshutdn.c
+++ b/Utilities/cmcurl/lib/cshutdn.c
@@ -87,14 +87,14 @@
     return;
   }
 
-  if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET))
+  if(!conn->bits.connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET))
     r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1);
   else {
     r1 = CURLE_OK;
     done1 = TRUE;
   }
 
-  if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET))
+  if(!conn->bits.connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET))
     r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2);
   else {
     r2 = CURLE_OK;
diff --git a/Utilities/cmcurl/lib/cshutdn.h b/Utilities/cmcurl/lib/cshutdn.h
index 9030474..b2e83f3 100644
--- a/Utilities/cmcurl/lib/cshutdn.h
+++ b/Utilities/cmcurl/lib/cshutdn.h
@@ -33,7 +33,7 @@
 struct Curl_sigpipe_ctx;
 
 /* Run the shutdown of the connection once.
- * Will shortly attach/detach `data` to `conn` while doing so.
+ * Shortly attach/detach `data` to `conn` while doing so.
  * `done` will be set TRUE if any error was encountered or if
  * the connection was shut down completely. */
 void Curl_cshutdn_run_once(struct Curl_easy *data,
@@ -78,7 +78,7 @@
 bool Curl_cshutdn_close_oldest(struct Curl_easy *data,
                                const char *destination);
 
-/* Add a connection to have it shut down. Will terminate the oldest
+/* Add a connection to have it shut down. Terminate the oldest
  * connection when total connection limit of multi is being reached. */
 void Curl_cshutdn_add(struct cshutdn *cshutdn,
                       struct connectdata *conn,
diff --git a/Utilities/cmcurl/lib/curl_addrinfo.c b/Utilities/cmcurl/lib/curl_addrinfo.c
index 43d398d..fd26c5f 100644
--- a/Utilities/cmcurl/lib/curl_addrinfo.c
+++ b/Utilities/cmcurl/lib/curl_addrinfo.c
@@ -404,7 +404,7 @@
  * Given an IPv4 or IPv6 dotted string address, this converts it to a proper
  * allocated Curl_addrinfo struct and returns it.
  */
-CURLcode Curl_str2addr(const char *dotted, int port,
+CURLcode Curl_str2addr(const char *dotted, uint16_t port,
                        struct Curl_addrinfo **addrp)
 {
   struct in_addr in;
@@ -422,10 +422,15 @@
   return CURLE_BAD_FUNCTION_ARGUMENT; /* bad input format */
 }
 
-bool Curl_is_ipaddr(const char *address)
+bool Curl_is_ipv4addr(const char *address)
 {
   struct in_addr in;
-  if(curlx_inet_pton(AF_INET, address, &in) > 0)
+  return (curlx_inet_pton(AF_INET, address, &in) > 0);
+}
+
+bool Curl_is_ipaddr(const char *address)
+{
+  if(Curl_is_ipv4addr(address))
     return TRUE;
 #ifdef USE_IPV6
   {
@@ -442,34 +447,31 @@
 /**
  * Given a path to a Unix domain socket, return a newly allocated Curl_addrinfo
  * struct initialized with this path.
- * Set '*longpath' to TRUE if the error is a too long path.
+ * Returns CURLE_TOO_LARGE when path is too long.
  */
-struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath,
-                                     bool abstract)
+CURLcode Curl_unix2addr(const char *path, bool abstract,
+                        struct Curl_addrinfo **paddr)
 {
   struct Curl_addrinfo *ai;
   struct sockaddr_un *sa_un;
   size_t path_len;
 
-  *longpath = FALSE;
+  *paddr = NULL;
+
+  /* sun_path must be able to store the null-terminated path */
+  path_len = strlen(path) + 1;
+  if(path_len > sizeof(sa_un->sun_path))
+    return CURLE_TOO_LARGE;
 
   ai = curlx_calloc(1,
                     sizeof(struct Curl_addrinfo) + sizeof(struct sockaddr_un));
   if(!ai)
-    return NULL;
-  ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
+    return CURLE_OUT_OF_MEMORY;
 
+  ai->ai_addr = (void *)((char *)ai + sizeof(struct Curl_addrinfo));
   sa_un = (void *)ai->ai_addr;
   sa_un->sun_family = AF_UNIX;
 
-  /* sun_path must be able to store the null-terminated path */
-  path_len = strlen(path) + 1;
-  if(path_len > sizeof(sa_un->sun_path)) {
-    curlx_free(ai);
-    *longpath = TRUE;
-    return NULL;
-  }
-
   ai->ai_family = AF_UNIX;
   ai->ai_socktype = SOCK_STREAM; /* assume reliable transport for HTTP */
   ai->ai_addrlen = (curl_socklen_t)
@@ -481,7 +483,8 @@
   else
     memcpy(sa_un->sun_path, path, path_len); /* copy NUL byte */
 
-  return ai;
+  *paddr = ai;
+  return CURLE_OK;
 }
 #endif
 
diff --git a/Utilities/cmcurl/lib/curl_addrinfo.h b/Utilities/cmcurl/lib/curl_addrinfo.h
index f464b5e..da2da87 100644
--- a/Utilities/cmcurl/lib/curl_addrinfo.h
+++ b/Utilities/cmcurl/lib/curl_addrinfo.h
@@ -71,13 +71,14 @@
 struct Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port);
 #endif
 
+bool Curl_is_ipv4addr(const char *address);
 bool Curl_is_ipaddr(const char *address);
-CURLcode Curl_str2addr(const char *dotted, int port,
+CURLcode Curl_str2addr(const char *dotted, uint16_t port,
                        struct Curl_addrinfo **addrp);
 
 #ifdef USE_UNIX_SOCKETS
-struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath,
-                                     bool abstract);
+CURLcode Curl_unix2addr(const char *path, bool abstract,
+                        struct Curl_addrinfo **paddr);
 #endif
 
 #if defined(CURL_MEMDEBUG) && defined(HAVE_GETADDRINFO) && \
diff --git a/Utilities/cmcurl/lib/curl_config-cmake.h.in b/Utilities/cmcurl/lib/curl_config-cmake.h.in
index 4292c2b..37c401f 100644
--- a/Utilities/cmcurl/lib/curl_config-cmake.h.in
+++ b/Utilities/cmcurl/lib/curl_config-cmake.h.in
@@ -111,8 +111,8 @@
 /* disables netrc parser */
 #cmakedefine CURL_DISABLE_NETRC 1
 
-/* disables NTLM support */
-#cmakedefine CURL_DISABLE_NTLM 1
+/* enables NTLM support */
+#cmakedefine CURL_ENABLE_NTLM 1
 
 /* disables date parsing */
 #cmakedefine CURL_DISABLE_PARSEDATE 1
@@ -139,7 +139,7 @@
 #cmakedefine CURL_DISABLE_SHUFFLE_DNS 1
 
 /* disables SMB */
-#cmakedefine CURL_DISABLE_SMB 1
+#cmakedefine CURL_ENABLE_SMB 1
 
 /* disables SMTP */
 #cmakedefine CURL_DISABLE_SMTP 1
@@ -245,9 +245,6 @@
 /* Define to 1 if you have the fseeko declaration. */
 #cmakedefine HAVE_DECL_FSEEKO 1
 
-/* Define to 1 if you have the ftruncate function. */
-#cmakedefine HAVE_FTRUNCATE 1
-
 /* Define to 1 if you have a working getaddrinfo function. */
 #cmakedefine HAVE_GETADDRINFO 1
 
@@ -438,9 +435,6 @@
 /* Define to 1 if you have a working POSIX-style strerror_r function. */
 #cmakedefine HAVE_POSIX_STRERROR_R 1
 
-/* Define to 1 if you have the <pthread.h> header file */
-#cmakedefine HAVE_PTHREAD_H 1
-
 /* Define to 1 if you have the <pwd.h> header file. */
 #cmakedefine HAVE_PWD_H 1
 
@@ -495,9 +489,6 @@
 /* Define to 1 if you have the sigsetjmp function or macro. */
 #cmakedefine HAVE_SIGSETJMP 1
 
-/* Define to 1 if you have the `snprintf' function. */
-#cmakedefine HAVE_SNPRINTF 1
-
 /* Define to 1 if struct sockaddr_in6 has the sin6_scope_id member */
 #cmakedefine HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1
 
@@ -653,14 +644,17 @@
 /* Define to 1 if you have the ANSI C header files. */
 #cmakedefine STDC_HEADERS 1
 
+/* Define if you have POSIX pthreads */
+#cmakedefine HAVE_THREADS_POSIX 1
+
 /* Define if you want to enable c-ares support */
 #cmakedefine USE_ARES 1
 
-/* Define if you want to enable POSIX threaded DNS lookup */
-#cmakedefine USE_THREADS_POSIX 1
+/* Define if you want to enable c-ares DNS lookup */
+#cmakedefine USE_RESOLV_ARES 1
 
-/* Define if you want to enable Win32 threaded DNS lookup */
-#cmakedefine USE_THREADS_WIN32 1
+/* Define if you want to enable threaded DNS lookup */
+#cmakedefine USE_RESOLV_THREADED 1
 
 /* if GnuTLS is enabled */
 #cmakedefine USE_GNUTLS 1
@@ -686,15 +680,15 @@
 /* if wolfSSL has the wolfSSL_UseALPN function. */
 #cmakedefine HAVE_WOLFSSL_USEALPN 1
 
-/* if wolfSSL has the wolfSSL_DES_ecb_encrypt function. */
-#cmakedefine HAVE_WOLFSSL_DES_ECB_ENCRYPT 1
-
 /* if wolfSSL has the wolfSSL_BIO_new function. */
 #cmakedefine HAVE_WOLFSSL_BIO_NEW 1
 
 /* if wolfSSL has the wolfSSL_BIO_set_shutdown function. */
 #cmakedefine HAVE_WOLFSSL_BIO_SET_SHUTDOWN 1
 
+/* if wolfSSL has the wc_Des_EcbEncrypt function. */
+#cmakedefine HAVE_WC_DES_ECBENCRYPT 1
+
 /* if libssh is in use */
 #cmakedefine USE_LIBSSH 1
 
@@ -713,9 +707,6 @@
 /* if AmiSSL is in use */
 #cmakedefine USE_AMISSL 1
 
-/* if librtmp/rtmpdump is in use */
-#cmakedefine USE_LIBRTMP 1
-
 /* if GSASL is in use */
 #cmakedefine USE_GSASL 1
 
diff --git a/Utilities/cmcurl/lib/curl_ctype.h b/Utilities/cmcurl/lib/curl_ctype.h
index 153a8f9..f3291ad 100644
--- a/Utilities/cmcurl/lib/curl_ctype.h
+++ b/Utilities/cmcurl/lib/curl_ctype.h
@@ -24,8 +24,8 @@
  *
  ***************************************************************************/
 
-#define ISLOWHEXALHA(x) (((x) >= 'a') && ((x) <= 'f'))
-#define ISUPHEXALHA(x)  (((x) >= 'A') && ((x) <= 'F'))
+#define ISLOWHEXALPHA(x) (((x) >= 'a') && ((x) <= 'f'))
+#define ISUPHEXALPHA(x)  (((x) >= 'A') && ((x) <= 'F'))
 
 #define ISLOWCNTRL(x) ((unsigned char)(x) <= 0x1f)
 #define IS7F(x)       ((x) == 0x7f)
@@ -36,7 +36,7 @@
 #define ISGRAPH(x)  (ISLOWPRINT(x) || (((x) > ' ') && ((x) <= 0x7e)))
 #define ISCNTRL(x)  (ISLOWCNTRL(x) || IS7F(x))
 #define ISALPHA(x)  (ISLOWER(x) || ISUPPER(x))
-#define ISXDIGIT(x) (ISDIGIT(x) || ISLOWHEXALHA(x) || ISUPHEXALHA(x))
+#define ISXDIGIT(x) (ISDIGIT(x) || ISLOWHEXALPHA(x) || ISUPHEXALPHA(x))
 #define ISODIGIT(x) (((x) >= '0') && ((x) <= '7'))
 #define ISALNUM(x)  (ISDIGIT(x) || ISLOWER(x) || ISUPPER(x))
 #define ISUPPER(x)  (((x) >= 'A') && ((x) <= 'Z'))
@@ -44,9 +44,9 @@
 #define ISDIGIT(x)  (((x) >= '0') && ((x) <= '9'))
 #define ISBLANK(x)  (((x) == ' ') || ((x) == '\t'))
 #define ISSPACE(x)  (ISBLANK(x) || (((x) >= 0xa) && ((x) <= 0x0d)))
-#define ISURLPUNTCS(x) (((x) == '-') || ((x) == '.') || ((x) == '_') || \
-                        ((x) == '~'))
+#define ISURLPUNTCS(x)                                           \
+  (((x) == '-') || ((x) == '.') || ((x) == '_') || ((x) == '~'))
 #define ISUNRESERVED(x) (ISALNUM(x) || ISURLPUNTCS(x))
-#define ISNEWLINE(x) (((x) == '\n') || (x) == '\r')
+#define ISNEWLINE(x)    (((x) == '\n') || (x) == '\r')
 
 #endif /* HEADER_CURL_CTYPE_H */
diff --git a/Utilities/cmcurl/lib/curl_fopen.c b/Utilities/cmcurl/lib/curl_fopen.c
index b7a67aa..cc888f7 100644
--- a/Utilities/cmcurl/lib/curl_fopen.c
+++ b/Utilities/cmcurl/lib/curl_fopen.c
@@ -103,7 +103,14 @@
     return CURLE_OK;
   }
   curlx_fclose(*fh);
+#ifdef HAVE_GETEUID
+  /* If the existing file is not owned by the user, do not inherit
+   * its permissions at the temp file created below. The permissions
+   * might be unsuitable for holding user private data. */
+  if(sb.st_uid != geteuid())
+    sb.st_mode = 0;
 #endif
+#endif /* !_WIN32 */
   *fh = NULL;
 
   result = Curl_rand_alnum(data, randbuf, sizeof(randbuf));
diff --git a/Utilities/cmcurl/lib/curl_get_line.c b/Utilities/cmcurl/lib/curl_get_line.c
index 85b3525..6fcd043 100644
--- a/Utilities/cmcurl/lib/curl_get_line.c
+++ b/Utilities/cmcurl/lib/curl_get_line.c
@@ -40,6 +40,8 @@
   while(1) {
     size_t rlen;
     const char *b = fgets(buffer, sizeof(buffer), input);
+    if(!b && ferror(input))
+      return CURLE_READ_ERROR;
 
     *eof = feof(input);
 
diff --git a/Utilities/cmcurl/lib/curl_gssapi.c b/Utilities/cmcurl/lib/curl_gssapi.c
index c0eb1c4..650d190 100644
--- a/Utilities/cmcurl/lib/curl_gssapi.c
+++ b/Utilities/cmcurl/lib/curl_gssapi.c
@@ -55,7 +55,7 @@
 #define CURL_ALIGN8
 #endif
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 #endif
@@ -93,20 +93,20 @@
   char creds[250];
 };
 
-static OM_uint32
-stub_gss_init_sec_context(OM_uint32 *min,
-                          gss_cred_id_t initiator_cred_handle,
-                          struct stub_gss_ctx_id_t_desc **context,
-                          gss_name_t target_name,
-                          const gss_OID mech_type,
-                          OM_uint32 req_flags,
-                          OM_uint32 time_req,
-                          const gss_channel_bindings_t input_chan_bindings,
-                          gss_buffer_desc *input_token,
-                          gss_OID *actual_mech_type,
-                          gss_buffer_desc *output_token,
-                          OM_uint32 *ret_flags,
-                          OM_uint32 *time_rec)
+static OM_uint32 stub_gss_init_sec_context(
+  OM_uint32 *min,
+  gss_cred_id_t initiator_cred_handle,
+  struct stub_gss_ctx_id_t_desc **context,
+  gss_name_t target_name,
+  const gss_OID mech_type,
+  OM_uint32 req_flags,
+  OM_uint32 time_req,
+  const gss_channel_bindings_t input_chan_bindings,
+  gss_buffer_desc *input_token,
+  gss_OID *actual_mech_type,
+  gss_buffer_desc *output_token,
+  OM_uint32 *ret_flags,
+  OM_uint32 *time_rec)
 {
   struct stub_gss_ctx_id_t_desc *ctx = NULL;
 
@@ -283,10 +283,10 @@
   return GSS_S_CONTINUE_NEEDED;
 }
 
-static OM_uint32
-stub_gss_delete_sec_context(OM_uint32 *min,
-                            struct stub_gss_ctx_id_t_desc **context,
-                            gss_buffer_t output_token)
+static OM_uint32 stub_gss_delete_sec_context(
+  OM_uint32 *min,
+  struct stub_gss_ctx_id_t_desc **context,
+  gss_buffer_t output_token)
 {
   (void)output_token;
 
@@ -441,7 +441,7 @@
 }
 #endif /* CURLVERBOSE */
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic pop
 #endif
 
diff --git a/Utilities/cmcurl/lib/curl_gssapi.h b/Utilities/cmcurl/lib/curl_gssapi.h
index 0d36097..fc3759e 100644
--- a/Utilities/cmcurl/lib/curl_gssapi.h
+++ b/Utilities/cmcurl/lib/curl_gssapi.h
@@ -44,7 +44,7 @@
                                     OM_uint32 *ret_flags);
 
 OM_uint32 Curl_gss_delete_sec_context(OM_uint32 *min,
-                                      gss_ctx_id_t *context_handle,
+                                      gss_ctx_id_t *context,
                                       gss_buffer_t output_token);
 
 #ifdef CURLVERBOSE
diff --git a/Utilities/cmcurl/lib/curl_hmac.h b/Utilities/cmcurl/lib/curl_hmac.h
index b9f218f..301d44f 100644
--- a/Utilities/cmcurl/lib/curl_hmac.h
+++ b/Utilities/cmcurl/lib/curl_hmac.h
@@ -57,14 +57,14 @@
 struct HMAC_context *Curl_HMAC_init(const struct HMAC_params *hashparams,
                                     const unsigned char *key,
                                     unsigned int keylen);
-int Curl_HMAC_update(struct HMAC_context *ctxt,
-                     const unsigned char *data,
-                     unsigned int len);
+void Curl_HMAC_update(struct HMAC_context *ctxt,
+                      const unsigned char *data,
+                      unsigned int len);
 int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *output);
 
 CURLcode Curl_hmacit(const struct HMAC_params *hashparams,
                      const unsigned char *key, const size_t keylen,
-                     const unsigned char *data, const size_t datalen,
+                     const unsigned char *data, size_t datalen,
                      unsigned char *output);
 
 #endif
diff --git a/Utilities/cmcurl/lib/curl_ldap.h b/Utilities/cmcurl/lib/curl_ldap.h
index 7c1e1c4..0ccc128 100644
--- a/Utilities/cmcurl/lib/curl_ldap.h
+++ b/Utilities/cmcurl/lib/curl_ldap.h
@@ -25,9 +25,6 @@
  ***************************************************************************/
 extern const struct Curl_protocol Curl_protocol_ldap;
 
-extern const struct Curl_scheme Curl_scheme_ldap;
-extern const struct Curl_scheme Curl_scheme_ldaps;
-
 void Curl_ldap_version(char *buf, size_t bufsz);
 
 #endif /* HEADER_CURL_LDAP_H */
diff --git a/Utilities/cmcurl/lib/curl_md5.h b/Utilities/cmcurl/lib/curl_md5.h
index 042c5f5..8a0cc26 100644
--- a/Utilities/cmcurl/lib/curl_md5.h
+++ b/Utilities/cmcurl/lib/curl_md5.h
@@ -54,7 +54,7 @@
 extern const struct HMAC_params Curl_HMAC_MD5;
 
 CURLcode Curl_md5it(unsigned char *output, const unsigned char *input,
-                    const size_t len);
+                    size_t len);
 
 struct MD5_context *Curl_MD5_init(const struct MD5_params *md5params);
 CURLcode Curl_MD5_update(struct MD5_context *context,
diff --git a/Utilities/cmcurl/lib/curl_ntlm_core.c b/Utilities/cmcurl/lib/curl_ntlm_core.c
index 4a9433b..4b2007b 100644
--- a/Utilities/cmcurl/lib/curl_ntlm_core.c
+++ b/Utilities/cmcurl/lib/curl_ntlm_core.c
@@ -49,59 +49,30 @@
      in NTLM type-3 messages.
  */
 
-#ifdef USE_MBEDTLS
-#include <mbedtls/version.h>
-#if MBEDTLS_VERSION_NUMBER < 0x03020000
-#error "mbedTLS 3.2.0 or later required"
-#endif
-#endif
-
 #if defined(USE_OPENSSL) && defined(HAVE_DES_ECB_ENCRYPT)
-#  define USE_OPENSSL_DES
-#elif defined(USE_WOLFSSL) && defined(HAVE_WOLFSSL_DES_ECB_ENCRYPT)
-#  define USE_OPENSSL_DES
-#elif defined(USE_MBEDTLS) && defined(HAVE_MBEDTLS_DES_CRYPT_ECB)
-#  define USE_MBEDTLS_DES
-#endif
 
-#ifdef USE_OPENSSL_DES
-
-#ifdef USE_OPENSSL
 #  include <openssl/des.h>
 #  ifdef OPENSSL_IS_AWSLC  /* for versions 1.2.0 to 1.30.1 */
 #    define DES_set_key_unchecked (void)DES_set_key
 #  endif
-#  define DESKEY(x) &x
-#else
+#  define USE_OPENSSL_DES
+
+#elif defined(USE_WOLFSSL) && defined(HAVE_WC_DES_ECBENCRYPT)
+
 #  include <wolfssl/options.h>
-#  include <wolfssl/openssl/des.h>
-#  include <wolfssl/version.h>
-#  ifdef OPENSSL_COEXIST
-#    define DES_key_schedule      WOLFSSL_DES_key_schedule
-#    define DES_cblock            WOLFSSL_DES_cblock
-#    define DES_set_odd_parity    wolfSSL_DES_set_odd_parity
-#    define DES_set_key           wolfSSL_DES_set_key
-#    define DES_set_key_unchecked wolfSSL_DES_set_key_unchecked
-#    define DES_ecb_encrypt       wolfSSL_DES_ecb_encrypt
-#    define DESKEY(x)             ((WOLFSSL_DES_key_schedule *)(x))
-#    if defined(LIBWOLFSSL_VERSION_HEX) && LIBWOLFSSL_VERSION_HEX >= 0x05007006
-#      define DES_ENCRYPT WC_DES_ENCRYPT
-#      define DES_DECRYPT WC_DES_DECRYPT
-#    endif
-#  else
-#    define DESKEY(x) &x
-#  endif
-#endif
+#  include <wolfssl/wolfcrypt/des3.h>
+#  define USE_WOLFSSL_DES
 
 #elif defined(USE_GNUTLS)
-
 #  include <nettle/des.h>
 #  define USE_CURL_DES_SET_ODD_PARITY
-
-#elif defined(USE_MBEDTLS_DES)
-
+#elif defined(USE_MBEDTLS) && defined(HAVE_MBEDTLS_DES_CRYPT_ECB)
+#  include <mbedtls/version.h>
+#  if MBEDTLS_VERSION_NUMBER < 0x03020000
+#  error "mbedTLS 3.2.0 or later required"
+#  endif
 #  include <mbedtls/des.h>
-
+#  define USE_MBEDTLS_DES
 #elif defined(USE_OS400CRYPTO)
 #  include "cipher.mih"  /* mih/cipher */
 #  define USE_CURL_DES_SET_ODD_PARITY
@@ -170,7 +141,7 @@
   key[4] = (char)(((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4));
   key[5] = (char)(((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5));
   key[6] = (char)(((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6));
-  key[7] = (char) ((key_56[6] << 1) & 0xFF);
+  key[7] = (char)((key_56[6] << 1) & 0xFF);
 }
 
 #ifdef USE_OPENSSL_DES
@@ -192,8 +163,19 @@
   DES_set_key_unchecked(&key, ks);
 }
 
-#elif defined(USE_GNUTLS)
+#elif defined(USE_WOLFSSL_DES)
+static void setup_des_key(const unsigned char *key_56, Des *des)
+{
+  byte key[8];
 
+  /* Expand the 56-bit key to 64 bits */
+  extend_key_56_to_64(key_56, (char *)key);
+
+  /* Set the key */
+  wc_Des_SetKey(des, key, NULL, 0);
+}
+
+#elif defined(USE_GNUTLS)
 static void setup_des_key(const unsigned char *key_56, struct des_ctx *des)
 {
   char key[8];
@@ -209,7 +191,6 @@
 }
 
 #elif defined(USE_MBEDTLS_DES)
-
 static bool encrypt_des(const unsigned char *in, unsigned char *out,
                         const unsigned char *key_56)
 {
@@ -229,7 +210,6 @@
 }
 
 #elif defined(USE_OS400CRYPTO)
-
 static bool encrypt_des(const unsigned char *in, unsigned char *out,
                         const unsigned char *key_56)
 {
@@ -253,7 +233,6 @@
 }
 
 #elif defined(USE_WIN32_CRYPTO)
-
 static bool encrypt_des(const unsigned char *in, unsigned char *out,
                         const unsigned char *key_56)
 {
@@ -316,17 +295,25 @@
 #ifdef USE_OPENSSL_DES
   DES_key_schedule ks;
 
-  setup_des_key(keys, DESKEY(ks));
+  setup_des_key(keys, &ks);
   DES_ecb_encrypt((DES_cblock *)CURL_UNCONST(plaintext),
-                  (DES_cblock *)results, DESKEY(ks), DES_ENCRYPT);
+                  (DES_cblock *)results, &ks, DES_ENCRYPT);
 
-  setup_des_key(keys + 7, DESKEY(ks));
+  setup_des_key(keys + 7, &ks);
   DES_ecb_encrypt((DES_cblock *)CURL_UNCONST(plaintext),
-                  (DES_cblock *)(results + 8), DESKEY(ks), DES_ENCRYPT);
+                  (DES_cblock *)(results + 8), &ks, DES_ENCRYPT);
 
-  setup_des_key(keys + 14, DESKEY(ks));
+  setup_des_key(keys + 14, &ks);
   DES_ecb_encrypt((DES_cblock *)CURL_UNCONST(plaintext),
-                  (DES_cblock *)(results + 16), DESKEY(ks), DES_ENCRYPT);
+                  (DES_cblock *)(results + 16), &ks, DES_ENCRYPT);
+#elif defined(USE_WOLFSSL_DES)
+  Des des;
+  setup_des_key(keys, &des);
+  wc_Des_EcbEncrypt(&des, results, plaintext, DES_KEY_SIZE);
+  setup_des_key(keys + 7, &des);
+  wc_Des_EcbEncrypt(&des, results + 8, plaintext, DES_KEY_SIZE);
+  setup_des_key(keys + 14, &des);
+  wc_Des_EcbEncrypt(&des, results + 16, plaintext, DES_KEY_SIZE);
 #elif defined(USE_GNUTLS)
   struct des_ctx des;
   setup_des_key(keys, &des);
@@ -364,17 +351,22 @@
 
   {
     /* Create LanManager hashed password. */
-
 #ifdef USE_OPENSSL_DES
     DES_key_schedule ks;
 
-    setup_des_key(pw, DESKEY(ks));
+    setup_des_key(pw, &ks);
     DES_ecb_encrypt((DES_cblock *)CURL_UNCONST(magic),
-                    (DES_cblock *)lmbuffer, DESKEY(ks), DES_ENCRYPT);
+                    (DES_cblock *)lmbuffer, &ks, DES_ENCRYPT);
 
-    setup_des_key(pw + 7, DESKEY(ks));
+    setup_des_key(pw + 7, &ks);
     DES_ecb_encrypt((DES_cblock *)CURL_UNCONST(magic),
-                    (DES_cblock *)(lmbuffer + 8), DESKEY(ks), DES_ENCRYPT);
+                    (DES_cblock *)(lmbuffer + 8), &ks, DES_ENCRYPT);
+#elif defined(USE_WOLFSSL_DES)
+    Des des;
+    setup_des_key(pw, &des);
+    wc_Des_EcbEncrypt(&des, lmbuffer, magic, DES_KEY_SIZE);
+    setup_des_key(pw + 7, &des);
+    wc_Des_EcbEncrypt(&des, lmbuffer + 8, magic, DES_KEY_SIZE);
 #elif defined(USE_GNUTLS)
     struct des_ctx des;
     setup_des_key(pw, &des);
diff --git a/Utilities/cmcurl/lib/curl_rtmp.c b/Utilities/cmcurl/lib/curl_rtmp.c
deleted file mode 100644
index 75b8091..0000000
--- a/Utilities/cmcurl/lib/curl_rtmp.c
+++ /dev/null
@@ -1,326 +0,0 @@
-/***************************************************************************
- *                      _   _ ____  _
- *  Project         ___| | | |  _ \| |
- *                 / __| | | | |_) | |
- *                | (__| |_| |  _ <| |___
- *                 \___|\___/|_| \_\_____|
- *
- * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) Howard Chu, <hyc@highlandsun.com>
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * 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_rtmp.h"
-#include "urldata.h"
-
-#ifdef USE_LIBRTMP
-
-#include "url.h"
-#include "curlx/nonblock.h"
-#include "progress.h" /* for Curl_pgrsSetUploadSize */
-#include "transfer.h"
-#include "bufref.h"
-
-#include <librtmp/rtmp.h>
-
-#if defined(USE_WINSOCK) || defined(LWIP_SO_SNDRCVTIMEO_NONSTANDARD)
-#define SET_RCVTIMEO(tv, s)  int tv = s * 1000
-#else
-#define SET_RCVTIMEO(tv, s)  struct timeval tv = { s, 0 }
-#endif
-
-#define DEF_BUFTIME    (2 * 60 * 60 * 1000)    /* 2 hours */
-
-/* meta key for storing RTMP* at connection */
-#define CURL_META_RTMP_CONN   "meta:proto:rtmp:conn"
-
-static Curl_recv rtmp_recv;
-static Curl_send rtmp_send;
-
-static void rtmp_conn_dtor(void *key, size_t klen, void *entry)
-{
-  RTMP *r = entry;
-  (void)key;
-  (void)klen;
-  RTMP_Close(r);
-  RTMP_Free(r);
-}
-
-static CURLcode rtmp_setup_connection(struct Curl_easy *data,
-                                      struct connectdata *conn)
-{
-  RTMP *r = RTMP_Alloc();
-  if(!r ||
-    Curl_conn_meta_set(conn, CURL_META_RTMP_CONN, r, rtmp_conn_dtor))
-    return CURLE_OUT_OF_MEMORY;
-
-  RTMP_Init(r);
-  RTMP_SetBufferMS(r, DEF_BUFTIME);
-  if(!RTMP_SetupURL(r, CURL_UNCONST(Curl_bufref_ptr(&data->state.url))))
-    /* rtmp_conn_dtor() performs the cleanup */
-    return CURLE_URL_MALFORMAT;
-  return CURLE_OK;
-}
-
-static CURLcode rtmp_connect(struct Curl_easy *data, bool *done)
-{
-  struct connectdata *conn = data->conn;
-  RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN);
-  SET_RCVTIMEO(tv, 10);
-
-  if(!r)
-    return CURLE_FAILED_INIT;
-
-  if(conn->sock[FIRSTSOCKET] > INT_MAX) {
-    /* The socket value is invalid for rtmp. */
-    return CURLE_FAILED_INIT;
-  }
-
-  r->m_sb.sb_socket = (int)conn->sock[FIRSTSOCKET];
-
-  /* We have to know if it is a write before we send the
-   * connect request packet
-   */
-  if(data->state.upload)
-    r->Link.protocol |= RTMP_FEATURE_WRITE;
-
-  /* For plain streams, use the buffer toggle trick to keep data flowing */
-  if(!(r->Link.lFlags & RTMP_LF_LIVE) &&
-     !(r->Link.protocol & RTMP_FEATURE_HTTP))
-    r->Link.lFlags |= RTMP_LF_BUFX;
-
-  (void)curlx_nonblock(r->m_sb.sb_socket, FALSE);
-  setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO,
-             (char *)&tv, sizeof(tv));
-
-  if(!RTMP_Connect1(r, NULL))
-    return CURLE_FAILED_INIT;
-
-  /* Clients must send a periodic BytesReceived report to the server */
-  r->m_bSendCounter = TRUE;
-
-  *done = TRUE;
-  conn->recv[FIRSTSOCKET] = rtmp_recv;
-  conn->send[FIRSTSOCKET] = rtmp_send;
-  return CURLE_OK;
-}
-
-static CURLcode rtmp_do(struct Curl_easy *data, bool *done)
-{
-  struct connectdata *conn = data->conn;
-  RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN);
-
-  if(!r || !RTMP_ConnectStream(r, 0))
-    return CURLE_FAILED_INIT;
-
-  if(data->state.upload) {
-    Curl_pgrsSetUploadSize(data, data->state.infilesize);
-    Curl_xfer_setup_send(data, FIRSTSOCKET);
-  }
-  else
-    Curl_xfer_setup_recv(data, FIRSTSOCKET, -1);
-  *done = TRUE;
-  return CURLE_OK;
-}
-
-static CURLcode rtmp_done(struct Curl_easy *data, CURLcode status,
-                          bool premature)
-{
-  (void)data;
-  (void)status;
-  (void)premature;
-
-  return CURLE_OK;
-}
-
-static CURLcode rtmp_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                bool dead_connection)
-{
-  RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN);
-  (void)data;
-  (void)dead_connection;
-  if(r)
-    Curl_conn_meta_remove(conn, CURL_META_RTMP_CONN);
-  return CURLE_OK;
-}
-
-static CURLcode rtmp_recv(struct Curl_easy *data, int sockindex, char *buf,
-                          size_t len, size_t *pnread)
-{
-  struct connectdata *conn = data->conn;
-  RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN);
-  CURLcode result = CURLE_OK;
-  ssize_t rv;
-
-  (void)sockindex;
-  *pnread = 0;
-  if(!r)
-    return CURLE_FAILED_INIT;
-
-  rv = RTMP_Read(r, buf, curlx_uztosi(len));
-  if(!curlx_sztouz(rv, pnread)) {
-    if(r->m_read.status == RTMP_READ_COMPLETE ||
-       r->m_read.status == RTMP_READ_EOF) {
-      data->req.size = data->req.bytecount;
-    }
-    else
-      result = CURLE_RECV_ERROR;
-  }
-
-  return result;
-}
-
-static CURLcode rtmp_send(struct Curl_easy *data, int sockindex,
-                          const uint8_t *buf, size_t len, bool eos,
-                          size_t *pnwritten)
-{
-  struct connectdata *conn = data->conn;
-  RTMP *r = Curl_conn_meta_get(conn, CURL_META_RTMP_CONN);
-  ssize_t rv;
-
-  (void)sockindex;
-  (void)eos;
-  *pnwritten = 0;
-  if(!r)
-    return CURLE_FAILED_INIT;
-
-  rv = RTMP_Write(r, (const char *)buf, curlx_uztosi(len));
-  if(!curlx_sztouz(rv, pnwritten))
-    return CURLE_SEND_ERROR;
-
-  return CURLE_OK;
-}
-
-void Curl_rtmp_version(char *version, size_t len)
-{
-  char suff[2];
-  if(RTMP_LIB_VERSION & 0xff) {
-    suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1;
-    suff[1] = '\0';
-  }
-  else
-    suff[0] = '\0';
-
-  curl_msnprintf(version, len, "librtmp/%d.%d%s",
-                 RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff,
-                 suff);
-}
-
-/*
- * RTMP protocol handler.h, based on https://rtmpdump.mplayerhq.hu/
- */
-
-static const struct Curl_protocol Curl_protocol_rtmp = {
-  rtmp_setup_connection,                /* setup_connection */
-  rtmp_do,                              /* do_it */
-  rtmp_done,                            /* done */
-  ZERO_NULL,                            /* do_more */
-  rtmp_connect,                         /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  ZERO_NULL,                            /* proto_pollset */
-  ZERO_NULL,                            /* doing_pollset */
-  ZERO_NULL,                            /* domore_pollset */
-  ZERO_NULL,                            /* perform_pollset */
-  rtmp_disconnect,                      /* disconnect */
-  ZERO_NULL,                            /* write_resp */
-  ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  ZERO_NULL,                            /* follow */
-};
-
-#endif /* USE_LIBRTMP */
-
-const struct Curl_scheme Curl_scheme_rtmp = {
-  "rtmp",                               /* scheme */
-#ifndef USE_LIBRTMP
-  ZERO_NULL,
-#else
-  &Curl_protocol_rtmp,
-#endif
-  CURLPROTO_RTMP,                       /* protocol */
-  CURLPROTO_RTMP,                       /* family */
-  PROTOPT_NONE,                         /* flags */
-  PORT_RTMP,                            /* defport */
-};
-
-const struct Curl_scheme Curl_scheme_rtmpt = {
-  "rtmpt",                              /* scheme */
-#ifndef USE_LIBRTMP
-  ZERO_NULL,
-#else
-  &Curl_protocol_rtmp,
-#endif
-  CURLPROTO_RTMPT,                      /* protocol */
-  CURLPROTO_RTMPT,                      /* family */
-  PROTOPT_NONE,                         /* flags */
-  PORT_RTMPT,                           /* defport */
-};
-
-const struct Curl_scheme Curl_scheme_rtmpe = {
-  "rtmpe",                              /* scheme */
-#ifndef USE_LIBRTMP
-  ZERO_NULL,
-#else
-  &Curl_protocol_rtmp,
-#endif
-  CURLPROTO_RTMPE,                      /* protocol */
-  CURLPROTO_RTMPE,                      /* family */
-  PROTOPT_NONE,                         /* flags */
-  PORT_RTMP,                            /* defport */
-};
-
-const struct Curl_scheme Curl_scheme_rtmpte = {
-  "rtmpte",                             /* scheme */
-#ifndef USE_LIBRTMP
-  ZERO_NULL,
-#else
-  &Curl_protocol_rtmp,
-#endif
-  CURLPROTO_RTMPTE,                     /* protocol */
-  CURLPROTO_RTMPTE,                     /* family */
-  PROTOPT_NONE,                         /* flags */
-  PORT_RTMPT,                           /* defport */
-};
-
-const struct Curl_scheme Curl_scheme_rtmps = {
-  "rtmps",                              /* scheme */
-#ifndef USE_LIBRTMP
-  ZERO_NULL,
-#else
-  &Curl_protocol_rtmp,
-#endif
-  CURLPROTO_RTMPS,                      /* protocol */
-  CURLPROTO_RTMP,                       /* family */
-  PROTOPT_NONE,                         /* flags */
-  PORT_RTMPS,                           /* defport */
-};
-
-const struct Curl_scheme Curl_scheme_rtmpts = {
-  "rtmpts",                             /* scheme */
-#ifndef USE_LIBRTMP
-  ZERO_NULL,
-#else
-  &Curl_protocol_rtmp,
-#endif
-  CURLPROTO_RTMPTS,                     /* protocol */
-  CURLPROTO_RTMPT,                      /* family */
-  PROTOPT_NONE,                         /* flags */
-  PORT_RTMPS,                           /* defport */
-};
diff --git a/Utilities/cmcurl/lib/curl_rtmp.h b/Utilities/cmcurl/lib/curl_rtmp.h
deleted file mode 100644
index e57d20c..0000000
--- a/Utilities/cmcurl/lib/curl_rtmp.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#ifndef HEADER_CURL_RTMP_H
-#define HEADER_CURL_RTMP_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) Howard Chu, <hyc@highlandsun.com>
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * 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
- *
- ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_rtmp;
-extern const struct Curl_scheme Curl_scheme_rtmpt;
-extern const struct Curl_scheme Curl_scheme_rtmpe;
-extern const struct Curl_scheme Curl_scheme_rtmpte;
-extern const struct Curl_scheme Curl_scheme_rtmps;
-extern const struct Curl_scheme Curl_scheme_rtmpts;
-#ifdef USE_LIBRTMP
-void Curl_rtmp_version(char *version, size_t len);
-#endif
-
-#endif /* HEADER_CURL_RTMP_H */
diff --git a/Utilities/cmcurl/lib/curl_sasl.c b/Utilities/cmcurl/lib/curl_sasl.c
index b2eb1fe..60f0859 100644
--- a/Utilities/cmcurl/lib/curl_sasl.c
+++ b/Utilities/cmcurl/lib/curl_sasl.c
@@ -415,9 +415,8 @@
       data->set.str[STRING_SERVICE_NAME] :
       sctx->sasl->params->service;
     const char *hostname;
-    int port;
 
-    Curl_conn_get_current_host(data, FIRSTSOCKET, &hostname, &port);
+    Curl_conn_get_current_host(data, FIRSTSOCKET, &hostname, NULL);
 
     sctx->mech = SASL_MECH_STRING_NTLM;
     sctx->state1 = SASL_NTLM;
diff --git a/Utilities/cmcurl/lib/curl_setup.h b/Utilities/cmcurl/lib/curl_setup.h
index 90d2ff7..2494152 100644
--- a/Utilities/cmcurl/lib/curl_setup.h
+++ b/Utilities/cmcurl/lib/curl_setup.h
@@ -224,7 +224,7 @@
 /* ================================================================ */
 
 /* Give calloc a chance to be dragging in early, so we do not redefine */
-#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
+#ifdef HAVE_THREADS_POSIX
 #  include <pthread.h>
 #endif
 
@@ -262,9 +262,6 @@
 #  ifndef CURL_DISABLE_RTSP
 #  define CURL_DISABLE_RTSP
 #  endif
-#  ifndef CURL_DISABLE_SMB
-#  define CURL_DISABLE_SMB
-#  endif
 #  ifndef CURL_DISABLE_SMTP
 #  define CURL_DISABLE_SMTP
 #  endif
@@ -372,16 +369,14 @@
 #endif
 
 /* based on logic in "curl/mprintf.h" */
-#if (defined(__GNUC__) || defined(__clang__) ||                         \
-  defined(__IAR_SYSTEMS_ICC__)) &&                                      \
-  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) &&         \
+#if (defined(__GNUC__) || defined(__clang__) ||                 \
+     defined(__IAR_SYSTEMS_ICC__)) &&                           \
+  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
   !defined(CURL_NO_FMT_CHECKS)
 #if defined(__MINGW32__) && !defined(__clang__)
-#define CURL_PRINTF(fmt, arg) \
-  __attribute__((format(gnu_printf, fmt, arg)))
+#define CURL_PRINTF(fmt, arg) __attribute__((format(gnu_printf, fmt, arg)))
 #else
-#define CURL_PRINTF(fmt, arg) \
-  __attribute__((format(__printf__, fmt, arg)))
+#define CURL_PRINTF(fmt, arg) __attribute__((format(__printf__, fmt, arg)))
 #endif
 #else
 #define CURL_PRINTF(fmt, arg)
@@ -399,7 +394,7 @@
    (defined(__GNUC__) && __GNUC__ <= 14)) &&                \
   defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
   !defined(__ENVIRONMENT_OS_VERSION_MIN_REQUIRED__)
-#define __ENVIRONMENT_OS_VERSION_MIN_REQUIRED__             \
+#define __ENVIRONMENT_OS_VERSION_MIN_REQUIRED__ \
   __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
 #endif
 
@@ -446,7 +441,7 @@
 #    if !(defined(__NEWLIB__) || \
           (defined(__CLIB2__) && defined(__THREAD_SAFE)))
        /* disable threaded resolver with clib2 - requires newlib or clib-ts */
-#      undef USE_THREADS_POSIX
+#      undef USE_RESOLV_THREADED
 #    endif
 #  endif
 #  include <exec/types.h>
@@ -700,6 +695,16 @@
 
 #endif /* _WIN32 */
 
+/* We want to use mutex when available. */
+#if defined(HAVE_THREADS_POSIX) || defined(_WIN32)
+#define USE_MUTEX
+#endif
+
+/* threaded resolver is the only feature requiring threads. */
+#ifdef USE_RESOLV_THREADED
+#define USE_THREADS
+#endif
+
 /* ---------------------------------------------------------------- */
 /*             resolver specialty compile-time defines              */
 /*         CURLRES_* defines to use in the host*.c sources          */
@@ -717,12 +722,10 @@
 #  define CURLRES_IPV4
 #endif
 
-#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
+#ifdef USE_RESOLV_THREADED
 #  define CURLRES_ASYNCH
-#  define CURLRES_THREADED
-#elif defined(USE_ARES)
+#elif defined(USE_RESOLV_ARES)
 #  define CURLRES_ASYNCH
-#  define CURLRES_ARES
 /* now undef the stock libc functions to avoid them being used */
 #  undef HAVE_GETADDRINFO
 #  undef HAVE_FREEADDRINFO
@@ -743,8 +746,7 @@
 #endif
 
 #if defined(USE_GNUTLS) || defined(USE_OPENSSL) || defined(USE_MBEDTLS) || \
-  defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || \
-  defined(USE_RUSTLS)
+  defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || defined(USE_RUSTLS)
 #define USE_SSL    /* SSL support has been enabled */
 #endif
 
@@ -760,24 +762,24 @@
 #endif
 
 /* Single point where USE_SPNEGO definition might be defined */
-#if !defined(CURL_DISABLE_NEGOTIATE_AUTH) && \
-    (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI))
+#if !defined(CURL_DISABLE_NEGOTIATE_AUTH) &&          \
+  (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI))
 #define USE_SPNEGO
 #endif
 
 /* Single point where USE_KERBEROS5 definition might be defined */
-#if !defined(CURL_DISABLE_KERBEROS_AUTH) && \
-    (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI))
+#if !defined(CURL_DISABLE_KERBEROS_AUTH) &&           \
+  (defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI))
 #define USE_KERBEROS5
 #endif
 
 /* Single point where USE_NTLM definition might be defined */
-#ifndef CURL_DISABLE_NTLM
-#  if (defined(USE_OPENSSL) && defined(HAVE_DES_ECB_ENCRYPT)) ||        \
-  defined(USE_GNUTLS) ||                                                \
-  (defined(USE_MBEDTLS) && defined(HAVE_MBEDTLS_DES_CRYPT_ECB)) ||      \
-  defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) ||              \
-  (defined(USE_WOLFSSL) && defined(HAVE_WOLFSSL_DES_ECB_ENCRYPT))
+#ifdef CURL_ENABLE_NTLM
+#  if (defined(USE_OPENSSL) && defined(HAVE_DES_ECB_ENCRYPT)) ||   \
+  defined(USE_GNUTLS) ||                                           \
+  (defined(USE_MBEDTLS) && defined(HAVE_MBEDTLS_DES_CRYPT_ECB)) || \
+  defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) ||         \
+  (defined(USE_WOLFSSL) && defined(HAVE_WC_DES_ECBENCRYPT))
 #    define USE_CURL_NTLM_CORE
 #  endif
 #  if defined(USE_CURL_NTLM_CORE) || defined(USE_WINDOWS_SSPI)
@@ -789,6 +791,16 @@
 #define USE_SSH
 #endif
 
+/* GCC <4.6 does not support '#pragma GCC diagnostic push' and does not support
+   'pragma GCC diagnostic' inside functions.
+   Use CURL_HAVE_DIAG to guard the above in the curl codebase, instead of
+   defined(__GNUC__) || defined(__clang__).
+ */
+#if defined(__clang__) || (defined(__GNUC__) && \
+  ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 6))))
+#define CURL_HAVE_DIAG
+#endif
+
 /*
  * Provide a mechanism to silence picky compilers, such as gcc 4.6+.
  * Parameters should of course normally not be unused, but for example when
@@ -819,8 +831,8 @@
 /* fallthrough attribute */
 
 #ifndef FALLTHROUGH
-#if (defined(__GNUC__) && __GNUC__ >= 7) || \
-    (defined(__clang__) && __clang_major__ >= 10)
+#if (defined(__GNUC__) && __GNUC__ >= 7) ||     \
+  (defined(__clang__) && __clang_major__ >= 10)
 #  define FALLTHROUGH()  __attribute__((fallthrough))
 #else
 #  define FALLTHROUGH()  do {} while(0)
@@ -1015,7 +1027,7 @@
  * 'bool' stuff compatible with HP-UX headers.
  */
 #if defined(__hpux) && !defined(HAVE_BOOL_T)
-   typedef int bool;
+typedef int bool;
 #  define false 0
 #  define true 1
 #  define HAVE_BOOL_T
@@ -1028,10 +1040,10 @@
  * global namespace though, so use bool_false and bool_true.
  */
 #ifndef HAVE_BOOL_T
-  typedef enum {
-    bool_false = 0,
-    bool_true  = 1
-  } bool;
+typedef enum {
+  bool_false = 0,
+  bool_true = 1
+} bool;
 
 /*
  * Use a define to let 'true' and 'false' use those enums. There
@@ -1322,14 +1334,14 @@
 extern curl_calloc_callback Curl_ccalloc;
 
 /*
- * Curl_safefree defined as a macro to allow MemoryTracking feature
- * to log free() calls at same location where Curl_safefree is used.
+ * curlx_safefree() defined as a macro to allow MemoryTracking feature
+ * to log free() calls at same location where curlx_safefree() is used.
  * This macro also assigns NULL to given pointer when free'd.
  */
-#define Curl_safefree(ptr) \
-  do {                     \
-    curlx_free(ptr);       \
-    (ptr) = NULL;          \
+#define curlx_safefree(ptr) \
+  do {                      \
+    curlx_free(ptr);        \
+    (ptr) = NULL;           \
   } while(0)
 
 #include <curl/curl.h> /* for CURL_EXTERN, curl_socket_t, mprintf.h */
@@ -1390,7 +1402,8 @@
                                       int line, const char *source);
 CURL_EXTERN int curl_dbg_sclose(curl_socket_t sockfd,
                                 int line, const char *source);
-CURL_EXTERN curl_socket_t curl_dbg_accept(curl_socket_t s, void *a, void *alen,
+CURL_EXTERN curl_socket_t curl_dbg_accept(curl_socket_t s,
+                                          void *saddr, void *saddrlen,
                                           int line, const char *source);
 #ifdef HAVE_ACCEPT4
 CURL_EXTERN curl_socket_t curl_dbg_accept4(curl_socket_t s, void *saddr,
@@ -1522,9 +1535,7 @@
 #define USE_HTTP2
 #endif
 
-#if (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \
-  (defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3)) || \
-  defined(USE_QUICHE)
+#if (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || defined(USE_QUICHE)
 
 #ifdef CURL_WITH_MULTI_SSL
 #error "MultiSSL combined with QUIC is not supported"
diff --git a/Utilities/cmcurl/lib/curl_sha256.h b/Utilities/cmcurl/lib/curl_sha256.h
index bc1512e..6c48a8e 100644
--- a/Utilities/cmcurl/lib/curl_sha256.h
+++ b/Utilities/cmcurl/lib/curl_sha256.h
@@ -38,7 +38,7 @@
 #endif
 
 CURLcode Curl_sha256it(unsigned char *output, const unsigned char *input,
-                       const size_t len);
+                       size_t len);
 
 #endif
 
diff --git a/Utilities/cmcurl/lib/curl_sha512_256.c b/Utilities/cmcurl/lib/curl_sha512_256.c
index 648e3e3..73b959c 100644
--- a/Utilities/cmcurl/lib/curl_sha512_256.c
+++ b/Utilities/cmcurl/lib/curl_sha512_256.c
@@ -28,12 +28,12 @@
 #include "curl_sha512_256.h"
 
 /* The recommended order of the TLS backends:
- * 1. OpenSSL
- * 2. GnuTLS
- * 3. wolfSSL
- * 4. Schannel SSPI
- * 5. mbedTLS
- * 6. Rustls
+ * 1. USE_OPENSSL
+ * 2. USE_WOLFSSL
+ * 3. USE_GNUTLS
+ * 4. USE_MBEDTLS (TBD)
+ * 5. USE_RUSTLS (TBD)
+ * 6. USE_WIN32_CRYPTO (TBD)
  * Skip the backend if it does not support the required algorithm */
 
 #ifdef USE_OPENSSL
@@ -66,6 +66,14 @@
 #  endif
 #endif /* USE_OPENSSL */
 
+#if !defined(HAS_SHA512_256_IMPLEMENTATION) && defined(USE_WOLFSSL)
+#  include <wolfssl/options.h>
+#  ifndef WOLFSSL_NOSHA512_256
+#    define USE_WOLFSSL_SHA512_256          1
+#    define HAS_SHA512_256_IMPLEMENTATION   1
+#  endif
+#endif
+
 #if !defined(HAS_SHA512_256_IMPLEMENTATION) && defined(USE_GNUTLS)
 #  include <nettle/sha.h>
 #  ifdef SHA512_256_DIGEST_SIZE
@@ -153,26 +161,63 @@
  */
 static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *context)
 {
-  CURLcode ret;
+  CURLcode result;
   Curl_sha512_256_ctx * const ctx = (Curl_sha512_256_ctx *)context;
 
 #ifdef NEED_NETBSD_SHA512_256_WORKAROUND
   /* Use a larger buffer to work around a bug in NetBSD:
      https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=58039 */
   unsigned char tmp_digest[CURL_SHA512_256_DIGEST_SIZE * 2];
-  ret = EVP_DigestFinal_ex(*ctx,
+  result = EVP_DigestFinal_ex(*ctx,
                            tmp_digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER;
-  if(ret == CURLE_OK)
+  if(result == CURLE_OK)
     memcpy(digest, tmp_digest, CURL_SHA512_256_DIGEST_SIZE);
   explicit_memset(tmp_digest, 0, sizeof(tmp_digest));
 #else /* !NEED_NETBSD_SHA512_256_WORKAROUND */
-  ret = EVP_DigestFinal_ex(*ctx, digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER;
+  result = EVP_DigestFinal_ex(*ctx, digest, NULL) ?
+    CURLE_OK : CURLE_SSL_CIPHER;
 #endif /* NEED_NETBSD_SHA512_256_WORKAROUND */
 
   EVP_MD_CTX_destroy(*ctx);
   *ctx = NULL;
 
-  return ret;
+  return result;
+}
+
+#elif defined(USE_WOLFSSL_SHA512_256)
+#include <wolfssl/wolfcrypt/sha512.h>
+
+#define CURL_SHA512_256_DIGEST_SIZE WC_SHA512_256_DIGEST_SIZE
+#define CURL_SHA512_256_BLOCK_SIZE  WC_SHA512_256_BLOCK_SIZE
+
+typedef struct wc_Sha512 Curl_sha512_256_ctx;
+
+static CURLcode Curl_sha512_256_init(void *ctx)
+{
+  if(wc_InitSha512_256(ctx))
+    return CURLE_FAILED_INIT;
+  return CURLE_OK;
+}
+
+static CURLcode Curl_sha512_256_update(void *ctx,
+                                       const unsigned char *data,
+                                       size_t length)
+{
+  do {
+    word32 ilen = (word32)CURLMIN(length, UINT_MAX);
+    if(wc_Sha512_256Update(ctx, data, ilen))
+      return CURLE_SSL_CIPHER;
+    length -= ilen;
+    data += ilen;
+  } while(length);
+  return CURLE_OK;
+}
+
+static CURLcode Curl_sha512_256_finish(unsigned char *digest, void *ctx)
+{
+  if(wc_Sha512_256Final(ctx, digest))
+    return CURLE_SSL_CIPHER;
+  return CURLE_OK;
 }
 
 #elif defined(USE_GNUTLS_SHA512_256)
@@ -247,10 +292,7 @@
 /* ** This implementation of SHA-512/256 hash calculation was originally ** *
  * ** written by Evgeny Grin (Karlson2k) for GNU libmicrohttpd.          ** *
  * ** The author ported the code to libcurl. The ported code is provided ** *
- * ** under curl license.                                                ** *
- * ** This is a minimal version with minimal optimizations. Performance  ** *
- * ** can be significantly improved. Big-endian store and load macros    ** *
- * ** are obvious targets for optimization.                              ** */
+ * ** under curl license.                                                ** */
 
 #ifdef __GNUC__
 #  if defined(__has_attribute) && defined(__STDC_VERSION__)
@@ -751,17 +793,17 @@
                            size_t input_size)
 {
   Curl_sha512_256_ctx ctx;
-  CURLcode res;
+  CURLcode result;
 
-  res = Curl_sha512_256_init(&ctx);
-  if(res != CURLE_OK)
-    return res;
+  result = Curl_sha512_256_init(&ctx);
+  if(result != CURLE_OK)
+    return result;
 
-  res = Curl_sha512_256_update(&ctx, (const void *)input, input_size);
+  result = Curl_sha512_256_update(&ctx, (const void *)input, input_size);
 
-  if(res != CURLE_OK) {
+  if(result != CURLE_OK) {
     (void)Curl_sha512_256_finish(output, &ctx);
-    return res;
+    return result;
   }
 
   return Curl_sha512_256_finish(output, &ctx);
diff --git a/Utilities/cmcurl/lib/curl_share.c b/Utilities/cmcurl/lib/curl_share.c
index 338727a..386a2b5 100644
--- a/Utilities/cmcurl/lib/curl_share.c
+++ b/Utilities/cmcurl/lib/curl_share.c
@@ -24,22 +24,61 @@
 #include "curl_setup.h"
 
 #include "urldata.h"
+#include "multiif.h"
+#include "curl_threads.h"
 #include "curl_share.h"
 #include "vtls/vtls.h"
 #include "vtls/vtls_scache.h"
 #include "hsts.h"
 #include "url.h"
 
+static void share_destroy(struct Curl_share *share)
+{
+  if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) {
+    Curl_cpool_destroy(&share->cpool);
+  }
+
+  Curl_dnscache_destroy(&share->dnscache);
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
+  Curl_cookie_cleanup(share->cookies);
+#endif
+
+#ifndef CURL_DISABLE_HSTS
+  Curl_hsts_cleanup(&share->hsts);
+#endif
+
+#ifdef USE_SSL
+  if(share->ssl_scache) {
+    Curl_ssl_scache_destroy(share->ssl_scache);
+    share->ssl_scache = NULL;
+  }
+#endif
+
+  Curl_psl_destroy(&share->psl);
+  Curl_close(&share->admin);
+
+#ifdef USE_MUTEX
+  Curl_mutex_destroy(&share->lock);
+#endif
+  share->magic = 0;
+  curlx_free(share);
+}
+
 CURLSH *curl_share_init(void)
 {
   struct Curl_share *share = curlx_calloc(1, sizeof(struct Curl_share));
   if(share) {
     share->magic = CURL_GOOD_SHARE;
     share->specifier |= (1 << CURL_LOCK_DATA_SHARE);
+#ifdef USE_MUTEX
+    Curl_mutex_init(&share->lock);
+#endif
+    share->ref_count = 1;
     Curl_dnscache_init(&share->dnscache, 23);
     share->admin = curl_easy_init();
     if(!share->admin) {
-      curlx_free(share);
+      share_destroy(share);
       return NULL;
     }
     /* admin handles have mid 0 */
@@ -50,10 +89,104 @@
       share->admin->set.verbose = TRUE;
 #endif
   }
-
   return share;
 }
 
+static uint32_t share_ref_inc(struct Curl_share *share)
+{
+  uint32_t n;
+#ifdef USE_MUTEX
+  Curl_mutex_acquire(&share->lock);
+  n = ++(share->ref_count);
+  share->has_been_shared = TRUE;
+  Curl_mutex_release(&share->lock);
+#else
+  n = ++(share->ref_count);
+  share->has_been_shared = TRUE;
+#endif
+  return n;
+}
+
+static uint32_t share_ref_dec(struct Curl_share *share)
+{
+  uint32_t n;
+#ifdef USE_MUTEX
+  Curl_mutex_acquire(&share->lock);
+  DEBUGASSERT(share->ref_count);
+  n = --(share->ref_count);
+  Curl_mutex_release(&share->lock);
+#else
+  n = --(share->ref_count);
+#endif
+  return n;
+}
+
+static bool share_has_been_shared(struct Curl_share *share)
+{
+  bool was_shared;
+#ifdef USE_MUTEX
+  Curl_mutex_acquire(&share->lock);
+  was_shared = share->has_been_shared;
+  Curl_mutex_release(&share->lock);
+#else
+  was_shared = share->has_been_shared;
+#endif
+  return was_shared;
+}
+
+static bool share_lock_acquire(struct Curl_share *share,
+                               struct Curl_easy *data)
+{
+  if(share->lockfunc && share->unlockfunc &&
+     (data || share_has_been_shared(share))) {
+    share->lockfunc(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE,
+                    share->clientdata);
+    return TRUE;
+  }
+  return FALSE;
+}
+
+static void share_lock_release(struct Curl_share *share,
+                               struct Curl_easy *data,
+                               bool locked)
+{
+  if(locked) {
+    DEBUGASSERT(share->unlockfunc);
+    if(share->unlockfunc)
+      share->unlockfunc(data, CURL_LOCK_DATA_SHARE, share->clientdata);
+  }
+}
+
+static bool share_in_use(struct Curl_share *share)
+{
+  bool in_use;
+#ifdef USE_MUTEX
+  Curl_mutex_acquire(&share->lock);
+  in_use = (share->ref_count > 1);
+  Curl_mutex_release(&share->lock);
+#else
+  bool locked = share_lock_acquire(share, NULL);
+  in_use = (share->ref_count > 1);
+  share_lock_release(share, NULL, locked);
+#endif
+  return in_use;
+}
+
+static void share_unlink(struct Curl_share **pshare,
+                         struct Curl_easy *data,
+                         bool locked)
+{
+  struct Curl_share *share = *pshare;
+  uint32_t n;
+
+  *pshare = NULL;
+  n = share_ref_dec(share);
+  if(locked)
+    share_lock_release(share, data, locked);
+  if(!n)  /* last reference gone */
+    share_destroy(share);
+}
+
 #undef curl_share_setopt
 CURLSHcode curl_share_setopt(CURLSH *sh, CURLSHoption option, ...)
 {
@@ -68,10 +201,11 @@
   if(!GOOD_SHARE_HANDLE(share))
     return CURLSHE_INVALID;
 
-  if(share->dirty)
+  if(share_in_use(share)) {
     /* do not allow setting options while one or more handles are already
        using this share */
     return CURLSHE_IN_USE;
+  }
 
   va_start(param, option);
 
@@ -114,8 +248,8 @@
         /* There is no way (yet) for the application to configure the
          * session cache size, shared between many transfers. As for curl
          * itself, a high session count will impact startup time. Also, the
-         * scache is not optimized for several hundreds of peers. So,
-         * keep it at a reasonable level. */
+         * scache is not optimized for several hundreds of peers.
+         * Keep it at a reasonable level. */
         if(Curl_ssl_scache_create(25, 2, &share->ssl_scache))
           res = CURLSHE_NOMEM;
       }
@@ -147,7 +281,6 @@
   case CURLSHOPT_UNSHARE:
     /* this is a type this share will no longer share */
     type = va_arg(param, int);
-    share->specifier &= ~(unsigned int)(1 << type);
     switch(type) {
     case CURL_LOCK_DATA_DNS:
       break;
@@ -191,6 +324,8 @@
       res = CURLSHE_BAD_OPTION;
       break;
     }
+    if(!res)
+      share->specifier &= ~(unsigned int)(1 << type);
     break;
 
   case CURLSHOPT_LOCKFUNC:
@@ -221,48 +356,15 @@
 CURLSHcode curl_share_cleanup(CURLSH *sh)
 {
   struct Curl_share *share = sh;
+  bool locked;
   if(!GOOD_SHARE_HANDLE(share))
     return CURLSHE_INVALID;
 
-  if(share->lockfunc)
-    share->lockfunc(NULL, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE,
-                    share->clientdata);
-
-  if(share->dirty) {
-    if(share->unlockfunc)
-      share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata);
+  if(share_in_use(share))
     return CURLSHE_IN_USE;
-  }
 
-  if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) {
-    Curl_cpool_destroy(&share->cpool);
-  }
-
-  Curl_dnscache_destroy(&share->dnscache);
-
-#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
-  Curl_cookie_cleanup(share->cookies);
-#endif
-
-#ifndef CURL_DISABLE_HSTS
-  Curl_hsts_cleanup(&share->hsts);
-#endif
-
-#ifdef USE_SSL
-  if(share->ssl_scache) {
-    Curl_ssl_scache_destroy(share->ssl_scache);
-    share->ssl_scache = NULL;
-  }
-#endif
-
-  Curl_psl_destroy(&share->psl);
-  Curl_close(&share->admin);
-
-  if(share->unlockfunc)
-    share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata);
-  share->magic = 0;
-  curlx_free(share);
-
+  locked = share_lock_acquire(share, NULL);
+  share_unlink(&share, NULL, locked);
   return CURLSHE_OK;
 }
 
@@ -297,3 +399,74 @@
 
   return CURLSHE_OK;
 }
+
+CURLcode Curl_share_easy_unlink(struct Curl_easy *data)
+{
+  struct Curl_share *share = data->share;
+
+  if(share) {
+    bool locked = share_lock_acquire(share, data);
+
+    /* If data has a connection from this share, detach it. */
+    if(data->conn && (share->specifier & (1 << CURL_LOCK_DATA_CONNECT)))
+      Curl_detach_connection(data);
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
+    if(share->cookies == data->cookies)
+      data->cookies = NULL;
+#endif
+
+#ifndef CURL_DISABLE_HSTS
+    if(share->hsts == data->hsts)
+      data->hsts = NULL;
+#endif
+#ifdef USE_LIBPSL
+    if(&share->psl == data->psl)
+      data->psl = data->multi ? &data->multi->psl : NULL;
+#endif
+
+    share_unlink(&data->share, data, locked);
+  }
+  return CURLE_OK;
+}
+
+CURLcode Curl_share_easy_link(struct Curl_easy *data,
+                              struct Curl_share *share)
+{
+  if(data->share) {
+    DEBUGASSERT(0);
+    return CURLE_FAILED_INIT;
+  }
+
+  if(share) {
+    bool locked = share_lock_acquire(share, data);
+
+    share_ref_inc(share);
+    data->share = share;
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
+    if(share->cookies) {
+      /* use shared cookie list, first free own one if any */
+      Curl_cookie_cleanup(data->cookies);
+      /* enable cookies since we now use a share that uses cookies! */
+      data->cookies = share->cookies;
+    }
+#endif /* CURL_DISABLE_HTTP */
+#ifndef CURL_DISABLE_HSTS
+    if(share->hsts) {
+      /* first free the private one if any */
+      Curl_hsts_cleanup(&data->hsts);
+      data->hsts = share->hsts;
+    }
+#endif
+#ifdef USE_LIBPSL
+    if(share->specifier & (1 << CURL_LOCK_DATA_PSL))
+      data->psl = &share->psl;
+#endif
+
+    /* check for host cache not needed,
+     * it will be done by curl_easy_perform */
+    share_lock_release(share, data, locked);
+  }
+  return CURLE_OK;
+}
diff --git a/Utilities/cmcurl/lib/curl_share.h b/Utilities/cmcurl/lib/curl_share.h
index 5e3f82a..69001be 100644
--- a/Utilities/cmcurl/lib/curl_share.h
+++ b/Utilities/cmcurl/lib/curl_share.h
@@ -25,6 +25,7 @@
  ***************************************************************************/
 #include "curl_setup.h"
 
+#include "curl_threads.h"
 #include "cookie.h"
 #include "psl.h"
 #include "urldata.h"
@@ -43,12 +44,22 @@
 struct Curl_share {
   unsigned int magic; /* CURL_GOOD_SHARE */
   unsigned int specifier;
-  volatile unsigned int dirty;
 
+  uint32_t ref_count;
+#ifdef USE_MUTEX
+   /* do `ref_count` and `has_been_shared` checks using this mutex. */
+  curl_mutex_t lock;
+  int has_been_shared;
+#else
+  /* this only ever goes from FALSE -> TRUE once. We need to check
+   * this without being able to use the `lockfunc`. */
+  volatile int has_been_shared;
+#endif
   curl_lock_function lockfunc;
   curl_unlock_function unlockfunc;
   void *clientdata;
   struct Curl_easy *admin;
+
   struct cpool cpool;
   struct Curl_dnscache dnscache; /* DNS cache */
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
@@ -74,4 +85,8 @@
                                     ((data)->share->specifier &          \
                                      (1 << CURL_LOCK_DATA_SSL_SESSION)))
 
+CURLcode Curl_share_easy_unlink(struct Curl_easy *data);
+CURLcode Curl_share_easy_link(struct Curl_easy *data,
+                              struct Curl_share *share);
+
 #endif /* HEADER_CURL_SHARE_H */
diff --git a/Utilities/cmcurl/lib/curl_sspi.c b/Utilities/cmcurl/lib/curl_sspi.c
index 1d4cf92..3ea1762 100644
--- a/Utilities/cmcurl/lib/curl_sspi.c
+++ b/Utilities/cmcurl/lib/curl_sspi.c
@@ -198,9 +198,9 @@
 void Curl_sspi_free_identity(SEC_WINNT_AUTH_IDENTITY *identity)
 {
   if(identity) {
-    Curl_safefree(identity->User);
-    Curl_safefree(identity->Password);
-    Curl_safefree(identity->Domain);
+    curlx_safefree(identity->User);
+    curlx_safefree(identity->Password);
+    curlx_safefree(identity->Domain);
   }
 }
 
diff --git a/Utilities/cmcurl/lib/curl_threads.c b/Utilities/cmcurl/lib/curl_threads.c
index f5fea98..01041f6 100644
--- a/Utilities/cmcurl/lib/curl_threads.c
+++ b/Utilities/cmcurl/lib/curl_threads.c
@@ -22,14 +22,12 @@
  *
  ***************************************************************************/
 #include "curl_setup.h"
-
-#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
-#include <pthread.h>
-#endif
-
 #include "curl_threads.h"
+#include "curlx/timeval.h"
 
-#ifdef USE_THREADS_POSIX
+#ifdef USE_THREADS
+
+#ifdef HAVE_THREADS_POSIX
 
 struct Curl_actual_call {
   unsigned int (*func)(void *);
@@ -49,8 +47,8 @@
   return 0;
 }
 
-curl_thread_t Curl_thread_create(CURL_THREAD_RETURN_T
-                                 (CURL_STDCALL *func) (void *), void *arg)
+curl_thread_t Curl_thread_create(
+  CURL_THREAD_RETURN_T(CURL_STDCALL *func)(void *), void *arg)
 {
   curl_thread_t t = curlx_malloc(sizeof(pthread_t));
   struct Curl_actual_call *ac = NULL;
@@ -97,10 +95,10 @@
   return ret;
 }
 
-#elif defined(USE_THREADS_WIN32)
+#elif defined(_WIN32)
 
-curl_thread_t Curl_thread_create(CURL_THREAD_RETURN_T
-                                 (CURL_STDCALL *func) (void *), void *arg)
+curl_thread_t Curl_thread_create(
+  CURL_THREAD_RETURN_T(CURL_STDCALL *func)(void *), void *arg)
 {
   curl_thread_t t = CreateThread(NULL, 0, func, arg, 0, NULL);
   if(!t) {
@@ -131,4 +129,86 @@
   return ret;
 }
 
-#endif /* USE_THREADS_* */
+#else
+#error neither HAVE_THREADS_POSIX nor _WIN32 defined
+#endif
+#endif /* USE_THREADS */
+
+#ifdef USE_MUTEX
+
+#ifdef HAVE_THREADS_POSIX
+
+void Curl_cond_signal(pthread_cond_t *c)
+{
+  /* return code defined as always 0 */
+  (void)pthread_cond_signal(c);
+}
+
+void Curl_cond_wait(pthread_cond_t *c, pthread_mutex_t *m)
+{
+  /* return code defined as always 0 */
+  (void)pthread_cond_wait(c, m);
+}
+
+CURLcode Curl_cond_timedwait(pthread_cond_t *c, pthread_mutex_t *m,
+                             uint32_t timeout_ms)
+{
+  struct curltime now;
+  struct timespec ts;
+  timediff_t usec;
+  int rc;
+
+  /* POSIX expects an "absolute" time until the condition wait ends.
+   * We cannot use `curlx_now()` here that may run on some monotonic clock
+   * that will be most likely in the past, as far as POSIX abstime is
+   * concerned. */
+#ifdef HAVE_GETTIMEOFDAY
+  struct timeval tv;
+  (void)gettimeofday(&tv, NULL);
+  now.tv_sec = tv.tv_sec;
+  now.tv_usec = (int)tv.tv_usec;
+#else
+  now.tv_sec = time(NULL);
+  now.tv_usec = 0;
+#endif
+
+  ts.tv_sec = now.tv_sec + (timeout_ms / 1000);
+  usec = now.tv_usec + ((timeout_ms % 1000) * 1000);
+  if(usec >= 1000000) {
+    ++ts.tv_sec;
+    usec %= 1000000;
+  }
+  ts.tv_nsec = (long)usec * 1000;
+
+  rc = pthread_cond_timedwait(c, m, &ts);
+  if(rc == SOCKETIMEDOUT)
+    return CURLE_OPERATION_TIMEDOUT;
+  return rc ? CURLE_UNRECOVERABLE_POLL : CURLE_OK;
+}
+
+#elif defined(_WIN32)
+
+void Curl_cond_signal(CONDITION_VARIABLE *c)
+{
+  WakeConditionVariable(c);
+}
+
+void Curl_cond_wait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m)
+{
+  SleepConditionVariableCS(c, m, INFINITE);
+}
+
+CURLcode Curl_cond_timedwait(CONDITION_VARIABLE *c, CRITICAL_SECTION *m,
+                             uint32_t timeout_ms)
+{
+  if(!SleepConditionVariableCS(c, m, (DWORD)timeout_ms)) {
+    DWORD err = GetLastError();
+    return (err == ERROR_TIMEOUT) ?
+           CURLE_OPERATION_TIMEDOUT : CURLE_UNRECOVERABLE_POLL;
+  }
+  return CURLE_OK;
+}
+#else
+#error neither HAVE_THREADS_POSIX nor _WIN32 defined
+#endif
+#endif /* USE_MUTEX */
diff --git a/Utilities/cmcurl/lib/curl_threads.h b/Utilities/cmcurl/lib/curl_threads.h
index 0e067c0..081d155 100644
--- a/Utilities/cmcurl/lib/curl_threads.h
+++ b/Utilities/cmcurl/lib/curl_threads.h
@@ -25,7 +25,8 @@
  ***************************************************************************/
 #include "curl_setup.h"
 
-#ifdef USE_THREADS_POSIX
+#ifdef USE_MUTEX
+#ifdef HAVE_THREADS_POSIX
 #  define CURL_THREAD_RETURN_T   unsigned int
 #  define CURL_STDCALL
 #  define curl_mutex_t           pthread_mutex_t
@@ -35,7 +36,10 @@
 #  define Curl_mutex_acquire(m)  pthread_mutex_lock(m)
 #  define Curl_mutex_release(m)  pthread_mutex_unlock(m)
 #  define Curl_mutex_destroy(m)  pthread_mutex_destroy(m)
-#elif defined(USE_THREADS_WIN32)
+#  define curl_cond_t            pthread_cond_t
+#  define Curl_cond_init(c)      pthread_cond_init(c, NULL)
+#  define Curl_cond_destroy(c)   pthread_cond_destroy(c)
+#elif defined(_WIN32)
 #  define CURL_THREAD_RETURN_T   DWORD
 #  define CURL_STDCALL           WINAPI
 #  define curl_mutex_t           CRITICAL_SECTION
@@ -45,17 +49,29 @@
 #  define Curl_mutex_acquire(m)  EnterCriticalSection(m)
 #  define Curl_mutex_release(m)  LeaveCriticalSection(m)
 #  define Curl_mutex_destroy(m)  DeleteCriticalSection(m)
+#  define curl_cond_t            CONDITION_VARIABLE
+#  define Curl_cond_init(c)      InitializeConditionVariable(c)
+#  define Curl_cond_destroy(c)   (void)(c)
+#else
+#error neither HAVE_THREADS_POSIX nor _WIN32 defined
 #endif
 
-#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
+void Curl_cond_signal(curl_cond_t *c);
+void Curl_cond_wait(curl_cond_t *c, curl_mutex_t *m);
+/* Returns CURLE_OPERATION_TIMEDOUT on timeout */
+CURLcode Curl_cond_timedwait(curl_cond_t *c, curl_mutex_t *m,
+                             uint32_t timeout_ms);
+#endif /* USE_MUTEX */
 
-curl_thread_t Curl_thread_create(CURL_THREAD_RETURN_T
-                                 (CURL_STDCALL *func) (void *), void *arg);
+#ifdef USE_THREADS
+
+curl_thread_t Curl_thread_create(
+  CURL_THREAD_RETURN_T(CURL_STDCALL *func)(void *), void *arg);
 
 void Curl_thread_destroy(curl_thread_t *hnd);
 
 int Curl_thread_join(curl_thread_t *hnd);
 
-#endif /* USE_THREADS_POSIX || USE_THREADS_WIN32 */
+#endif /* USE_THREADS */
 
 #endif /* HEADER_CURL_THREADS_H */
diff --git a/Utilities/cmcurl/lib/curl_trc.c b/Utilities/cmcurl/lib/curl_trc.c
index 4b8734a..c6115cf 100644
--- a/Utilities/cmcurl/lib/curl_trc.c
+++ b/Utilities/cmcurl/lib/curl_trc.c
@@ -28,6 +28,7 @@
 #include "cfilters.h"
 #include "multiif.h"
 
+#include "cf-dns.h"
 #include "cf-socket.h"
 #include "connect.h"
 #include "http2.h"
@@ -222,6 +223,12 @@
   "TIMER",
   CURL_LOG_LVL_NONE,
 };
+#ifdef USE_THREADS
+struct curl_trc_feat Curl_trc_feat_threads = {
+  "THREADS",
+  CURL_LOG_LVL_NONE,
+};
+#endif
 #endif
 
 #ifndef CURL_DISABLE_VERBOSE_STRINGS
@@ -278,6 +285,19 @@
   }
 }
 
+void Curl_trc_feat_infof(struct Curl_easy *data,
+                         struct curl_trc_feat *feat,
+                         const char *fmt, ...)
+{
+  DEBUGASSERT(feat);
+  if(Curl_trc_ft_is_verbose(data, feat)) {
+    va_list ap;
+    va_start(ap, fmt);
+    trc_infof(data, feat, NULL, 0, fmt, ap);
+    va_end(ap);
+  }
+}
+
 static const char * const Curl_trc_timer_names[] = {
   "100_TIMEOUT",
   "ASYNC_NAME",
@@ -336,7 +356,6 @@
   "PENDING",
   "SETUP",
   "CONNECT",
-  "RESOLVING",
   "CONNECTING",
   "PROTOCONNECT",
   "PROTOCONNECTING",
@@ -512,6 +531,9 @@
   { &Curl_trc_feat_write,     TRC_CT_NONE },
   { &Curl_trc_feat_dns,       TRC_CT_NETWORK },
   { &Curl_trc_feat_timer,     TRC_CT_NETWORK },
+#ifdef USE_THREADS
+  { &Curl_trc_feat_threads,   TRC_CT_NONE },
+#endif
 #ifndef CURL_DISABLE_FTP
   { &Curl_trc_feat_ftp,       TRC_CT_PROTOCOL },
 #endif
@@ -535,6 +557,7 @@
 };
 
 static struct trc_cft_def trc_cfts[] = {
+  { &Curl_cft_dns,            TRC_CT_NETWORK },
   { &Curl_cft_tcp,            TRC_CT_NETWORK },
   { &Curl_cft_udp,            TRC_CT_NETWORK },
   { &Curl_cft_unix,           TRC_CT_NETWORK },
diff --git a/Utilities/cmcurl/lib/curl_trc.h b/Utilities/cmcurl/lib/curl_trc.h
index a82c945..b4ae8e5 100644
--- a/Utilities/cmcurl/lib/curl_trc.h
+++ b/Utilities/cmcurl/lib/curl_trc.h
@@ -101,6 +101,10 @@
   int log_level;
 };
 
+void Curl_trc_feat_infof(struct Curl_easy *data,
+                         struct curl_trc_feat *feat,
+                         const char *fmt, ...) CURL_PRINTF(3, 4);
+
 #ifndef CURL_DISABLE_FTP
 extern struct curl_trc_feat Curl_trc_feat_ftp;
 void Curl_trc_ftp(struct Curl_easy *data,
@@ -304,6 +308,9 @@
 extern struct curl_trc_feat Curl_trc_feat_write;
 extern struct curl_trc_feat Curl_trc_feat_dns;
 extern struct curl_trc_feat Curl_trc_feat_timer;
+#ifdef USE_THREADS
+extern struct curl_trc_feat Curl_trc_feat_threads;
+#endif
 #endif
 
 #ifndef CURL_DISABLE_VERBOSE_STRINGS
diff --git a/Utilities/cmcurl/lib/curlx/base64.c b/Utilities/cmcurl/lib/curlx/base64.c
index a7a6703..7f51576 100644
--- a/Utilities/cmcurl/lib/curlx/base64.c
+++ b/Utilities/cmcurl/lib/curlx/base64.c
@@ -149,7 +149,7 @@
     pos += 3 - padding;
   }
 
-  /* Zero terminate */
+  /* Null-terminate */
   *pos = '\0';
 
   /* Return the decoded data */
@@ -213,7 +213,7 @@
     }
   }
 
-  /* Zero terminate */
+  /* Null-terminate */
   *output = '\0';
 
   /* Return the pointer to the new data (allocated memory) */
diff --git a/Utilities/cmcurl/lib/curlx/curlx.h b/Utilities/cmcurl/lib/curlx/curlx.h
deleted file mode 100644
index 79cffc8..0000000
--- a/Utilities/cmcurl/lib/curlx/curlx.h
+++ /dev/null
@@ -1,52 +0,0 @@
-#ifndef HEADER_CURL_CURLX_H
-#define HEADER_CURL_CURLX_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
- *
- ***************************************************************************/
-
-/*
- * Defines protos and includes all header files that provide the curlx_*
- * functions. The curlx_* functions are not part of the libcurl API, but are
- * stand-alone functions whose sources can be built and linked by apps if need
- * be.
- */
-
-#include "curlx/base64.h" /* for curlx_base64* */
-#include "curlx/basename.h" /* for curlx_basename() */
-#include "curlx/dynbuf.h" /* for curlx_dyn_*() */
-#include "curlx/fopen.h" /* for curlx_f*() */
-#include "curlx/inet_ntop.h" /* for curlx_inet_ntop() */
-#include "curlx/inet_pton.h" /* for curlx_inet_pton() */
-#include "curlx/multibyte.h" /* for curlx_convert_*() */
-#include "curlx/nonblock.h" /* for curlx_nonblock() */
-#include "curlx/strcopy.h" /* for curlx_strcopy() */
-#include "curlx/strdup.h" /* for curlx_memdup*() and curlx_tcsdup() */
-#include "curlx/strerr.h" /* for curlx_strerror() */
-#include "curlx/strparse.h" /* for curlx_str_* parsing functions */
-#include "curlx/timediff.h" /* for timediff_t type and related functions */
-#include "curlx/timeval.h" /* for curlx_now type and related functions */
-#include "curlx/version_win32.h" /* for curlx_verify_windows_version() */
-#include "curlx/wait.h" /* for curlx_wait_ms() */
-#include "curlx/winapi.h" /* for curlx_winapi_strerror() */
-
-#endif /* HEADER_CURL_CURLX_H */
diff --git a/Utilities/cmcurl/lib/curlx/dynbuf.c b/Utilities/cmcurl/lib/curlx/dynbuf.c
index 7045a1f..1e5ea51 100644
--- a/Utilities/cmcurl/lib/curlx/dynbuf.c
+++ b/Utilities/cmcurl/lib/curlx/dynbuf.c
@@ -57,7 +57,7 @@
 {
   DEBUGASSERT(s);
   DEBUGASSERT(s->init == DYNINIT);
-  Curl_safefree(s->bufr);
+  curlx_safefree(s->bufr);
   s->leng = s->allc = 0;
 }
 
@@ -134,7 +134,7 @@
 
 /*
  * Specify the size of the tail to keep (number of bytes from the end of the
- * buffer). The rest will be dropped.
+ * buffer). The rest is dropped.
  */
 CURLcode curlx_dyn_tail(struct dynbuf *s, size_t trail)
 {
@@ -205,7 +205,7 @@
 
   if(str) {
     CURLcode result = dyn_nappend(s, (const unsigned char *)str, strlen(str));
-    curlx_free(str);
+    curl_free(str);
     return result;
   }
   /* If we failed, we cleanup the whole buffer and return error */
diff --git a/Utilities/cmcurl/lib/curlx/dynbuf.h b/Utilities/cmcurl/lib/curlx/dynbuf.h
index 6f09e13..0cf4a2c 100644
--- a/Utilities/cmcurl/lib/curlx/dynbuf.h
+++ b/Utilities/cmcurl/lib/curlx/dynbuf.h
@@ -53,7 +53,7 @@
 
 /* returns 0 on success, -1 on error */
 /* The implementation of this function exists in mprintf.c */
-int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save);
+int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list args);
 
 /* Take the buffer out of the dynbuf. Caller has ownership and
  * dynbuf resets to initial state. */
diff --git a/Utilities/cmcurl/lib/curlx/fopen.c b/Utilities/cmcurl/lib/curlx/fopen.c
index 6d0ef7e..6733010 100644
--- a/Utilities/cmcurl/lib/curlx/fopen.c
+++ b/Utilities/cmcurl/lib/curlx/fopen.c
@@ -94,7 +94,7 @@
  * longer than MAX_PATH then setting 'out' to "\\?\" prefix + that full path.
  *
  * For example 'in' filename255chars in current directory C:\foo\bar is
- * fixed as \\?\C:\foo\bar\filename255chars for 'out' which will tell Windows
+ * fixed as \\?\C:\foo\bar\filename255chars for 'out' which tells Windows
  * it is ok to access that filename even though the actual full path is longer
  * than 260 chars.
  *
@@ -296,7 +296,7 @@
 int curlx_win32_open(const char *filename, int oflag, ...)
 {
   int pmode = 0;
-  int result = -1;
+  int res = -1;
   TCHAR *fixed = NULL;
   const TCHAR *target = NULL;
 
@@ -316,7 +316,7 @@
       target = fixed;
     else
       target = filename_w;
-    errno = _wsopen_s(&result, target, oflag, _SH_DENYNO, pmode);
+    errno = _wsopen_s(&res, target, oflag, _SH_DENYNO, pmode);
     CURLX_FREE(filename_w);
   }
   else
@@ -327,16 +327,16 @@
     target = fixed;
   else
     target = filename;
-  errno = _sopen_s(&result, target, oflag, _SH_DENYNO, pmode);
+  errno = _sopen_s(&res, target, oflag, _SH_DENYNO, pmode);
 #endif
 
   CURLX_FREE(fixed);
-  return result;
+  return res;
 }
 
 FILE *curlx_win32_fopen(const char *filename, const char *mode)
 {
-  FILE *result = NULL;
+  FILE *file = NULL;
   TCHAR *fixed = NULL;
   const TCHAR *target = NULL;
 
@@ -348,7 +348,7 @@
       target = fixed;
     else
       target = filename_w;
-    result = _wfsopen(target, mode_w, _SH_DENYNO);
+    file = _wfsopen(target, mode_w, _SH_DENYNO);
   }
   else
     /* !checksrc! disable ERRNOVAR 1 */
@@ -360,11 +360,11 @@
     target = fixed;
   else
     target = filename;
-  result = _fsopen(target, mode, _SH_DENYNO);
+  file = _fsopen(target, mode, _SH_DENYNO);
 #endif
 
   CURLX_FREE(fixed);
-  return result;
+  return file;
 }
 
 #if defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR < 5)
@@ -374,7 +374,7 @@
 
 FILE *curlx_win32_freopen(const char *filename, const char *mode, FILE *fp)
 {
-  FILE *result = NULL;
+  FILE *file = NULL;
   TCHAR *fixed = NULL;
   const TCHAR *target = NULL;
 
@@ -386,7 +386,7 @@
       target = fixed;
     else
       target = filename_w;
-    errno = _wfreopen_s(&result, target, mode_w, fp);
+    errno = _wfreopen_s(&file, target, mode_w, fp);
   }
   else
     /* !checksrc! disable ERRNOVAR 1 */
@@ -398,16 +398,16 @@
     target = fixed;
   else
     target = filename;
-  errno = freopen_s(&result, target, mode, fp);
+  errno = freopen_s(&file, target, mode, fp);
 #endif
 
   CURLX_FREE(fixed);
-  return result;
+  return file;
 }
 
 int curlx_win32_stat(const char *path, curlx_struct_stat *buffer)
 {
-  int result = -1;
+  int res = -1;
   TCHAR *fixed = NULL;
   const TCHAR *target = NULL;
 
@@ -418,7 +418,7 @@
       target = fixed;
     else
       target = path_w;
-    result = _wstati64(target, buffer);
+    res = _wstati64(target, buffer);
     curlx_free(path_w);
   }
   else
@@ -429,17 +429,17 @@
     target = fixed;
   else
     target = path;
-  result = _stati64(target, buffer);
+  res = _stati64(target, buffer);
 #endif
 
   CURLX_FREE(fixed);
-  return result;
+  return res;
 }
 
 #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_COOKIES) || \
   !defined(CURL_DISABLE_ALTSVC)
 /* rename() on Windows does not overwrite, so we cannot use it here.
-   MoveFileEx() will overwrite and is usually atomic but fails when there are
+   MoveFileEx() does overwrite and is usually atomic but fails when there are
    open handles to the file. */
 int curlx_win32_rename(const char *oldpath, const char *newpath)
 {
diff --git a/Utilities/cmcurl/lib/curlx/fopen.h b/Utilities/cmcurl/lib/curlx/fopen.h
index d89830e..b64fbf6 100644
--- a/Utilities/cmcurl/lib/curlx/fopen.h
+++ b/Utilities/cmcurl/lib/curlx/fopen.h
@@ -47,7 +47,7 @@
 #define curlx_fstat             _fstati64
 #define curlx_struct_stat       struct _stati64
 FILE *curlx_win32_fopen(const char *filename, const char *mode);
-FILE *curlx_win32_freopen(const char *filename, const char *mode, FILE *fh);
+FILE *curlx_win32_freopen(const char *filename, const char *mode, FILE *fp);
 int curlx_win32_stat(const char *path, curlx_struct_stat *buffer);
 int curlx_win32_open(const char *filename, int oflag, ...);
 int curlx_win32_rename(const char *oldpath, const char *newpath);
diff --git a/Utilities/cmcurl/lib/curlx/inet_ntop.h b/Utilities/cmcurl/lib/curlx/inet_ntop.h
index 2e3419f..ba8299f 100644
--- a/Utilities/cmcurl/lib/curlx/inet_ntop.h
+++ b/Utilities/cmcurl/lib/curlx/inet_ntop.h
@@ -36,15 +36,15 @@
 #include <arpa/inet.h>
 #endif
 #ifdef __AMIGA__
-#define curlx_inet_ntop(af, addr, buf, size)                            \
-  (char *)inet_ntop(af, CURL_UNCONST(addr), (unsigned char *)(buf),     \
+#define curlx_inet_ntop(af, src, buf, size)                            \
+  (char *)inet_ntop(af, CURL_UNCONST(src), (unsigned char *)(buf),     \
                     (curl_socklen_t)(size))
 #else
-#define curlx_inet_ntop(af, addr, buf, size)                            \
-  inet_ntop(af, addr, buf, (curl_socklen_t)(size))
+#define curlx_inet_ntop(af, src, buf, size)                            \
+  inet_ntop(af, src, buf, (curl_socklen_t)(size))
 #endif
 #else
-char *curlx_inet_ntop(int af, const void *addr, char *buf, size_t size);
+char *curlx_inet_ntop(int af, const void *src, char *buf, size_t size);
 #endif /* HAVE_INET_NTOP */
 
 #endif /* HEADER_CURL_INET_NTOP_H */
diff --git a/Utilities/cmcurl/lib/curlx/inet_pton.c b/Utilities/cmcurl/lib/curlx/inet_pton.c
index d3c53fb..7994f25 100644
--- a/Utilities/cmcurl/lib/curlx/inet_pton.c
+++ b/Utilities/cmcurl/lib/curlx/inet_pton.c
@@ -170,7 +170,7 @@
   if(colonp) {
     /*
      * Since some memmove()'s erroneously fail to handle
-     * overlapping regions, we will do the shift by hand.
+     * overlapping regions, we do the shift by hand.
      */
     const ssize_t n = tp - colonp;
     ssize_t i;
diff --git a/Utilities/cmcurl/lib/curlx/snprintf.c b/Utilities/cmcurl/lib/curlx/snprintf.c
index c733252..911c42e 100644
--- a/Utilities/cmcurl/lib/curlx/snprintf.c
+++ b/Utilities/cmcurl/lib/curlx/snprintf.c
@@ -34,13 +34,13 @@
   if(!maxlen)
     return;
   va_start(ap, fmt);
-#if defined(__GNUC__) || defined(__clang__)
+#ifdef CURL_HAVE_DIAG
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
 #endif
   /* !checksrc! disable BANNEDFUNC 1 */
   (void)vsnprintf(buf, maxlen, fmt, ap);
-#if defined(__GNUC__) || defined(__clang__)
+#ifdef CURL_HAVE_DIAG
 #pragma GCC diagnostic pop
 #endif
   buf[maxlen - 1] = 0;
diff --git a/Utilities/cmcurl/lib/curlx/strcopy.c b/Utilities/cmcurl/lib/curlx/strcopy.c
index da137c3..3e58ea2 100644
--- a/Utilities/cmcurl/lib/curlx/strcopy.c
+++ b/Utilities/cmcurl/lib/curlx/strcopy.c
@@ -30,10 +30,10 @@
  *
  * Provide the target buffer @dest and size of the target buffer @dsize, If
  * the source string @src with its *string length* @slen fits in the target
- * buffer it will be copied there - including storing a null terminator.
+ * buffer it is copied there - including storing a null terminator.
  *
  * If the target buffer is too small, the copy is not performed but if the
- * target buffer has a non-zero size it will get a null terminator stored.
+ * target buffer has a non-zero size it gets a null terminator stored.
  */
 void curlx_strcopy(char *dest,      /* destination buffer */
                    size_t dsize,    /* size of target buffer */
diff --git a/Utilities/cmcurl/lib/curlx/strerr.c b/Utilities/cmcurl/lib/curlx/strerr.c
index 91a329e..b53173c 100644
--- a/Utilities/cmcurl/lib/curlx/strerr.c
+++ b/Utilities/cmcurl/lib/curlx/strerr.c
@@ -263,7 +263,7 @@
   *buf = '\0';
 
 #ifdef _WIN32
-  if((!strerror_s(buf, buflen, err) || !strcmp(buf, "Unknown error")) &&
+  if((strerror_s(buf, buflen, err) || !strcmp(buf, "Unknown error")) &&
 #ifdef USE_WINSOCK
      !get_winsock_error(err, buf, buflen) &&
 #endif
diff --git a/Utilities/cmcurl/lib/curlx/strparse.c b/Utilities/cmcurl/lib/curlx/strparse.c
index 138b3df..7866b20 100644
--- a/Utilities/cmcurl/lib/curlx/strparse.c
+++ b/Utilities/cmcurl/lib/curlx/strparse.c
@@ -35,6 +35,15 @@
   out->len = len;
 }
 
+/* remove bytes from the end of the string, never remove more bytes than what
+   the string holds! */
+void curlx_str_trim(struct Curl_str *out, size_t len)
+{
+  DEBUGASSERT(out);
+  DEBUGASSERT(out->len >= len);
+  out->len -= len;
+}
+
 /* Get a word until the first DELIM or end of string. At least one byte long.
    return non-zero on error */
 int curlx_str_until(const char **linep, struct Curl_str *out,
diff --git a/Utilities/cmcurl/lib/curlx/strparse.h b/Utilities/cmcurl/lib/curlx/strparse.h
index 514203d..c7801b2 100644
--- a/Utilities/cmcurl/lib/curlx/strparse.h
+++ b/Utilities/cmcurl/lib/curlx/strparse.h
@@ -44,6 +44,7 @@
 
 void curlx_str_init(struct Curl_str *out);
 void curlx_str_assign(struct Curl_str *out, const char *str, size_t len);
+void curlx_str_trim(struct Curl_str *out, size_t len);
 
 #define curlx_str(x)    ((x)->str)
 #define curlx_strlen(x) ((x)->len)
diff --git a/Utilities/cmcurl/lib/curlx/timeval.c b/Utilities/cmcurl/lib/curlx/timeval.c
index 435804c..2363e60 100644
--- a/Utilities/cmcurl/lib/curlx/timeval.c
+++ b/Utilities/cmcurl/lib/curlx/timeval.c
@@ -166,6 +166,8 @@
    */
   pnow->tv_sec = time(NULL);
   pnow->tv_usec = 0;
+  if(!pnow->tv_sec) /* avoid a `now` fully zero */
+    pnow->tv_usec = 1;
 }
 
 #endif
diff --git a/Utilities/cmcurl/lib/curlx/wait.c b/Utilities/cmcurl/lib/curlx/wait.c
index 5278426..e50a0f0 100644
--- a/Utilities/cmcurl/lib/curlx/wait.c
+++ b/Utilities/cmcurl/lib/curlx/wait.c
@@ -46,7 +46,7 @@
  * wait on, being used to delay execution. Winsock select() and poll() timeout
  * mechanisms need a valid socket descriptor in a not null file descriptor set
  * to work. Waiting indefinitely with this function is not allowed, a zero or
- * negative timeout value will return immediately. Timeout resolution,
+ * negative timeout value is returned immediately. Timeout resolution,
  * accuracy, as well as maximum supported value is system dependent, neither
  * factor is a critical issue for the intended use of this function in the
  * library.
diff --git a/Utilities/cmcurl/lib/curlx/winapi.c b/Utilities/cmcurl/lib/curlx/winapi.c
index 3417df2..f025ca4 100644
--- a/Utilities/cmcurl/lib/curlx/winapi.c
+++ b/Utilities/cmcurl/lib/curlx/winapi.c
@@ -44,7 +44,7 @@
     return NULL;
 
   /* We return the local codepage version of the error string because if it is
-     output to the user's terminal it will likely be with functions which
+     output to the user's terminal, it is likely done with functions which
      expect the local codepage (eg fprintf, failf, infof). */
   if(!FormatMessageA((FORMAT_MESSAGE_FROM_SYSTEM |
                       FORMAT_MESSAGE_IGNORE_INSERTS), NULL, err,
diff --git a/Utilities/cmcurl/lib/cw-out.c b/Utilities/cmcurl/lib/cw-out.c
index 554c32e..2af074e 100644
--- a/Utilities/cmcurl/lib/cw-out.c
+++ b/Utilities/cmcurl/lib/cw-out.c
@@ -31,7 +31,7 @@
 #include "transfer.h"
 #include "cw-out.h"
 #include "cw-pause.h"
-
+#include "progress.h"
 
 /**
  * OVERALL DESIGN of this client writer
@@ -211,7 +211,7 @@
   }
   else if(nwritten != blen) {
     failf(data, "Failure writing output to destination, "
-          "passed %zu returned %zd", blen, nwritten);
+          "passed %zu returned %zu", blen, nwritten);
     return CURLE_WRITE_ERROR;
   }
   *pnwritten = nwritten;
@@ -228,8 +228,8 @@
   curl_write_callback wcb = NULL;
   void *wcb_data;
   size_t max_write, min_write;
-  size_t wlen, nwritten;
-  CURLcode result;
+  size_t wlen, nwritten = 0;
+  CURLcode result = CURLE_OK;
 
   /* If we errored once, we do not invoke the client callback again */
   if(ctx->errored)
@@ -253,10 +253,15 @@
       if(!flush_all && blen < min_write)
         break;
       wlen = max_write ? CURLMIN(blen, max_write) : blen;
-      result = cw_out_cb_write(ctx, data, wcb, wcb_data, otype,
-                               buf, wlen, &nwritten);
+      if(otype == CW_OUT_BODY)
+        result = Curl_pgrs_deliver_check(data, wlen);
+      if(!result)
+        result = cw_out_cb_write(ctx, data, wcb, wcb_data, otype,
+                                 buf, wlen, &nwritten);
       if(result)
         return result;
+      if(otype == CW_OUT_BODY)
+        Curl_pgrs_deliver_inc(data, nwritten);
       *pconsumed += nwritten;
       blen -= nwritten;
       buf += nwritten;
@@ -350,8 +355,8 @@
   }
 
   /* if we do not have a buffer, or it is of another type, make a new one.
-   * And for CW_OUT_HDS always make a new one, so we "replay" headers
-   * exactly as they came in */
+   * For CW_OUT_HDS always make a new one, so we "replay" headers exactly
+   * as they came in */
   if(!ctx->buf || (ctx->buf->type != otype) || (otype == CW_OUT_HDS)) {
     struct cw_out_buf *cwbuf = cw_out_buf_create(otype);
     if(!cwbuf)
diff --git a/Utilities/cmcurl/lib/dict.c b/Utilities/cmcurl/lib/dict.c
index fcdea91..7b83c6c 100644
--- a/Utilities/cmcurl/lib/dict.c
+++ b/Utilities/cmcurl/lib/dict.c
@@ -64,7 +64,6 @@
 #define DICT_DEFINE2 "/D:"
 #define DICT_DEFINE3 "/LOOKUP:"
 
-
 #define DYN_DICT_WORD 10000
 static char *unescape_word(const char *input)
 {
@@ -275,7 +274,7 @@
 /*
  * DICT protocol
  */
-static const struct Curl_protocol Curl_protocol_dict = {
+const struct Curl_protocol Curl_protocol_dict = {
   ZERO_NULL,                            /* setup_connection */
   dict_do,                              /* do_it */
   ZERO_NULL,                            /* done */
@@ -290,25 +289,9 @@
   ZERO_NULL,                            /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
 
 #endif /* CURL_DISABLE_DICT */
-
-/*
- * DICT protocol handler.
- */
-const struct Curl_scheme Curl_scheme_dict = {
-  "dict",                               /* scheme */
-#ifdef CURL_DISABLE_DICT
-  ZERO_NULL,
-#else
-  &Curl_protocol_dict,
-#endif
-  CURLPROTO_DICT,                       /* protocol */
-  CURLPROTO_DICT,                       /* family */
-  PROTOPT_NONE | PROTOPT_NOURLQUERY,    /* flags */
-  PORT_DICT,                            /* defport */
-};
diff --git a/Utilities/cmcurl/lib/dict.h b/Utilities/cmcurl/lib/dict.h
index b5d6947..5bdfcff 100644
--- a/Utilities/cmcurl/lib/dict.h
+++ b/Utilities/cmcurl/lib/dict.h
@@ -23,6 +23,8 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_dict;
+#ifndef CURL_DISABLE_DICT
+extern const struct Curl_protocol Curl_protocol_dict;
+#endif
 
 #endif /* HEADER_CURL_DICT_H */
diff --git a/Utilities/cmcurl/lib/dnscache.c b/Utilities/cmcurl/lib/dnscache.c
new file mode 100644
index 0000000..20f6b21
--- /dev/null
+++ b/Utilities/cmcurl/lib/dnscache.c
@@ -0,0 +1,873 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_IN6_H
+#include <netinet/in6.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef __VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#include "urldata.h"
+#include "curl_addrinfo.h"
+#include "curl_share.h"
+#include "curl_trc.h"
+#include "dnscache.h"
+#include "hash.h"
+#include "hostip.h"
+#include "httpsrr.h"
+#include "progress.h"
+#include "rand.h"
+#include "strcase.h"
+#include "curlx/inet_ntop.h"
+#include "curlx/inet_pton.h"
+#include "curlx/strcopy.h"
+#include "curlx/strparse.h"
+
+#define MAX_HOSTCACHE_LEN (255 + 7) /* max FQDN + colon + port number + zero */
+
+#define MAX_DNS_CACHE_SIZE 29999
+
+static void dnscache_entry_free(struct Curl_dns_entry *dns)
+{
+  Curl_freeaddrinfo(dns->addr);
+#ifdef USE_HTTPSRR
+  if(dns->hinfo) {
+    Curl_httpsrr_cleanup(dns->hinfo);
+    curlx_free(dns->hinfo);
+  }
+#endif
+  curlx_free(dns);
+}
+
+/*
+ * Create a hostcache id string for the provided host + port, to be used by
+ * the DNS caching. Without alloc. Return length of the id string.
+ */
+static size_t create_dnscache_id(const char *name,
+                                 size_t nlen, /* 0 or actual name length */
+                                 uint16_t port, char *ptr, size_t buflen)
+{
+  size_t len = nlen ? nlen : strlen(name);
+  DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN);
+  if(len > (buflen - 7))
+    len = buflen - 7;
+  /* store and lower case the name */
+  Curl_strntolower(ptr, name, len);
+  return curl_msnprintf(&ptr[len], 7, ":%u", port) + len;
+}
+
+struct dnscache_prune_data {
+  struct curltime now;
+  timediff_t oldest_ms; /* oldest time in cache not pruned. */
+  timediff_t max_age_ms;
+};
+
+/*
+ * This function is set as a callback to be called for every entry in the DNS
+ * cache when we want to prune old unused entries.
+ *
+ * Returning non-zero means remove the entry, return 0 to keep it in the
+ * cache.
+ */
+static int dnscache_entry_is_stale(void *datap, void *hc)
+{
+  struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap;
+  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc;
+
+  if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) {
+    /* get age in milliseconds */
+    timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp);
+    if(!dns->addr)
+      age *= 2; /* negative entries age twice as fast */
+    if(age >= prune->max_age_ms)
+      return TRUE;
+    if(age > prune->oldest_ms)
+      prune->oldest_ms = 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 - in milliseconds.
+ */
+static timediff_t dnscache_prune(struct Curl_hash *hostcache,
+                                 timediff_t cache_timeout_ms,
+                                 struct curltime now)
+{
+  struct dnscache_prune_data user;
+
+  user.max_age_ms = cache_timeout_ms;
+  user.now = now;
+  user.oldest_ms = 0;
+
+  Curl_hash_clean_with_criterium(hostcache,
+                                 (void *)&user,
+                                 dnscache_entry_is_stale);
+
+  return user.oldest_ms;
+}
+
+static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
+{
+  if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS))
+    return &data->share->dnscache;
+  if(data->multi)
+    return &data->multi->dnscache;
+  return NULL;
+}
+
+static void dnscache_lock(struct Curl_easy *data,
+                          struct Curl_dnscache *dnscache)
+{
+  if(data->share && dnscache == &data->share->dnscache)
+    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
+}
+
+static void dnscache_unlock(struct Curl_easy *data,
+                            struct Curl_dnscache *dnscache)
+{
+  if(data->share && dnscache == &data->share->dnscache)
+    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
+}
+
+/*
+ * Library-wide function for pruning the DNS cache. This function takes and
+ * returns the appropriate locks.
+ */
+void Curl_dnscache_prune(struct Curl_easy *data)
+{
+  struct Curl_dnscache *dnscache = dnscache_get(data);
+  /* the timeout may be set -1 (forever) */
+  timediff_t timeout_ms = data->set.dns_cache_timeout_ms;
+
+  if(!dnscache || (timeout_ms == -1))
+    /* NULL hostcache means we cannot do it */
+    return;
+
+  dnscache_lock(data, dnscache);
+
+  do {
+    /* Remove outdated and unused entries from the hostcache */
+    timediff_t oldest_ms =
+      dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data));
+
+    if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)
+      /* prune the ones over half this age */
+      timeout_ms = oldest_ms / 2;
+    else
+      break;
+
+    /* if the cache size is still too big, use the oldest age as new prune
+       limit */
+  } while(timeout_ms);
+
+  dnscache_unlock(data, dnscache);
+}
+
+void Curl_dnscache_clear(struct Curl_easy *data)
+{
+  struct Curl_dnscache *dnscache = dnscache_get(data);
+  if(dnscache) {
+    dnscache_lock(data, dnscache);
+    Curl_hash_clean(&dnscache->entries);
+    dnscache_unlock(data, dnscache);
+  }
+}
+
+/* lookup address, returns entry if found and not stale */
+static CURLcode fetch_addr(struct Curl_easy *data,
+                           struct Curl_dnscache *dnscache,
+                           uint8_t dns_queries,
+                           const char *hostname,
+                           uint16_t port,
+                           struct Curl_dns_entry **pdns)
+{
+  struct Curl_dns_entry *dns = NULL;
+  char entry_id[MAX_HOSTCACHE_LEN];
+  size_t entry_len;
+  CURLcode result = CURLE_OK;
+
+  *pdns = NULL;
+  if(!dnscache)
+    return CURLE_OK;
+
+  /* Create an entry id, based upon the hostname and port */
+  entry_len = create_dnscache_id(hostname, 0, port,
+                                 entry_id, sizeof(entry_id));
+
+  /* See if it is already in our dns cache */
+  dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
+
+  /* No entry found in cache, check if we might have a wildcard entry */
+  if(!dns && data->state.wildcard_resolve) {
+    entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id));
+
+    /* See if it is already in our dns cache */
+    dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
+  }
+
+  if(dns && (data->set.dns_cache_timeout_ms != -1)) {
+    /* See whether the returned entry is stale. Done before we release lock */
+    struct dnscache_prune_data user;
+
+    user.now = *Curl_pgrs_now(data);
+    user.max_age_ms = data->set.dns_cache_timeout_ms;
+    user.oldest_ms = 0;
+
+    if(dnscache_entry_is_stale(&user, dns)) {
+      infof(data, "Hostname in DNS cache was stale, zapped");
+      dns = NULL; /* the memory deallocation is being handled by the hash */
+      Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
+    }
+  }
+
+  if(dns) {
+    if((dns->dns_queries & dns_queries) != dns_queries) {
+      /* The entry does not cover all wanted DNS queries, a miss. */
+      dns = NULL;
+    }
+    else if(!(dns->dns_responses & dns_queries)) {
+      /* The entry has no responses for the wanted DNS queries. */
+      CURL_TRC_DNS(data, "cache entry does not have type=%s addresses",
+                   Curl_resolv_query_str(dns_queries));
+      dns = NULL;
+      result = CURLE_COULDNT_RESOLVE_HOST;
+    }
+  }
+
+  if(dns && !dns->addr) { /* negative entry */
+    dns = NULL;
+    result = CURLE_COULDNT_RESOLVE_HOST;
+  }
+  *pdns = dns;
+  return result;
+}
+
+/*
+ * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
+ *
+ * Curl_resolv() checks initially and multi_runsingle() checks each time
+ * it discovers the handle in the state WAITRESOLVE whether the hostname
+ * has already been resolved and the address has already been stored in
+ * the DNS cache. This short circuits waiting for a lot of pending
+ * lookups for the same hostname requested by different handles.
+ *
+ * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
+ *
+ * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after
+ * use, or we will leak memory!
+ */
+CURLcode Curl_dnscache_get(struct Curl_easy *data,
+                           uint8_t dns_queries,
+                           const char *hostname,
+                           uint16_t port,
+                           struct Curl_dns_entry **pentry)
+{
+  struct Curl_dnscache *dnscache = dnscache_get(data);
+  struct Curl_dns_entry *dns = NULL;
+  CURLcode result = CURLE_OK;
+
+  dnscache_lock(data, dnscache);
+  result = fetch_addr(data, dnscache, dns_queries, hostname, port, &dns);
+  if(!result && dns)
+    dns->refcount++; /* we pass out a reference */
+  else if(result) {
+    DEBUGASSERT(!dns);
+    dns = NULL;
+  }
+  dnscache_unlock(data, dnscache);
+
+  *pentry = dns;
+  return result;
+}
+
+#ifndef CURL_DISABLE_SHUFFLE_DNS
+/*
+ * Return # of addresses in a Curl_addrinfo struct
+ */
+static int num_addresses(const struct Curl_addrinfo *addr)
+{
+  int i = 0;
+  while(addr) {
+    addr = addr->ai_next;
+    i++;
+  }
+  return i;
+}
+
+/*
+ * dns_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
+ * struct by re-linking its linked list.
+ *
+ * The addr argument should be the address of a pointer to the head node of a
+ * `Curl_addrinfo` list and it will be modified to point to the new head after
+ * shuffling.
+ *
+ * Not declared static only to make it easy to use in a unit test!
+ *
+ * @unittest 1608
+ */
+UNITTEST CURLcode dns_shuffle_addr(struct Curl_easy *data,
+                                   struct Curl_addrinfo **addr);
+UNITTEST CURLcode dns_shuffle_addr(struct Curl_easy *data,
+                                   struct Curl_addrinfo **addr)
+{
+  CURLcode result = CURLE_OK;
+  const int num_addrs = num_addresses(*addr);
+
+  if(num_addrs > 1) {
+    struct Curl_addrinfo **nodes;
+    CURL_TRC_DNS(data, "Shuffling %i addresses", num_addrs);
+
+    nodes = curlx_malloc(num_addrs * sizeof(*nodes));
+    if(nodes) {
+      int i;
+      unsigned int *rnd;
+      const size_t rnd_size = num_addrs * sizeof(*rnd);
+
+      /* build a plain array of Curl_addrinfo pointers */
+      nodes[0] = *addr;
+      for(i = 1; i < num_addrs; i++) {
+        nodes[i] = nodes[i - 1]->ai_next;
+      }
+
+      rnd = curlx_malloc(rnd_size);
+      if(rnd) {
+        /* Fisher-Yates shuffle */
+        if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
+          struct Curl_addrinfo *swap_tmp;
+          for(i = num_addrs - 1; i > 0; i--) {
+            swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)];
+            nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i];
+            nodes[i] = swap_tmp;
+          }
+
+          /* relink list in the new order */
+          for(i = 1; i < num_addrs; i++) {
+            nodes[i - 1]->ai_next = nodes[i];
+          }
+
+          nodes[num_addrs - 1]->ai_next = NULL;
+          *addr = nodes[0];
+        }
+        curlx_free(rnd);
+      }
+      else
+        result = CURLE_OUT_OF_MEMORY;
+      curlx_free(nodes);
+    }
+    else
+      result = CURLE_OUT_OF_MEMORY;
+  }
+  return result;
+}
+#endif
+
+static bool dnscache_ai_has_family(struct Curl_addrinfo *ai,
+                                   int ai_family)
+{
+  for(; ai; ai = ai->ai_next) {
+    if(ai->ai_family == ai_family)
+      return TRUE;
+  }
+  return FALSE;
+}
+
+static struct Curl_dns_entry *dnscache_entry_create(
+  struct Curl_easy *data,
+  uint8_t dns_queries,
+  struct Curl_addrinfo **paddr1,
+  struct Curl_addrinfo **paddr2,
+  const char *hostname,
+  size_t hostlen,
+  uint16_t port,
+  bool permanent)
+{
+  struct Curl_dns_entry *dns = NULL;
+
+  /* Create a new cache entry, struct already has the hostname NUL */
+  dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen);
+  if(!dns)
+    goto out;
+
+  dns->refcount = 1; /* the cache has the first reference */
+  dns->dns_queries = dns_queries;
+  dns->port = port;
+  if(hostlen)
+    memcpy(dns->hostname, hostname, hostlen);
+
+  if(permanent) {
+    dns->timestamp.tv_sec = 0; /* an entry that never goes stale */
+    dns->timestamp.tv_usec = 0; /* an entry that never goes stale */
+  }
+  else {
+    dns->timestamp = *Curl_pgrs_now(data);
+  }
+
+  /* Take the given address lists into the entry */
+  if(paddr1 && *paddr1) {
+    dns->addr = *paddr1;
+    *paddr1 = NULL;
+  }
+  if(paddr2 && *paddr2) {
+    struct Curl_addrinfo **phead = &dns->addr;
+    while(*phead)
+      phead = &(*phead)->ai_next;
+    *phead = *paddr2;
+    *paddr2 = NULL;
+  }
+
+  if((dns_queries & CURL_DNSQ_A) &&
+     dnscache_ai_has_family(dns->addr, PF_INET))
+    dns->dns_responses |= CURL_DNSQ_A;
+
+#ifdef USE_IPV6
+  if((dns_queries & CURL_DNSQ_AAAA) &&
+     dnscache_ai_has_family(dns->addr, PF_INET6))
+    dns->dns_responses |= CURL_DNSQ_AAAA;
+#endif /* USE_IPV6 */
+
+#ifndef CURL_DISABLE_SHUFFLE_DNS
+  /* shuffle addresses if requested */
+  if(data->set.dns_shuffle_addresses && dns->addr) {
+    CURLcode result = dns_shuffle_addr(data, &dns->addr);
+    if(result) {
+      /* free without lock, we are the sole owner */
+      dnscache_entry_free(dns);
+      dns = NULL;
+      goto out;
+    }
+  }
+#else
+  (void)data;
+#endif
+
+out:
+  if(paddr1 && *paddr1) {
+    Curl_freeaddrinfo(*paddr1);
+    *paddr1 = NULL;
+  }
+  if(paddr2 && *paddr2) {
+    Curl_freeaddrinfo(*paddr2);
+    *paddr2 = NULL;
+  }
+  return dns;
+}
+
+struct Curl_dns_entry *Curl_dnscache_mk_entry(struct Curl_easy *data,
+                                              uint8_t dns_queries,
+                                              struct Curl_addrinfo **paddr,
+                                              const char *hostname,
+                                              uint16_t port)
+{
+  return dnscache_entry_create(data, dns_queries, paddr, NULL, hostname,
+                               hostname ? strlen(hostname) : 0,
+                               port, FALSE);
+}
+
+struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data,
+                                               uint8_t dns_queries,
+                                               struct Curl_addrinfo **paddr1,
+                                               struct Curl_addrinfo **paddr2,
+                                               const char *hostname,
+                                               uint16_t port)
+{
+  return dnscache_entry_create(data, dns_queries, paddr1, paddr2, hostname,
+                               hostname ? strlen(hostname) : 0,
+                               port, FALSE);
+}
+
+#ifdef USE_HTTPSRR
+void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns,
+                                 struct Curl_https_rrinfo *hinfo)
+{
+  /* only do this when this is the only reference */
+  DEBUGASSERT(dns->refcount == 1);
+  /* it should have been in the queries */
+  DEBUGASSERT(dns->dns_queries & CURL_DNSQ_HTTPS);
+  if(dns->hinfo) {
+    Curl_httpsrr_cleanup(dns->hinfo);
+    curlx_free(dns->hinfo);
+  }
+  dns->hinfo = hinfo;
+  dns->dns_responses |= CURL_DNSQ_HTTPS;
+}
+#endif /* USE_HTTPSRR */
+
+static struct Curl_dns_entry *dnscache_add_addr(struct Curl_easy *data,
+                                                struct Curl_dnscache *dnscache,
+                                                uint8_t dns_queries,
+                                                struct Curl_addrinfo **paddr,
+                                                const char *hostname,
+                                                size_t hlen,
+                                                uint16_t port,
+                                                bool permanent)
+{
+  char entry_id[MAX_HOSTCACHE_LEN];
+  size_t entry_len;
+  struct Curl_dns_entry *dns;
+  struct Curl_dns_entry *dns2;
+
+  dns = dnscache_entry_create(data, dns_queries, paddr, NULL,
+                              hostname, hlen, port, permanent);
+  if(!dns)
+    return NULL;
+
+  /* Create an entry id, based upon the hostname and port */
+  entry_len = create_dnscache_id(hostname, hlen, port,
+                                 entry_id, sizeof(entry_id));
+
+  /* Store the resolved data in our DNS cache. */
+  dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1,
+                       (void *)dns);
+  if(!dns2) {
+    dnscache_entry_free(dns);
+    return NULL;
+  }
+
+  dns = dns2;
+  dns->refcount++;         /* mark entry as in-use */
+  return dns;
+}
+
+CURLcode Curl_dnscache_add(struct Curl_easy *data,
+                           struct Curl_dns_entry *entry)
+{
+  struct Curl_dnscache *dnscache = dnscache_get(data);
+  char id[MAX_HOSTCACHE_LEN];
+  size_t idlen;
+
+  if(!dnscache)
+    return CURLE_FAILED_INIT;
+  /* Create an entry id, based upon the hostname and port */
+  idlen = create_dnscache_id(entry->hostname, 0, entry->port, id, sizeof(id));
+
+  /* Store the resolved data in our DNS cache and up ref count */
+  dnscache_lock(data, dnscache);
+  if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) {
+    dnscache_unlock(data, dnscache);
+    return CURLE_OUT_OF_MEMORY;
+  }
+  entry->refcount++;
+  dnscache_unlock(data, dnscache);
+  return CURLE_OK;
+}
+
+CURLcode Curl_dnscache_add_negative(struct Curl_easy *data,
+                                    uint8_t dns_queries,
+                                    const char *host,
+                                    uint16_t port)
+{
+  struct Curl_dnscache *dnscache = dnscache_get(data);
+  struct Curl_dns_entry *dns;
+  DEBUGASSERT(dnscache);
+  if(!dnscache)
+    return CURLE_FAILED_INIT;
+
+  dnscache_lock(data, dnscache);
+
+  /* put this new host in the cache */
+  dns = dnscache_add_addr(data, dnscache, dns_queries, NULL,
+                          host, strlen(host), port, FALSE);
+  if(dns) {
+    /* release the returned reference; the cache itself will keep the
+     * entry alive: */
+    dns->refcount--;
+    dnscache_unlock(data, dnscache);
+    CURL_TRC_DNS(data, "cache negative name resolve for %s:%d type=%s",
+                 host, port, Curl_resolv_query_str(dns_queries));
+    return CURLE_OK;
+  }
+  dnscache_unlock(data, dnscache);
+  return CURLE_OUT_OF_MEMORY;
+}
+
+struct Curl_dns_entry *Curl_dns_entry_link(struct Curl_easy *data,
+                                           struct Curl_dns_entry *dns)
+{
+  if(!dns)
+    return NULL;
+  else {
+    struct Curl_dnscache *dnscache = dnscache_get(data);
+    dnscache_lock(data, dnscache);
+    dns->refcount++;
+    dnscache_unlock(data, dnscache);
+    return dns;
+  }
+}
+
+/*
+ * Curl_dns_entry_unlink() releases a reference to the given cached DNS entry.
+ * When the reference count reaches 0, the entry is destroyed. It is important
+ * that only one unlink is made for each Curl_resolv() call.
+ *
+ * May be called with 'data' == NULL for global cache.
+ */
+void Curl_dns_entry_unlink(struct Curl_easy *data,
+                           struct Curl_dns_entry **pdns)
+{
+  if(*pdns) {
+    struct Curl_dnscache *dnscache = dnscache_get(data);
+    struct Curl_dns_entry *dns = *pdns;
+    *pdns = NULL;
+    dnscache_lock(data, dnscache);
+    dns->refcount--;
+    if(dns->refcount == 0)
+      dnscache_entry_free(dns);
+    dnscache_unlock(data, dnscache);
+  }
+}
+
+static void dnscache_entry_dtor(void *entry)
+{
+  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry;
+  DEBUGASSERT(dns && (dns->refcount > 0));
+  dns->refcount--;
+  if(dns->refcount == 0)
+    dnscache_entry_free(dns);
+}
+
+/*
+ * Curl_dnscache_init() inits a new DNS cache.
+ */
+void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size)
+{
+  Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare,
+                 dnscache_entry_dtor);
+}
+
+void Curl_dnscache_destroy(struct Curl_dnscache *dns)
+{
+  Curl_hash_destroy(&dns->entries);
+}
+
+CURLcode Curl_loadhostpairs(struct Curl_easy *data)
+{
+  struct Curl_dnscache *dnscache = dnscache_get(data);
+  struct curl_slist *hostp;
+
+  if(!dnscache)
+    return CURLE_FAILED_INIT;
+
+  /* Default is no wildcard found */
+  data->state.wildcard_resolve = FALSE;
+
+  for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
+    char entry_id[MAX_HOSTCACHE_LEN];
+    const char *host = hostp->data;
+    struct Curl_str source;
+    if(!host)
+      continue;
+    if(*host == '-') {
+      curl_off_t num = 0;
+      size_t entry_len;
+      host++;
+      if(!curlx_str_single(&host, '[')) {
+        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
+           curlx_str_single(&host, ']') ||
+           curlx_str_single(&host, ':'))
+          continue;
+      }
+      else {
+        if(curlx_str_until(&host, &source, 4096, ':') ||
+           curlx_str_single(&host, ':')) {
+          continue;
+        }
+      }
+
+      if(!curlx_str_number(&host, &num, 0xffff)) {
+        /* Create an entry id, based upon the hostname and port */
+        entry_len = create_dnscache_id(curlx_str(&source),
+                                       curlx_strlen(&source), (uint16_t)num,
+                                       entry_id, sizeof(entry_id));
+        dnscache_lock(data, dnscache);
+        /* delete entry, ignore if it did not exist */
+        Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
+        dnscache_unlock(data, dnscache);
+      }
+    }
+    else {
+      struct Curl_dns_entry *dns;
+      struct Curl_addrinfo *head = NULL, *tail = NULL;
+      size_t entry_len;
+      char address[64];
+      curl_off_t tmpofft = 0;
+      uint16_t port = 0;
+      bool permanent = TRUE;
+      bool error = TRUE;
+      VERBOSE(const char *addresses = NULL);
+
+      if(*host == '+') {
+        host++;
+        permanent = FALSE;
+      }
+      if(!curlx_str_single(&host, '[')) {
+        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
+           curlx_str_single(&host, ']'))
+          continue;
+      }
+      else {
+        if(curlx_str_until(&host, &source, 4096, ':'))
+          continue;
+      }
+      if(curlx_str_single(&host, ':') ||
+         curlx_str_number(&host, &tmpofft, 0xffff) ||
+         curlx_str_single(&host, ':'))
+        goto err;
+      port = (uint16_t)tmpofft;
+
+      VERBOSE(addresses = host);
+
+      /* start the address section */
+      while(*host) {
+        struct Curl_str target;
+        struct Curl_addrinfo *ai;
+        CURLcode result;
+
+        if(!curlx_str_single(&host, '[')) {
+          if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') ||
+             curlx_str_single(&host, ']'))
+            goto err;
+        }
+        else {
+          if(curlx_str_until(&host, &target, 4096, ',')) {
+            if(curlx_str_single(&host, ','))
+              goto err;
+            /* survive nothing but a comma */
+            continue;
+          }
+        }
+#ifndef USE_IPV6
+        if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) {
+          infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.",
+                (int)curlx_strlen(&target), curlx_str(&target));
+          if(curlx_str_single(&host, ','))
+            goto err;
+          continue;
+        }
+#endif
+
+        if(curlx_strlen(&target) >= sizeof(address))
+          goto err;
+
+        memcpy(address, curlx_str(&target), curlx_strlen(&target));
+        address[curlx_strlen(&target)] = '\0';
+
+        result = Curl_str2addr(address, port, &ai);
+        if(result) {
+          infof(data, "Resolve address '%s' found illegal", address);
+          goto err;
+        }
+
+        if(tail) {
+          tail->ai_next = ai;
+          tail = tail->ai_next;
+        }
+        else {
+          head = tail = ai;
+        }
+        if(curlx_str_single(&host, ','))
+          break;
+      }
+
+      if(!head)
+        goto err;
+
+      error = FALSE;
+err:
+      if(error) {
+        failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data);
+        Curl_freeaddrinfo(head);
+        return CURLE_SETOPT_OPTION_SYNTAX;
+      }
+
+      /* Create an entry id, based upon the hostname and port */
+      entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source),
+                                     port, entry_id, sizeof(entry_id));
+
+      dnscache_lock(data, dnscache);
+
+      /* See if it is already in our dns cache */
+      dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
+
+      if(dns) {
+        infof(data, "RESOLVE %.*s:%u - old addresses discarded",
+              (int)curlx_strlen(&source),
+              curlx_str(&source), port);
+        /* delete old entry, there are two reasons for this
+         1. old entry may have different addresses.
+         2. even if entry with correct addresses is already in the cache,
+            but if it is close to expire, then by the time next http
+            request is made, it can get expired and pruned because old
+            entry is not necessarily marked as permanent.
+         3. when adding a non-permanent entry, we want it to remove and
+            replace an existing permanent entry.
+         4. when adding a non-permanent entry, we want it to get a "fresh"
+            timeout that starts _now_. */
+
+        Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
+      }
+
+      /* put this new host in the cache, an overridy for ALL dns queries */
+      dns = dnscache_add_addr(data, dnscache, CURL_DNSQ_ALL,
+                              &head, curlx_str(&source),
+                              curlx_strlen(&source), port, permanent);
+      if(dns)
+        /* release the returned reference; the cache itself will keep the
+         * entry alive: */
+        dns->refcount--;
+
+      dnscache_unlock(data, dnscache);
+
+      if(!dns)
+        return CURLE_OUT_OF_MEMORY;
+
+      infof(data, "Added %.*s:%u:%s to DNS cache%s",
+            (int)curlx_strlen(&source), curlx_str(&source), port, addresses,
+            permanent ? "" : " (non-permanent)");
+
+      /* Wildcard hostname */
+      if(curlx_str_casecompare(&source, "*")) {
+        infof(data, "RESOLVE *:%u using wildcard", port);
+        data->state.wildcard_resolve = TRUE;
+      }
+    }
+  }
+  data->state.resolve = NULL; /* dealt with now */
+
+  return CURLE_OK;
+}
diff --git a/Utilities/cmcurl/lib/dnscache.h b/Utilities/cmcurl/lib/dnscache.h
new file mode 100644
index 0000000..ebe25f6
--- /dev/null
+++ b/Utilities/cmcurl/lib/dnscache.h
@@ -0,0 +1,143 @@
+#ifndef HEADER_CURL_DNSCACHE_H
+#define HEADER_CURL_DNSCACHE_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 "hash.h"
+#include "curlx/timeval.h"
+
+struct addrinfo;
+struct hostent;
+struct Curl_easy;
+struct connectdata;
+struct easy_pollset;
+struct Curl_https_rrinfo;
+struct Curl_multi;
+
+struct Curl_dns_entry {
+  struct Curl_addrinfo *addr;
+#ifdef USE_HTTPSRR
+  struct Curl_https_rrinfo *hinfo;
+#endif
+  /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */
+  struct curltime timestamp;
+  /* reference counter, entry is freed on reaching 0 */
+  uint32_t refcount;
+  /* hostname port number that resolved to addr. */
+  uint16_t port;
+  uint8_t dns_queries; /* CURL_DNSQ_* type of queries performed for this */
+  uint8_t dns_responses; /* CURL_DNSQ_* type this entry has responses for */
+  /* hostname that resolved to addr. may be NULL (Unix domain sockets). */
+  char hostname[1];
+};
+
+/*
+ * Create a `Curl_dns_entry` with a reference count of 1.
+ * Use `Curl_dns_entry_unlink()` to release your hold on it.
+ *
+ * The call takes ownership of `paddr`, even in case of failure, and always
+ * clears `*paddr`. It makes a copy of `hostname`.
+ *
+ * Returns entry or NULL on OOM.
+ */
+struct Curl_dns_entry *Curl_dnscache_mk_entry(struct Curl_easy *data,
+                                              uint8_t dns_queries,
+                                              struct Curl_addrinfo **paddr,
+                                              const char *hostname,
+                                              uint16_t port);
+
+struct Curl_dns_entry *Curl_dnscache_mk_entry2(struct Curl_easy *data,
+                                               uint8_t dns_queries,
+                                               struct Curl_addrinfo **paddr1,
+                                               struct Curl_addrinfo **paddr2,
+                                               const char *hostname,
+                                               uint16_t port);
+
+#ifdef USE_HTTPSRR
+void Curl_dns_entry_set_https_rr(struct Curl_dns_entry *dns,
+                                 struct Curl_https_rrinfo *hinfo);
+#endif /* USE_HTTPSRR */
+
+/* Increase the ref counter and return it for storing in another place.
+ * May be called with NULL, in which case it returns NULL. */
+struct Curl_dns_entry *Curl_dns_entry_link(struct Curl_easy *data,
+                                           struct Curl_dns_entry *dns);
+
+/* unlink a dns entry, frees all resources if it was the last reference.
+ * Always clears `*pdns`` */
+void Curl_dns_entry_unlink(struct Curl_easy *data,
+                           struct Curl_dns_entry **pdns);
+
+struct Curl_dnscache {
+  struct Curl_hash entries;
+};
+
+/* init a new dns cache */
+void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size);
+
+void Curl_dnscache_destroy(struct Curl_dnscache *dns);
+
+/* prune old entries from the DNS cache */
+void Curl_dnscache_prune(struct Curl_easy *data);
+
+/* clear the DNS cache */
+void Curl_dnscache_clear(struct Curl_easy *data);
+
+/*
+ * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
+ *
+ * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
+ *
+ * The returned data *MUST* be "released" with Curl_dns_entry_unlink() after
+ * use, or we will leak memory!
+ * Returns CURLE_OK or CURLE_COULDNT_RESOLVE_HOST when a negative
+ * entry was in the cache.
+ */
+CURLcode Curl_dnscache_get(struct Curl_easy *data,
+                           uint8_t dns_queries,
+                           const char *hostname,
+                           uint16_t port,
+                           struct Curl_dns_entry **pentry);
+
+/*
+ * Curl_dnscache_addr() adds `entry` to the cache, increasing its
+ * reference count on success.
+ */
+CURLcode Curl_dnscache_add(struct Curl_easy *data,
+                           struct Curl_dns_entry *entry);
+
+/* Store a "negative" entry for host:port, e.g. remember that
+ * it could not be resolved. */
+CURLcode Curl_dnscache_add_negative(struct Curl_easy *data,
+                                    uint8_t dns_queries,
+                                    const char *host,
+                                    uint16_t port);
+
+/*
+ * Populate the cache with specified entries from CURLOPT_RESOLVE.
+ */
+CURLcode Curl_loadhostpairs(struct Curl_easy *data);
+
+#endif /* HEADER_CURL_DNSCACHE_H */
diff --git a/Utilities/cmcurl/lib/doh.c b/Utilities/cmcurl/lib/doh.c
index 4488d42..3044135 100644
--- a/Utilities/cmcurl/lib/doh.c
+++ b/Utilities/cmcurl/lib/doh.c
@@ -40,6 +40,9 @@
 
 #define DNS_CLASS_IN 0x01
 
+static void doh_close(struct Curl_easy *data,
+                      struct Curl_resolv_async *async);
+
 #ifdef CURLVERBOSE
 static const char * const errors[] = {
   "",
@@ -71,6 +74,11 @@
  */
 UNITTEST DOHcode doh_req_encode(const char *host,
                                 DNStype dnstype,
+                                unsigned char *dnsp,  /* buffer */
+                                size_t len,  /* buffer size */
+                                size_t *olen);  /* output length */
+UNITTEST DOHcode doh_req_encode(const char *host,
+                                DNStype dnstype,
                                 unsigned char *dnsp, /* buffer */
                                 size_t len,   /* buffer size */
                                 size_t *olen) /* output length */
@@ -205,51 +213,62 @@
 #endif
 
 /* called from multi when a sub transfer, e.g. doh probe, is done.
- * This looks up the the probe response at its meta CURL_EZM_DOH_PROBE
+ * This looks up the probe response at its meta CURL_EZM_DOH_PROBE
  * and copies the response body over to the struct at the master's
  * meta at CURL_EZM_DOH_MASTER. */
 static void doh_probe_done(struct Curl_easy *data,
                            struct Curl_easy *doh, CURLcode result)
 {
-  struct doh_probes *dohp = data->state.async.doh;
-  DEBUGASSERT(dohp);
-  if(dohp) {
-    struct doh_request *doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE);
-    int i;
+  struct Curl_resolv_async *async = NULL;
+  struct doh_probes *dohp = NULL;
+  struct doh_request *doh_req = NULL;
+  int i;
 
-    for(i = 0; i < DOH_SLOT_COUNT; ++i) {
-      if(dohp->probe_resp[i].probe_mid == doh->mid)
-        break;
-    }
-    if(i >= DOH_SLOT_COUNT) {
-      failf(data, "unknown sub request done");
-      return;
-    }
+  doh_req = Curl_meta_get(doh, CURL_EZM_DOH_PROBE);
+  if(!doh_req) {
+    DEBUGASSERT(0);
+    return;
+  }
 
-    dohp->pending--;
-    infof(doh, "a DoH request is completed, %u to go", dohp->pending);
-    dohp->probe_resp[i].result = result;
-    /* We expect either the meta data still to exist or the sub request
-     * to have already failed. */
-    DEBUGASSERT(doh_req || result);
-    if(doh_req) {
-      if(!result) {
-        dohp->probe_resp[i].dnstype = doh_req->dnstype;
-        result = curlx_dyn_addn(&dohp->probe_resp[i].body,
-                                curlx_dyn_ptr(&doh_req->resp_body),
-                                curlx_dyn_len(&doh_req->resp_body));
-        curlx_dyn_free(&doh_req->resp_body);
-      }
-      Curl_meta_remove(doh, CURL_EZM_DOH_PROBE);
-    }
+  async = Curl_async_get(data, doh_req->resolv_id);
+  if(!async) {
+    CURL_TRC_DNS(data, "[%u] ignoring outdated DoH response",
+                 doh_req->resolv_id);
+    return;
+  }
+  dohp = async->doh;
 
-    if(result)
-      infof(doh, "DoH request %s", curl_easy_strerror(result));
+  for(i = 0; i < DOH_SLOT_COUNT; ++i) {
+    if(dohp->probe_resp[i].probe_mid == doh->mid)
+      break;
+  }
+  /* We really should have found the slot where to store the response */
+  if(i >= DOH_SLOT_COUNT) {
+    DEBUGASSERT(0);
+    failf(data, "DoH: unknown sub request done");
+    return;
+  }
 
-    if(!dohp->pending) {
-      /* DoH completed, run the transfer picking up the results */
-      Curl_multi_mark_dirty(data);
-    }
+  dohp->pending--;
+  infof(doh, "a DoH request is completed, %u to go", dohp->pending);
+  dohp->probe_resp[i].result = result;
+  /* We expect either the meta data still to exist or the sub request
+   * to have already failed. */
+  if(!result) {
+    dohp->probe_resp[i].dnstype = doh_req->dnstype;
+    result = curlx_dyn_addn(&dohp->probe_resp[i].body,
+                            curlx_dyn_ptr(&doh_req->resp_body),
+                            curlx_dyn_len(&doh_req->resp_body));
+    curlx_dyn_free(&doh_req->resp_body);
+  }
+  Curl_meta_remove(doh, CURL_EZM_DOH_PROBE);
+
+  if(result)
+    infof(doh, "DoH request %s", curl_easy_strerror(result));
+
+  if(!dohp->pending) {
+    /* DoH completed, run the transfer picking up the results */
+    Curl_multi_mark_dirty(data);
   }
 }
 
@@ -278,6 +297,7 @@
                               DNStype dnstype,
                               const char *host,
                               const char *url, CURLM *multi,
+                              uint32_t resolv_id,
                               uint32_t *pmid)
 {
   struct Curl_easy *doh = NULL;
@@ -291,6 +311,7 @@
   doh_req = curlx_calloc(1, sizeof(*doh_req));
   if(!doh_req)
     return CURLE_OUT_OF_MEMORY;
+  doh_req->resolv_id = resolv_id;
   doh_req->dnstype = dnstype;
   curlx_dyn_init(&doh_req->resp_body, DYN_DOH_RESPONSE);
 
@@ -431,29 +452,22 @@
  * a 'Curl_addrinfo *' with the address information.
  */
 
-CURLcode Curl_doh(struct Curl_easy *data, const char *hostname,
-                  int port, int ip_version)
+CURLcode Curl_doh(struct Curl_easy *data,
+                  struct Curl_resolv_async *async)
 {
   CURLcode result = CURLE_OK;
   struct doh_probes *dohp = NULL;
-  struct connectdata *conn = data->conn;
   size_t i;
 
-  DEBUGASSERT(conn);
-  DEBUGASSERT(!data->state.async.doh);
-  DEBUGASSERT(hostname && hostname[0]);
-  if(data->state.async.doh)
-    Curl_doh_cleanup(data);
-
-  data->state.async.done = FALSE;
-  data->state.async.port = port;
-  data->state.async.ip_version = ip_version;
-  data->state.async.hostname = curlx_strdup(hostname);
-  if(!data->state.async.hostname)
-    return CURLE_OUT_OF_MEMORY;
+  DEBUGASSERT(!async->doh);
+  DEBUGASSERT(async->hostname[0]);
+  if(async->doh) {
+    DEBUGASSERT(0); /* should not happen */
+    Curl_doh_cleanup(data, async);
+  }
 
   /* start clean, consider allocating this struct on demand */
-  data->state.async.doh = dohp = curlx_calloc(1, sizeof(struct doh_probes));
+  async->doh = dohp = curlx_calloc(1, sizeof(struct doh_probes));
   if(!dohp)
     return CURLE_OUT_OF_MEMORY;
 
@@ -462,28 +476,29 @@
     curlx_dyn_init(&dohp->probe_resp[i].body, DYN_DOH_RESPONSE);
   }
 
-  conn->bits.doh = TRUE;
-  dohp->host = data->state.async.hostname;
-  dohp->port = data->state.async.port;
+  dohp->host = async->hostname;
+  dohp->port = async->port;
   /* We are making sub easy handles and want to be called back when
    * one is done. */
   data->sub_xfer_done = doh_probe_done;
 
   /* create IPv4 DoH request */
-  result = doh_probe_run(data, CURL_DNS_TYPE_A,
-                         hostname, data->set.str[STRING_DOH],
-                         data->multi,
-                         &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid);
-  if(result)
-    goto error;
-  dohp->pending++;
+  if(async->dns_queries & CURL_DNSQ_A) {
+    result = doh_probe_run(data, CURL_DNS_TYPE_A,
+                           async->hostname, data->set.str[STRING_DOH],
+                           data->multi, async->id,
+                           &dohp->probe_resp[DOH_SLOT_IPV4].probe_mid);
+    if(result)
+      goto error;
+    dohp->pending++;
+  }
 
 #ifdef USE_IPV6
-  if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
+  if(async->dns_queries & CURL_DNSQ_AAAA) {
     /* create IPv6 DoH request */
     result = doh_probe_run(data, CURL_DNS_TYPE_AAAA,
-                           hostname, data->set.str[STRING_DOH],
-                           data->multi,
+                           async->hostname, data->set.str[STRING_DOH],
+                           data->multi, async->id,
                            &dohp->probe_resp[DOH_SLOT_IPV6].probe_mid);
     if(result)
       goto error;
@@ -492,17 +507,17 @@
 #endif
 
 #ifdef USE_HTTPSRR
-  if(conn->scheme->protocol & PROTO_FAMILY_HTTP) {
-    /* Only use HTTPS RR for HTTP(S) transfers */
+  if(async->dns_queries & CURL_DNSQ_HTTPS) {
     char *qname = NULL;
-    if(port != PORT_HTTPS) {
-      qname = curl_maprintf("_%d._https.%s", port, hostname);
+    if(async->port != PORT_HTTPS) {
+      qname = curl_maprintf("_%d._https.%s", async->port, async->hostname);
       if(!qname)
         goto error;
     }
     result = doh_probe_run(data, CURL_DNS_TYPE_HTTPS,
-                           qname ? qname : hostname, data->set.str[STRING_DOH],
-                           data->multi,
+                           qname ? qname : async->hostname,
+                           data->set.str[STRING_DOH], data->multi,
+                           async->id,
                            &dohp->probe_resp[DOH_SLOT_HTTPS_RR].probe_mid);
     curlx_free(qname);
     if(result)
@@ -513,7 +528,7 @@
   return CURLE_OK;
 
 error:
-  Curl_doh_cleanup(data);
+  Curl_doh_cleanup(data, async);
   return result;
 }
 
@@ -698,6 +713,8 @@
   return DOH_OK;
 }
 
+/* @unittest 1655 */
+UNITTEST void de_init(struct dohentry *de);
 UNITTEST void de_init(struct dohentry *de)
 {
   int i;
@@ -707,6 +724,11 @@
     curlx_dyn_init(&de->cname[i], DYN_DOH_CNAME);
 }
 
+/* @unittest 1655 */
+UNITTEST DOHcode doh_resp_decode(const unsigned char *doh,
+                                 size_t dohlen,
+                                 DNStype dnstype,
+                                 struct dohentry *d);
 UNITTEST DOHcode doh_resp_decode(const unsigned char *doh,
                                  size_t dohlen,
                                  DNStype dnstype,
@@ -838,7 +860,7 @@
   if(index != dohlen)
     return DOH_DNS_MALFORMAT; /* something is wrong */
 
-#ifdef USE_HTTTPS
+#ifdef USE_HTTPSRR
   if((type != CURL_DNS_TYPE_NS) && !d->numcname && !d->numaddr &&
      !d->numhttps_rrs)
 #else
@@ -1024,6 +1046,8 @@
 }
 #endif
 
+/* @unittest 1655 */
+UNITTEST void de_cleanup(struct dohentry *d);
 UNITTEST void de_cleanup(struct dohentry *d)
 {
   int i = 0;
@@ -1032,7 +1056,7 @@
   }
 #ifdef USE_HTTPSRR
   for(i = 0; i < d->numhttps_rrs; i++)
-    Curl_safefree(d->https_rrs[i].val);
+    curlx_safefree(d->https_rrs[i].val);
 #endif
 }
 
@@ -1049,7 +1073,7 @@
  * https://datatracker.ietf.org/doc/html/rfc1035#section-3.1
  *
  * The input buffer pointer will be modified so it points to after the end of
- * the DNS name encoding on output. (And that is why it is an "unsigned char
+ * the DNS name encoding on output. (that is why it is an "unsigned char
  * **" :-)
  */
 static CURLcode doh_decode_rdata_name(const unsigned char **buf,
@@ -1095,11 +1119,10 @@
   return CURLE_OK;
 }
 
+/* @unittest 1658 */
 UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data,
                                           const unsigned char *cp, size_t len,
                                           struct Curl_https_rrinfo **hrr);
-
-/* @unittest 1658 */
 UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data,
                                           const unsigned char *cp, size_t len,
                                           struct Curl_https_rrinfo **hrr)
@@ -1111,6 +1134,7 @@
   CURLcode result = CURLE_OUT_OF_MEMORY;
   size_t olen;
 
+  (void)data;
   *hrr = NULL;
   if(len <= 2)
     return CURLE_BAD_FUNCTION_ARGUMENT;
@@ -1128,7 +1152,6 @@
     result = CURLE_WEIRD_SERVER_REPLY;
     goto err;
   }
-  lhrr->port = -1; /* until set */
   while(len >= 4) {
     pcode = doh_get16bit(cp, 0);
     plen = doh_get16bit(cp, 2);
@@ -1138,9 +1161,10 @@
       result = CURLE_WEIRD_SERVER_REPLY;
       goto err;
     }
-    result = Curl_httpsrr_set(data, lhrr, pcode, cp, plen);
+    result = Curl_httpsrr_set(lhrr, pcode, cp, plen);
     if(result)
       goto err;
+    Curl_httpsrr_trace(data, lhrr);
     cp += plen;
     len -= plen;
     expected_min_pcode = pcode + 1;
@@ -1150,16 +1174,13 @@
   return CURLE_OK;
 err:
   Curl_httpsrr_cleanup(lhrr);
-  Curl_safefree(lhrr);
+  curlx_safefree(lhrr);
   return result;
 }
 
 #if defined(DEBUGBUILD) && defined(CURLVERBOSE)
-UNITTEST void doh_print_httpsrr(struct Curl_easy *data,
-                                struct Curl_https_rrinfo *hrr);
-
-UNITTEST void doh_print_httpsrr(struct Curl_easy *data,
-                                struct Curl_https_rrinfo *hrr)
+static void doh_print_httpsrr(struct Curl_easy *data,
+                              struct Curl_https_rrinfo *hrr)
 {
   DEBUGASSERT(hrr);
   infof(data, "HTTPS RR: priority %d, target: %s", hrr->priority, hrr->target);
@@ -1194,32 +1215,31 @@
 # endif
 #endif
 
-CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
-                              struct Curl_dns_entry **dnsp)
+CURLcode Curl_doh_take_result(struct Curl_easy *data,
+                              struct Curl_resolv_async *async,
+                              struct Curl_dns_entry **pdns)
 {
+  struct doh_probes *dohp = async->doh;
   CURLcode result = CURLE_OK;
-  struct doh_probes *dohp = data->state.async.doh;
   struct dohentry de;
-  *dnsp = NULL; /* defaults to no response */
+
+  *pdns = NULL; /* defaults to no response */
   if(!dohp)
     return CURLE_OUT_OF_MEMORY;
 
   if(dohp->probe_resp[DOH_SLOT_IPV4].probe_mid == UINT32_MAX &&
      dohp->probe_resp[DOH_SLOT_IPV6].probe_mid == UINT32_MAX) {
     failf(data, "Could not DoH-resolve: %s", dohp->host);
-    return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY :
-      CURLE_COULDNT_RESOLVE_HOST;
+    return async->for_proxy ?
+      CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST;
   }
   else if(!dohp->pending) {
     DOHcode rc[DOH_SLOT_COUNT];
     int slot;
 
-    /* Clear any result the might still be there */
-    Curl_resolv_unlink(data, &data->state.async.dns);
-
     memset(rc, 0, sizeof(rc));
     /* remove DoH handles from multi handle and close them */
-    Curl_doh_close(data);
+    doh_close(data, async);
     /* parse the responses, create the struct and return it! */
     de_init(&de);
     for(slot = 0; slot < DOH_SLOT_COUNT; slot++) {
@@ -1235,7 +1255,6 @@
       }
     } /* next slot */
 
-    result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */
     if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) {
       /* we have an address, of one kind or other */
       struct Curl_dns_entry *dns;
@@ -1251,50 +1270,56 @@
         goto error;
 
       /* we got a response, create a dns entry. */
-      dns = Curl_dnscache_mk_entry(data, &ai, dohp->host, 0,
-                                   dohp->port, FALSE);
-      if(dns) {
-        /* Now add and HTTPSRR information if we have */
-#ifdef USE_HTTPSRR
-        if(de.numhttps_rrs > 0 && result == CURLE_OK) {
-          struct Curl_https_rrinfo *hrr = NULL;
-          result = doh_resp_decode_httpsrr(data, de.https_rrs->val,
-                                           de.https_rrs->len, &hrr);
-          if(result) {
-            infof(data, "Failed to decode HTTPS RR");
-            Curl_resolv_unlink(data, &dns);
-            goto error;
-          }
-          infof(data, "Some HTTPS RR to process");
-#if defined(DEBUGBUILD) && defined(CURLVERBOSE)
-          doh_print_httpsrr(data, hrr);
-#endif
-          dns->hinfo = hrr;
-        }
-#endif /* USE_HTTPSRR */
-        /* and add the entry to the cache */
-        data->state.async.dns = dns;
-        result = Curl_dnscache_add(data, dns);
-        *dnsp = data->state.async.dns;
+      dns = Curl_dnscache_mk_entry(data, async->dns_queries,
+                                   &ai, dohp->host, dohp->port);
+      if(!dns) {
+        result = CURLE_OUT_OF_MEMORY;
+        goto error;
       }
-    } /* address processing done */
 
-    /* All done */
-    data->state.async.done = TRUE;
+      /* Now add and HTTPSRR information if we have */
+#ifdef USE_HTTPSRR
+      if(de.numhttps_rrs > 0 && result == CURLE_OK) {
+        struct Curl_https_rrinfo *hrr = NULL;
+        result = doh_resp_decode_httpsrr(data, de.https_rrs->val,
+                                         de.https_rrs->len, &hrr);
+        if(result) {
+          infof(data, "Failed to decode HTTPS RR");
+          Curl_dns_entry_unlink(data, &dns);
+          goto error;
+        }
+        infof(data, "Some HTTPS RR to process");
+#if defined(DEBUGBUILD) && defined(CURLVERBOSE)
+        doh_print_httpsrr(data, hrr);
+#endif
+        Curl_dns_entry_set_https_rr(dns, hrr);
+      }
+#endif /* USE_HTTPSRR */
+
+      /* and add the entry to the cache */
+      result = Curl_dnscache_add(data, dns);
+      *pdns = dns;
+    } /* address processing done */
+    else {
+      result = async->for_proxy ?
+        CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST;
+    }
+
   } /* !dohp->pending */
   else
     /* wait for pending DoH transactions to complete */
-    return CURLE_OK;
+    return CURLE_AGAIN;
 
 error:
   de_cleanup(&de);
-  Curl_doh_cleanup(data);
+  Curl_doh_cleanup(data, async);
   return result;
 }
 
-void Curl_doh_close(struct Curl_easy *data)
+static void doh_close(struct Curl_easy *data,
+                      struct Curl_resolv_async *async)
 {
-  struct doh_probes *doh = data->state.async.doh;
+  struct doh_probes *doh = async ? async->doh : NULL;
   if(doh && data->multi) {
     struct Curl_easy *probe_data;
     uint32_t mid;
@@ -1320,16 +1345,17 @@
   }
 }
 
-void Curl_doh_cleanup(struct Curl_easy *data)
+void Curl_doh_cleanup(struct Curl_easy *data,
+                      struct Curl_resolv_async *async)
 {
-  struct doh_probes *dohp = data->state.async.doh;
+  struct doh_probes *dohp = async->doh;
   if(dohp) {
     int i;
-    Curl_doh_close(data);
+    doh_close(data, async);
     for(i = 0; i < DOH_SLOT_COUNT; ++i) {
       curlx_dyn_free(&dohp->probe_resp[i].body);
     }
-    Curl_safefree(data->state.async.doh);
+    curlx_safefree(async->doh);
   }
 }
 
diff --git a/Utilities/cmcurl/lib/doh.h b/Utilities/cmcurl/lib/doh.h
index 3eba4df..428b230 100644
--- a/Utilities/cmcurl/lib/doh.h
+++ b/Utilities/cmcurl/lib/doh.h
@@ -25,7 +25,10 @@
  ***************************************************************************/
 #include "urldata.h"
 
-#ifndef CURL_DISABLE_DOH
+/* enums outside of the #ifdef to make the types work in unitprotos.h even on
+   builds without DoH support */
+
+struct Curl_resolv_async;
 
 typedef enum {
   DOH_OK,
@@ -53,6 +56,10 @@
   CURL_DNS_TYPE_HTTPS = 65
 } DNStype;
 
+struct dohentry; /* forward-declare for non-DoH builds */
+
+#ifndef CURL_DISABLE_DOH
+
 enum doh_slot_num {
   /* Explicit values for first two symbols so as to match hard-coded
    * constants in existing code
@@ -86,6 +93,7 @@
   struct curl_slist *req_hds;
   struct dynbuf resp_body;
   size_t req_body_len;
+  uint32_t resolv_id; /* id of the resolve operation */
   DNStype dnstype;
 };
 
@@ -101,7 +109,7 @@
 struct doh_probes {
   struct doh_response probe_resp[DOH_SLOT_COUNT];
   unsigned int pending; /* still outstanding probes */
-  int port;
+  uint16_t port;
   const char *host;
 };
 
@@ -109,12 +117,12 @@
  * Curl_doh() starts a name resolve using DoH (DNS-over-HTTPS). It resolves a
  * name and returns a 'Curl_addrinfo *' with the address information.
  */
+CURLcode Curl_doh(struct Curl_easy *data,
+                  struct Curl_resolv_async *async);
 
-CURLcode Curl_doh(struct Curl_easy *data, const char *hostname,
-                  int port, int ip_version);
-
-CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
-                              struct Curl_dns_entry **dnsp);
+CURLcode Curl_doh_take_result(struct Curl_easy *data,
+                              struct Curl_resolv_async *async,
+                              struct Curl_dns_entry **pdns);
 
 #define DOH_MAX_ADDR  24
 #define DOH_MAX_CNAME 4
@@ -155,27 +163,15 @@
 #endif
 };
 
-void Curl_doh_close(struct Curl_easy *data);
-void Curl_doh_cleanup(struct Curl_easy *data);
+void Curl_doh_cleanup(struct Curl_easy *data,
+                      struct Curl_resolv_async *async);
+#define Curl_doh_wanted(d)  (!!(d)->set.doh)
 
-#ifdef UNITTESTS
-UNITTEST DOHcode doh_req_encode(const char *host,
-                                DNStype dnstype,
-                                unsigned char *dnsp,  /* buffer */
-                                size_t len,  /* buffer size */
-                                size_t *olen);  /* output length */
-UNITTEST DOHcode doh_resp_decode(const unsigned char *doh,
-                                 size_t dohlen,
-                                 DNStype dnstype,
-                                 struct dohentry *d);
-
-UNITTEST void de_init(struct dohentry *d);
-UNITTEST void de_cleanup(struct dohentry *d);
-#endif
 
 #else /* CURL_DISABLE_DOH */
-#define Curl_doh(a, b, c, d, e)    NULL
-#define Curl_doh_is_resolved(x, y) CURLE_COULDNT_RESOLVE_HOST
+#define Curl_doh(a, b)             NULL
+#define Curl_doh_take_result(x, y, z) CURLE_COULDNT_RESOLVE_HOST
+#define Curl_doh_wanted(d)         FALSE
 #endif /* !CURL_DISABLE_DOH */
 
 #endif /* HEADER_CURL_DOH_H */
diff --git a/Utilities/cmcurl/lib/dynhds.c b/Utilities/cmcurl/lib/dynhds.c
index 7424a57..097306c 100644
--- a/Utilities/cmcurl/lib/dynhds.c
+++ b/Utilities/cmcurl/lib/dynhds.c
@@ -76,7 +76,7 @@
       entry_free(dynhds->hds[i]);
     }
   }
-  Curl_safefree(dynhds->hds);
+  curlx_safefree(dynhds->hds);
   dynhds->hds_len = dynhds->hds_allc = dynhds->strs_len = 0;
 }
 
@@ -158,7 +158,7 @@
     if(dynhds->hds) {
       memcpy(nhds, dynhds->hds,
              dynhds->hds_len * sizeof(struct dynhds_entry *));
-      Curl_safefree(dynhds->hds);
+      curlx_safefree(dynhds->hds);
     }
     dynhds->hds = nhds;
     dynhds->hds_allc = nallc;
@@ -220,21 +220,36 @@
 }
 
 #ifdef UNITTESTS
-/* used by unit2602.c */
+/* @unittest 2602 */
 
-bool Curl_dynhds_contains(struct dynhds *dynhds,
-                          const char *name, size_t namelen)
+/**
+ * Return TRUE iff one or more headers with the given name exist.
+ */
+UNITTEST bool dynhds_contains(struct dynhds *dynhds,
+                              const char *name, size_t namelen);
+UNITTEST bool 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)
+/* @unittest 2602 */
+UNITTEST bool dynhds_ccontains(struct dynhds *dynhds, const char *name);
+UNITTEST bool dynhds_ccontains(struct dynhds *dynhds, const char *name)
 {
-  return Curl_dynhds_contains(dynhds, name, strlen(name));
+  return dynhds_contains(dynhds, name, strlen(name));
 }
 
-size_t Curl_dynhds_count_name(struct dynhds *dynhds,
-                              const char *name, size_t namelen)
+/**
+ * Return how often the given name appears in `dynhds`.
+ * Names are case-insensitive.
+ *
+ * @unittest 2602
+ */
+UNITTEST size_t dynhds_count_name(struct dynhds *dynhds,
+                                  const char *name, size_t namelen);
+UNITTEST size_t dynhds_count_name(struct dynhds *dynhds,
+                                  const char *name, size_t namelen)
 {
   size_t n = 0;
   if(dynhds->hds_len) {
@@ -248,21 +263,30 @@
   return n;
 }
 
-size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name)
+/**
+ * Return how often the given null-terminated name appears in `dynhds`.
+ * Names are case-insensitive.
+ *
+ * @unittest 2602
+ */
+UNITTEST size_t dynhds_ccount_name(struct dynhds *dynhds,
+                                        const char *name);
+UNITTEST size_t dynhds_ccount_name(struct dynhds *dynhds,
+                                        const char *name)
 {
-  return Curl_dynhds_count_name(dynhds, name, strlen(name));
+  return 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)
+/**
+ * Remove all entries with the given name.
+ * Returns number of entries removed.
+ *
+ * @unittest 2602
+ */
+UNITTEST size_t dynhds_remove(struct dynhds *dynhds,
+                              const char *name, size_t namelen);
+UNITTEST size_t dynhds_remove(struct dynhds *dynhds,
+                              const char *name, size_t namelen)
 {
   size_t n = 0;
   if(dynhds->hds_len) {
@@ -287,12 +311,32 @@
   return n;
 }
 
-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.
+ *
+ * @unittest 2602
+ */
+UNITTEST CURLcode dynhds_set(struct dynhds *dynhds,
+                             const char *name, size_t namelen,
+                             const char *value, size_t valuelen);
+UNITTEST CURLcode dynhds_set(struct dynhds *dynhds,
+                             const char *name, size_t namelen,
+                             const char *value, size_t valuelen)
 {
-  return Curl_dynhds_remove(dynhds, name, strlen(name));
+  dynhds_remove(dynhds, name, namelen);
+  return Curl_dynhds_add(dynhds, name, namelen, value, valuelen);
 }
 
-#endif
+/* @unittest 2602 */
+UNITTEST size_t dynhds_cremove(struct dynhds *dynhds, const char *name);
+UNITTEST size_t dynhds_cremove(struct dynhds *dynhds, const char *name)
+{
+  return dynhds_remove(dynhds, name, strlen(name));
+}
+
+#endif /* UNITTESTS */
 
 CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf)
 {
diff --git a/Utilities/cmcurl/lib/dynhds.h b/Utilities/cmcurl/lib/dynhds.h
index e30eb45..d5337eb 100644
--- a/Utilities/cmcurl/lib/dynhds.h
+++ b/Utilities/cmcurl/lib/dynhds.h
@@ -94,47 +94,6 @@
                                      const char *name, size_t namelen);
 struct dynhds_entry *Curl_dynhds_cget(struct dynhds *dynhds, const char *name);
 
-#ifdef UNITTESTS
-/* used by unit2602.c */
-
-/**
- * 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 null-terminated name appears in `dynhds`.
- * Names are case-insensitive.
- */
-size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name);
-
-/**
- * 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);
-#endif
-
 CURLcode Curl_dynhds_cset(struct dynhds *dynhds,
                           const char *name, const char *value);
 
@@ -167,7 +126,7 @@
 
 /**
  * Add the headers to the given `dynbuf` in HTTP/1.1 format with
- * cr+lf line endings. Will NOT output a last empty line.
+ * CR+LF line endings. Does NOT output a last empty line.
  */
 CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf);
 
diff --git a/Utilities/cmcurl/lib/easy.c b/Utilities/cmcurl/lib/easy.c
index d05674e..a472d6d 100644
--- a/Utilities/cmcurl/lib/easy.c
+++ b/Utilities/cmcurl/lib/easy.c
@@ -779,8 +779,9 @@
   if(multi->in_callback)
     return CURLE_RECURSIVE_API_CALL;
 
-  /* Copy the MAXCONNECTS option to the multi handle */
+  /* Copy relevant easy options to the multi handle */
   curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, (long)data->set.maxconnects);
+  curl_multi_setopt(multi, CURLMOPT_QUICK_EXIT, (long)data->set.quick_exit);
 
   data->multi_easy = NULL; /* pretend it does not exist */
   mresult = curl_multi_add_handle(multi, data);
@@ -814,9 +815,9 @@
  * curl_easy_perform() is the external interface that performs a blocking
  * transfer as previously setup.
  */
-CURLcode curl_easy_perform(CURL *data)
+CURLcode curl_easy_perform(CURL *curl)
 {
-  return easy_perform(data, FALSE);
+  return easy_perform(curl, FALSE);
 }
 
 #ifdef DEBUGBUILD
@@ -824,9 +825,9 @@
  * curl_easy_perform_ev() is the external interface that performs a blocking
  * transfer using the event-based API internally.
  */
-CURLcode curl_easy_perform_ev(struct Curl_easy *data)
+CURLcode curl_easy_perform_ev(struct Curl_easy *easy)
 {
-  return easy_perform(data, TRUE);
+  return easy_perform(easy, TRUE);
 }
 #endif
 
@@ -834,9 +835,9 @@
  * curl_easy_cleanup() is the external interface to cleaning/freeing the given
  * easy handle.
  */
-void curl_easy_cleanup(CURL *ptr)
+void curl_easy_cleanup(CURL *curl)
 {
-  struct Curl_easy *data = ptr;
+  struct Curl_easy *data = curl;
   if(GOOD_EASY_HANDLE(data)) {
     struct Curl_sigpipe_ctx sigpipe_ctx;
     sigpipe_ignore(data, &sigpipe_ctx);
@@ -850,9 +851,9 @@
  * information from a performed transfer and similar.
  */
 #undef curl_easy_getinfo
-CURLcode curl_easy_getinfo(CURL *easy, CURLINFO info, ...)
+CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...)
 {
-  struct Curl_easy *data = easy;
+  struct Curl_easy *data = curl;
   va_list arg;
   void *paramp;
   CURLcode result;
@@ -938,7 +939,7 @@
 static void dupeasy_meta_freeentry(void *p)
 {
   (void)p;
-  /* Will always be FALSE. Cannot use a 0 assert here since compilers
+  /* Always FALSE. Cannot use a 0 assert here since compilers
    * are not in agreement if they then want a NORETURN attribute or
    * not. *sigh* */
   DEBUGASSERT(p == NULL);
@@ -949,9 +950,9 @@
  * given input easy handle. The returned handle will be a new working handle
  * with all options set exactly as the input source handle.
  */
-CURL *curl_easy_duphandle(CURL *d)
+CURL *curl_easy_duphandle(CURL *curl)
 {
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   struct Curl_easy *outcurl = NULL;
 
   if(!GOOD_EASY_HANDLE(data))
@@ -1080,9 +1081,9 @@
  * curl_easy_reset() is an external interface that allows an app to re-
  * initialize a session handle to the default values.
  */
-void curl_easy_reset(CURL *d)
+void curl_easy_reset(CURL *curl)
 {
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   if(!GOOD_EASY_HANDLE(data))
     return;
 
@@ -1091,10 +1092,6 @@
 
   /* clear all meta data */
   Curl_meta_reset(data);
-  /* clear any resolve data */
-  Curl_async_shutdown(data);
-  Curl_resolv_unlink(data, &data->state.dns[0]);
-  Curl_resolv_unlink(data, &data->state.dns[1]);
   /* zero out UserDefined data: */
   Curl_freeset(data);
   memset(&data->set, 0, sizeof(struct UserDefined));
@@ -1133,12 +1130,12 @@
  * NOTE: This is one of few API functions that are allowed to be called from
  * within a callback.
  */
-CURLcode curl_easy_pause(CURL *d, int action)
+CURLcode curl_easy_pause(CURL *curl, int action)
 {
   CURLcode result = CURLE_OK;
   bool recursive = FALSE;
   bool changed = FALSE;
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   bool recv_paused, recv_paused_new;
   bool send_paused, send_paused_new;
 
@@ -1157,12 +1154,14 @@
   if((send_paused != send_paused_new) ||
      (send_paused_new != Curl_creader_is_paused(data))) {
     changed = TRUE;
-    result = Curl_1st_err(result, Curl_xfer_pause_send(data, send_paused_new));
+    result = Curl_1st_fatal(
+      result, Curl_xfer_pause_send(data, send_paused_new));
   }
 
   if(recv_paused != recv_paused_new) {
     changed = TRUE;
-    result = Curl_1st_err(result, Curl_xfer_pause_recv(data, recv_paused_new));
+    result = Curl_1st_fatal(
+      result, Curl_xfer_pause_recv(data, recv_paused_new));
   }
 
   /* If not completely pausing both directions now, run again in any case. */
@@ -1221,11 +1220,11 @@
  * curl_easy_perform() with CURLOPT_CONNECT_ONLY option.
  * Returns CURLE_OK on success, error code on error.
  */
-CURLcode curl_easy_recv(CURL *d, void *buffer, size_t buflen, size_t *n)
+CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n)
 {
   CURLcode result;
   struct connectdata *c;
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
 
   if(!GOOD_EASY_HANDLE(data))
     return CURLE_BAD_FUNCTION_ARGUMENT;
@@ -1299,11 +1298,12 @@
  * Sends data over the connected socket. Use after successful
  * curl_easy_perform() with CURLOPT_CONNECT_ONLY option.
  */
-CURLcode curl_easy_send(CURL *d, const void *buffer, size_t buflen, size_t *n)
+CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen,
+                        size_t *n)
 {
   size_t written = 0;
   CURLcode result;
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   if(!GOOD_EASY_HANDLE(data))
     return CURLE_BAD_FUNCTION_ARGUMENT;
   if(Curl_is_in_callback(data))
@@ -1317,9 +1317,9 @@
 /*
  * Performs connection upkeep for the given session handle.
  */
-CURLcode curl_easy_upkeep(CURL *d)
+CURLcode curl_easy_upkeep(CURL *curl)
 {
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   /* Verify that we got an easy handle we can work with. */
   if(!GOOD_EASY_HANDLE(data))
     return CURLE_BAD_FUNCTION_ARGUMENT;
@@ -1331,18 +1331,20 @@
   return Curl_cpool_upkeep(data);
 }
 
-CURLcode curl_easy_ssls_import(CURL *d, const char *session_key,
+CURLcode curl_easy_ssls_import(CURL *curl, const char *session_key,
                                const unsigned char *shmac, size_t shmac_len,
                                const unsigned char *sdata, size_t sdata_len)
 {
 #if defined(USE_SSL) && defined(USE_SSLS_EXPORT)
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   if(!GOOD_EASY_HANDLE(data))
     return CURLE_BAD_FUNCTION_ARGUMENT;
+  if(Curl_is_in_callback(data) || Curl_ssl_scache_is_locked(data))
+    return CURLE_RECURSIVE_API_CALL;
   return Curl_ssl_session_import(data, session_key,
                                  shmac, shmac_len, sdata, sdata_len);
 #else
-  (void)d;
+  (void)curl;
   (void)session_key;
   (void)shmac;
   (void)shmac_len;
@@ -1352,17 +1354,19 @@
 #endif
 }
 
-CURLcode curl_easy_ssls_export(CURL *d,
+CURLcode curl_easy_ssls_export(CURL *curl,
                                curl_ssls_export_cb *export_fn,
                                void *userptr)
 {
 #if defined(USE_SSL) && defined(USE_SSLS_EXPORT)
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   if(!GOOD_EASY_HANDLE(data))
     return CURLE_BAD_FUNCTION_ARGUMENT;
+  if(Curl_is_in_callback(data) || Curl_ssl_scache_is_locked(data))
+    return CURLE_RECURSIVE_API_CALL;
   return Curl_ssl_session_export(data, export_fn, userptr);
 #else
-  (void)d;
+  (void)curl;
   (void)export_fn;
   (void)userptr;
   return CURLE_NOT_BUILT_IN;
diff --git a/Utilities/cmcurl/lib/easy_lock.h b/Utilities/cmcurl/lib/easy_lock.h
index cefb01d..b8f916f 100644
--- a/Utilities/cmcurl/lib/easy_lock.h
+++ b/Utilities/cmcurl/lib/easy_lock.h
@@ -49,8 +49,8 @@
    __has_builtin() function, so override it. */
 
 /* if GCC on i386/x86_64 or if the built-in is present */
-#if (defined(__GNUC__) && !defined(__clang__)) &&     \
-    (defined(__i386__) || defined(__x86_64__))
+#if (defined(__GNUC__) && !defined(__clang__)) && \
+  (defined(__i386__) || defined(__x86_64__))
 #define HAVE_BUILTIN_IA32_PAUSE
 #elif defined(__has_builtin)  /* Keep this PP check separate from others */
 #if __has_builtin(__builtin_ia32_pause)
@@ -84,9 +84,7 @@
   atomic_store_explicit(lock, false, memory_order_release);
 }
 
-#elif defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
-
-#include <pthread.h>
+#elif defined(HAVE_THREADS_POSIX)
 
 #define curl_simple_lock           pthread_mutex_t
 #define CURL_SIMPLE_LOCK_INIT      PTHREAD_MUTEX_INITIALIZER
diff --git a/Utilities/cmcurl/lib/easygetopt.c b/Utilities/cmcurl/lib/easygetopt.c
index 2685ada..533d7e3 100644
--- a/Utilities/cmcurl/lib/easygetopt.c
+++ b/Utilities/cmcurl/lib/easygetopt.c
@@ -62,8 +62,8 @@
 }
 
 /* Iterates over available options */
-const struct curl_easyoption *
-curl_easy_option_next(const struct curl_easyoption *prev)
+const struct curl_easyoption *curl_easy_option_next(
+  const struct curl_easyoption *prev)
 {
   if(prev && prev->name) {
     prev++;
@@ -88,8 +88,8 @@
   return NULL;
 }
 
-const struct curl_easyoption *
-curl_easy_option_next(const struct curl_easyoption *prev)
+const struct curl_easyoption *curl_easy_option_next(
+  const struct curl_easyoption *prev)
 {
   (void)prev;
   return NULL;
diff --git a/Utilities/cmcurl/lib/escape.c b/Utilities/cmcurl/lib/escape.c
index be44e97..4aff583 100644
--- a/Utilities/cmcurl/lib/escape.c
+++ b/Utilities/cmcurl/lib/escape.c
@@ -33,9 +33,9 @@
 #include "curl_printf.h"
 
 /* for ABI-compatibility with previous versions */
-char *curl_escape(const char *string, int inlength)
+char *curl_escape(const char *string, int length)
 {
-  return curl_easy_escape(NULL, string, inlength);
+  return curl_easy_escape(NULL, string, length);
 }
 
 /* for ABI-compatibility with previous versions */
@@ -47,25 +47,25 @@
 /* Escapes for URL the given unescaped string of given length.
  * 'data' is ignored since 7.82.0.
  */
-char *curl_easy_escape(CURL *data, const char *string, int inlength)
+char *curl_easy_escape(CURL *curl, const char *string, int length)
 {
-  size_t length;
+  size_t len;
   struct dynbuf d;
-  (void)data;
+  (void)curl;
 
-  if(!string || (inlength < 0))
+  if(!string || (length < 0))
     return NULL;
 
-  length = (inlength ? (size_t)inlength : strlen(string));
-  if(!length)
+  len = (length ? (size_t)length : strlen(string));
+  if(!len)
     return curlx_strdup("");
 
-  if(length > SIZE_MAX / 16)
+  if(len > SIZE_MAX / 16)
     return NULL;
 
-  curlx_dyn_init(&d, (length * 3) + 1);
+  curlx_dyn_init(&d, (len * 3) + 1);
 
-  while(length--) {
+  while(len--) {
     /* treat the characters unsigned */
     unsigned char in = (unsigned char)*string++;
 
@@ -138,7 +138,7 @@
 
     if(((ctrl == REJECT_CTRL) && (in < 0x20)) ||
        ((ctrl == REJECT_ZERO) && (in == 0))) {
-      Curl_safefree(*ostring);
+      curlx_safefree(*ostring);
       return CURLE_URL_MALFORMAT;
     }
 
@@ -160,24 +160,25 @@
  * If olen == NULL, no output length is stored.
  * 'data' is ignored since 7.82.0.
  */
-char *curl_easy_unescape(CURL *data, const char *string, int length, int *olen)
+char *curl_easy_unescape(CURL *curl, const char *string, int inlength,
+                         int *outlength)
 {
   char *str = NULL;
-  (void)data;
-  if(string && (length >= 0)) {
-    size_t inputlen = (size_t)length;
+  (void)curl;
+  if(string && (inlength >= 0)) {
+    size_t inputlen = (size_t)inlength;
     size_t outputlen;
     CURLcode res = Curl_urldecode(string, inputlen, &str, &outputlen,
                                   REJECT_NADA);
     if(res)
       return NULL;
 
-    if(olen) {
+    if(outlength) {
       if(outputlen <= (size_t)INT_MAX)
-        *olen = curlx_uztosi(outputlen);
+        *outlength = curlx_uztosi(outputlen);
       else
         /* too large to return in an int, fail! */
-        Curl_safefree(str);
+        curlx_safefree(str);
     }
   }
   return str;
diff --git a/Utilities/cmcurl/lib/fake_addrinfo.c b/Utilities/cmcurl/lib/fake_addrinfo.c
index d8b1398..5a89202 100644
--- a/Utilities/cmcurl/lib/fake_addrinfo.c
+++ b/Utilities/cmcurl/lib/fake_addrinfo.c
@@ -41,16 +41,16 @@
 }
 
 struct context {
-  struct ares_addrinfo *result;
+  struct ares_addrinfo *addr;
 };
 
 static void async_addrinfo_cb(void *userp, int status, int timeouts,
-                              struct ares_addrinfo *result)
+                              struct ares_addrinfo *addr)
 {
   struct context *ctx = (struct context *)userp;
   (void)timeouts;
   if(ARES_SUCCESS == status) {
-    ctx->result = result;
+    ctx->addr = addr;
   }
 }
 
@@ -184,11 +184,11 @@
   /* Wait until no more requests are left to be processed */
   ares_queue_wait_empty(channel, -1);
 
-  if(ctx.result) {
+  if(ctx.addr) {
     /* convert the c-ares version */
-    *res = mk_getaddrinfo(ctx.result);
+    *res = mk_getaddrinfo(ctx.addr);
     /* free the old */
-    ares_freeaddrinfo(ctx.result);
+    ares_freeaddrinfo(ctx.addr);
   }
   else
     rc = EAI_NONAME; /* got nothing */
diff --git a/Utilities/cmcurl/lib/file.c b/Utilities/cmcurl/lib/file.c
index 1c3f663..fff8fee 100644
--- a/Utilities/cmcurl/lib/file.c
+++ b/Utilities/cmcurl/lib/file.c
@@ -84,7 +84,7 @@
 
 static void file_cleanup(struct FILEPROTO *file)
 {
-  Curl_safefree(file->freepath);
+  curlx_safefree(file->freepath);
   file->path = NULL;
   if(file->fd != -1) {
     curlx_close(file->fd);
@@ -108,6 +108,8 @@
   (void)conn;
   /* allocate the FILE specific struct */
   filep = curlx_calloc(1, sizeof(*filep));
+  if(filep)
+    filep->fd = -1;
   if(!filep ||
      Curl_meta_set(data, CURL_META_FILE_EASY, filep, file_easy_dtor))
     return CURLE_OUT_OF_MEMORY;
@@ -191,7 +193,7 @@
     if(actual_path[i] == '/')
       actual_path[i] = '\\';
     else if(!actual_path[i]) { /* binary zero */
-      Curl_safefree(real_path);
+      curlx_safefree(real_path);
       return CURLE_URL_MALFORMAT;
     }
 
@@ -200,7 +202,7 @@
 #else
   if(memchr(real_path, 0, real_path_len)) {
     /* binary zeroes indicate foul play */
-    Curl_safefree(real_path);
+    curlx_safefree(real_path);
     return CURLE_URL_MALFORMAT;
   }
 
@@ -598,7 +600,7 @@
   return result;
 }
 
-static const struct Curl_protocol Curl_protocol_file = {
+const struct Curl_protocol Curl_protocol_file = {
   file_setup_connection,                /* setup_connection */
   file_do,                              /* do_it */
   file_done,                            /* done */
@@ -613,25 +615,9 @@
   file_disconnect,                      /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
 
 #endif
-
-/*
- * FILE scheme handler.
- */
-const struct Curl_scheme Curl_scheme_file = {
-  "file",                               /* scheme */
-#ifdef CURL_DISABLE_FILE
-  ZERO_NULL,
-#else
-  &Curl_protocol_file,
-#endif
-  CURLPROTO_FILE,                       /* protocol */
-  CURLPROTO_FILE,                       /* family */
-  PROTOPT_NONETWORK | PROTOPT_NOURLQUERY, /* flags */
-  0                                     /* defport */
-};
diff --git a/Utilities/cmcurl/lib/file.h b/Utilities/cmcurl/lib/file.h
index a306544..0aa411b 100644
--- a/Utilities/cmcurl/lib/file.h
+++ b/Utilities/cmcurl/lib/file.h
@@ -23,6 +23,8 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_file;
+#ifndef CURL_DISABLE_FILE
+extern const struct Curl_protocol Curl_protocol_file;
+#endif
 
 #endif /* HEADER_CURL_FILE_H */
diff --git a/Utilities/cmcurl/lib/formdata.c b/Utilities/cmcurl/lib/formdata.c
index 3770d28..3619c15 100644
--- a/Utilities/cmcurl/lib/formdata.c
+++ b/Utilities/cmcurl/lib/formdata.c
@@ -609,11 +609,11 @@
                           struct curl_httppost **last_post, ...)
 {
   va_list arg;
-  CURLFORMcode result;
+  CURLFORMcode form;
   va_start(arg, last_post);
-  result = FormAdd(httppost, last_post, arg);
+  form = FormAdd(httppost, last_post, arg);
   va_end(arg);
-  return result;
+  return form;
 }
 
 /*
@@ -691,16 +691,16 @@
 static CURLcode setname(curl_mimepart *part, const char *name, size_t len)
 {
   char *zname;
-  CURLcode res;
+  CURLcode result;
 
   if(!name || !len)
     return curl_mime_name(part, name);
   zname = curlx_memdup0(name, len);
   if(!zname)
     return CURLE_OUT_OF_MEMORY;
-  res = curl_mime_name(part, zname);
+  result = curl_mime_name(part, zname);
   curlx_free(zname);
-  return res;
+  return result;
 }
 
 /*
diff --git a/Utilities/cmcurl/lib/ftp-int.h b/Utilities/cmcurl/lib/ftp-int.h
new file mode 100644
index 0000000..68d26f3
--- /dev/null
+++ b/Utilities/cmcurl/lib/ftp-int.h
@@ -0,0 +1,160 @@
+#ifndef HEADER_CURL_FTP_INT_H
+#define HEADER_CURL_FTP_INT_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 "pingpong.h"
+
+#ifndef CURL_DISABLE_FTP
+
+/****************************************************************************
+ * FTP unique setup
+ ***************************************************************************/
+enum {
+  FTP_STOP,    /* do nothing state, stops the state machine */
+  FTP_WAIT220, /* waiting for the initial 220 response immediately after
+                  a connect */
+  FTP_AUTH,
+  FTP_USER,
+  FTP_PASS,
+  FTP_ACCT,
+  FTP_PBSZ,
+  FTP_PROT,
+  FTP_CCC,
+  FTP_PWD,
+  FTP_SYST,
+  FTP_NAMEFMT,
+  FTP_QUOTE, /* waiting for a response to a command sent in a quote list */
+  FTP_RETR_PREQUOTE,
+  FTP_STOR_PREQUOTE,
+  FTP_LIST_PREQUOTE,
+  FTP_POSTQUOTE,
+  FTP_CWD,  /* change directory */
+  FTP_MKD,  /* if the directory did not exist */
+  FTP_MDTM, /* to figure out the datestamp */
+  FTP_TYPE, /* to set type when doing a head-like request */
+  FTP_LIST_TYPE, /* set type when about to do a directory list */
+  FTP_RETR_LIST_TYPE,
+  FTP_RETR_TYPE, /* set type when about to RETR a file */
+  FTP_STOR_TYPE, /* set type when about to STOR a file */
+  FTP_SIZE, /* get the remote file's size for head-like request */
+  FTP_RETR_SIZE, /* get the remote file's size for RETR */
+  FTP_STOR_SIZE, /* get the size for STOR */
+  FTP_REST, /* when used to check if the server supports it in head-like */
+  FTP_RETR_REST, /* when asking for "resume" in for RETR */
+  FTP_PORT, /* generic state for PORT, LPRT and EPRT, check count1 */
+  FTP_PRET, /* generic state for PRET RETR, PRET STOR and PRET LIST/NLST */
+  FTP_PASV, /* generic state for PASV and EPSV, check count1 */
+  FTP_LIST, /* generic state for LIST, NLST or a custom list command */
+  FTP_RETR,
+  FTP_STOR, /* generic state for STOR and APPE */
+  FTP_QUIT,
+  FTP_LAST  /* never used */
+};
+typedef unsigned char ftpstate; /* use the enum values */
+
+struct ftp_parselist_data; /* defined later in ftplistparser.c */
+
+struct ftp_wc {
+  struct ftp_parselist_data *parser;
+
+  struct {
+    curl_write_callback write_function;
+    FILE *file_descriptor;
+  } backup;
+};
+
+/* This FTP struct is used in the Curl_easy. All FTP data that is
+   connection-oriented must be in FTP_conn to properly deal with the fact that
+   perhaps the Curl_easy is changed between the times the connection is
+   used. */
+struct FTP {
+  char *path;    /* points to the urlpieces struct field */
+  char *pathalloc; /* if non-NULL a pointer to an allocated path */
+
+  /* transfer a file/body or not, done as a typedefed enum to make debuggers
+     display the full symbol and not the numerical value */
+  curl_pp_transfer transfer;
+  curl_off_t downloadsize;
+};
+
+/* one struct entry for each path component (of 'rawpath') */
+struct pathcomp {
+  int start; /* start column */
+  int len;   /* length in bytes */
+};
+
+/* ftp_conn is used for struct connection-oriented data in the connectdata
+   struct */
+struct ftp_conn {
+  struct pingpong pp;
+  char *account;
+  char *alternative_to_user;
+  char *entrypath; /* the PWD reply when we logged on */
+  const char *file; /* url-decoded filename (or path), points into rawpath */
+  char *rawpath; /* URL decoded, allocated, version of the path */
+  struct pathcomp *dirs; /* allocated array for path components */
+  char *prevpath;   /* url-decoded conn->path from the previous transfer */
+  char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a
+                        and others (A/I or zero) */
+  char *server_os;     /* The target server operating system. */
+  curl_off_t known_filesize; /* file size is different from -1, if wildcard
+                                LIST parsing was done and wc_statemach set
+                                it */
+  int count1; /* general purpose counter for the state machine */
+  int count2; /* general purpose counter for the state machine */
+  int count3; /* general purpose counter for the state machine */
+  unsigned short dirdepth;  /* number of entries used in the 'dirs' array,
+                               < FTP_MAX_DIR_DEPTH */
+  unsigned short cwdcount;  /* number of CWD commands issued,
+                               < FTP_MAX_DIR_DEPTH */
+  unsigned char state; /* (ftpstate enum) always use ftp.c:state() to change
+                          state! */
+  unsigned char use_ssl;   /* if AUTH TLS is to be attempted etc, for FTP or
+                              IMAP or POP3 or others! (type: curl_usessl)*/
+  unsigned char ccc;       /* ccc level for this connection */
+  BIT(ftp_trying_alternative);
+  BIT(dont_check);  /* Set to TRUE to prevent the final (post-transfer)
+                       file size and 226/250 status check. It should still
+                       read the line, ignore the result. */
+  BIT(ctl_valid);   /* Tells Curl_ftp_quit() whether or not to do anything. If
+                       the connection has timed out or been closed, this
+                       should be FALSE when it gets to Curl_ftp_quit() */
+  BIT(cwddone);     /* if it has been determined that the proper CWD combo
+                       already has been done */
+  BIT(cwdfail);     /* set TRUE if a CWD command fails, as then we must prevent
+                       caching the current directory */
+  BIT(wait_data_conn); /* this is set TRUE if data connection is waited */
+  BIT(shutdown);    /* connection is being shutdown, e.g. QUIT */
+};
+
+/* meta key for storing `struct FTP` as easy meta data */
+#define CURL_META_FTP_EASY   "meta:proto:ftp:easy"
+/* meta key for storing `struct ftp_conn` as connection meta data */
+#define CURL_META_FTP_CONN   "meta:proto:ftp:conn"
+
+#endif /* CURL_DISABLE_FTP */
+
+#endif /* HEADER_CURL_FTP_INT_H */
diff --git a/Utilities/cmcurl/lib/ftp.c b/Utilities/cmcurl/lib/ftp.c
index 5b1151e..61a78c2 100644
--- a/Utilities/cmcurl/lib/ftp.c
+++ b/Utilities/cmcurl/lib/ftp.c
@@ -49,6 +49,7 @@
 #include "transfer.h"
 #include "escape.h"
 #include "ftp.h"
+#include "ftp-int.h"
 #include "ftplistparser.h"
 #include "curl_range.h"
 #include "strcase.h"
@@ -67,6 +68,7 @@
 #include "curlx/strdup.h"
 #include "curlx/strerr.h"
 #include "curlx/strparse.h"
+#include "curl_ctype.h"
 
 #ifndef NI_MAXHOST
 #define NI_MAXHOST 1025
@@ -173,9 +175,9 @@
 
 static void freedirs(struct ftp_conn *ftpc)
 {
-  Curl_safefree(ftpc->dirs);
+  curlx_safefree(ftpc->dirs);
   ftpc->dirdepth = 0;
-  Curl_safefree(ftpc->rawpath);
+  curlx_safefree(ftpc->rawpath);
   ftpc->file = NULL;
 }
 
@@ -209,7 +211,7 @@
   const char *slashPos = NULL;
   const char *fileName = NULL;
   CURLcode result = CURLE_OK;
-  const char *rawPath = NULL; /* url-decoded "raw" path */
+  const char *rawPath = NULL; /* URL-decoded "raw" path */
   size_t pathLen = 0;
 
   ftpc->ctl_valid = FALSE;
@@ -217,7 +219,7 @@
 
   if(ftpc->rawpath)
     freedirs(ftpc);
-  /* url-decode ftp path before further evaluation */
+  /* URL-decode ftp path before further evaluation */
   result = Curl_urldecode(ftp->path, 0, &ftpc->rawpath, &pathLen, REJECT_CTRL);
   if(result) {
     failf(data, "path contains control characters");
@@ -232,8 +234,8 @@
       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.
-            So we can safely leave filename as NULL here and use it as a
+            a file. In other words, never for directory operations,
+            so we can safely leave filename as NULL here and use it as a
             argument in dir/file decisions.
     */
     break;
@@ -305,7 +307,7 @@
     ftpc->file = fileName;
   else
     ftpc->file = NULL; /* instead of point to a zero byte,
-                            we make it a NULL pointer */
+                          we make it a NULL pointer */
 
   if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) {
     /* We need a filename when uploading. Return error! */
@@ -657,7 +659,7 @@
 
   while(!*ftpcodep && !result) {
     /* check and reset timeout value every lap */
-    timediff_t timeout = Curl_pp_state_timeout(data, pp);
+    timediff_t timeout = Curl_pp_state_timeleft_ms(data, pp);
     timediff_t interval_ms;
 
     if(timeout <= 0) {
@@ -677,7 +679,7 @@
      * A caution here is that the ftp_readresp() function has a cache that may
      * contain pieces of a response from the previous invoke and we need to
      * make sure we do not wait for input while there is unhandled data in
-     * that cache. But also, if the cache is there, we call ftp_readresp() and
+     * that cache. Also, if the cache is there, we call ftp_readresp() and
      * the cache was not good enough to continue we must not busy-loop around
      * this function.
      *
@@ -730,7 +732,7 @@
   } /* while there is buffer left and loop is requested */
 
   pp->pending_resp = FALSE;
-  CURL_TRC_FTP(data, "getftpresponse -> result=%d, nread=%zd, ftpcode=%d",
+  CURL_TRC_FTP(data, "getftpresponse -> result=%d, nread=%zu, ftpcode=%d",
                result, *nreadp, *ftpcodep);
 
   return result;
@@ -1060,8 +1062,9 @@
   CURLcode result;
 
   *resp = NULL;
-  result = Curl_resolv_blocking(data, host, 0, conn->ip_version,
-                                dns_entryp);
+  result = Curl_resolv_blocking(
+    data, Curl_resolv_dns_queries(data, conn->ip_version),
+    host, 0, Curl_conn_get_transport(data, conn), dns_entryp);
   if(result)
     failf(data, "failed to resolve the address provided to PORT: %s", host);
   else {
@@ -1385,7 +1388,7 @@
   /* cleanup */
 
   if(dns_entry)
-    Curl_resolv_unlink(data, &dns_entry);
+    Curl_dns_entry_unlink(data, &dns_entry);
   if(result) {
     ftp_state(data, ftpc, FTP_STOP);
   }
@@ -1559,7 +1562,7 @@
   char *cmd;
 
   if((data->set.ftp_filemethod == FTPFILE_NOCWD) && ftp->path) {
-    /* url-decode before evaluation: e.g. paths starting/ending with %2f */
+    /* URL-decode before evaluation: e.g. paths starting/ending with %2f */
     const char *rawPath = ftpc->rawpath;
     const char *slashPos = strrchr(rawPath, '/');
     if(slashPos) {
@@ -1683,17 +1686,17 @@
 
   if((data->state.resume_from && !sizechecked) ||
      ((data->state.resume_from > 0) && sizechecked)) {
-    /* we are about to continue the uploading of a file */
-    /* 1. get already existing file's size. We use the SIZE command for this
-       which may not exist in the server!  The SIZE command is not in
-       RFC959. */
+    /* we are about to continue the uploading of a file
+       1. get already existing file's size. We use the SIZE command for this
+          which may not exist in the server!  The SIZE command is not in
+          RFC959.
 
-    /* 2. This used to set REST. But since we can do append, we issue no
-       another ftp command. Skip the source file offset and APPEND the rest on
-       the file instead */
+       2. This used to set REST, but since we can do append, we issue no
+          another ftp command. Skip the source file offset and APPEND the rest
+          on the file instead
 
-    /* 3. pass file-size number of bytes in the source file */
-    /* 4. lower the infilesize counter */
+       3. pass file-size number of bytes in the source file
+       4. lower the infilesize counter */
     /* => transfer as usual */
     int seekerr = CURL_SEEKFUNC_OK;
 
@@ -1942,8 +1945,8 @@
                behavior.
 
                In addition: asking for the size for 'TYPE A' transfers is not
-               constructive since servers do not report the converted size. So
-               skip it.
+               constructive since servers do not report the converted size.
+               Thus, skip it.
             */
             result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file);
             if(!result)
@@ -2159,9 +2162,10 @@
     if(result)
       goto error;
 
-    (void)Curl_resolv_blocking(data, host_name, ipquad.remote_port,
-                               is_ipv6 ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_V4,
-                               &dns);
+    (void)Curl_resolv_blocking(
+      data, is_ipv6 ? CURL_DNSQ_AAAA : CURL_DNSQ_A,
+      host_name, ipquad.remote_port, Curl_conn_get_transport(data, conn),
+      &dns);
     /* we connect to the proxy's port */
     connectport = (unsigned short)ipquad.remote_port;
 
@@ -2185,7 +2189,9 @@
         goto error;
     }
 
-    (void)Curl_resolv_blocking(data, newhost, newport, conn->ip_version, &dns);
+    (void)Curl_resolv_blocking(
+      data, Curl_resolv_dns_queries(data, conn->ip_version),
+      newhost, newport, Curl_conn_get_transport(data, conn), &dns);
     connectport = newport; /* we connect to the remote port */
 
     if(!dns) {
@@ -2195,6 +2201,12 @@
     }
   }
 
+  DEBUGASSERT(newhost);
+  curlx_free(conn->secondaryhostname);
+  conn->secondary_port = newport;
+  conn->secondaryhostname = newhost;
+  newhost = NULL;
+
   result = Curl_conn_setup(data, conn, SECONDARYSOCKET, dns,
                            conn->bits.ftp_use_data_ssl ?
                            CURL_CF_SSL_ENABLE : CURL_CF_SSL_DISABLE);
@@ -2202,10 +2214,8 @@
   if(result) {
     if((result != CURLE_OUT_OF_MEMORY) &&
        (ftpc->count1 == 0) && (ftpcode == 229)) {
-      curlx_free(newhost);
-      return ftp_epsv_disable(data, ftpc, conn);
+      result = ftp_epsv_disable(data, ftpc, conn);
     }
-
     goto error;
   }
 
@@ -2222,22 +2232,16 @@
      */
     char buf[256];
     Curl_printable_address(dns->addr, buf, sizeof(buf));
-    infof(data, "Connecting to %s (%s) port %d", newhost, buf, connectport);
+    infof(data, "Connecting to %s (%s) port %d",
+          conn->secondaryhostname, buf, connectport);
   }
 #endif
 
-  curlx_free(conn->secondaryhostname);
-  conn->secondary_port = newport;
-  conn->secondaryhostname = curlx_strdup(newhost);
-  if(!conn->secondaryhostname) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto error;
-  }
-
   conn->bits.do_more = TRUE;
   ftp_state(data, ftpc, FTP_STOP); /* this phase is completed */
 
 error:
+  Curl_dns_entry_unlink(data, &dns);
   curlx_free(newhost);
   return result;
 }
@@ -2382,10 +2386,10 @@
       else if((data->state.list_only || !ftpc->file) &&
               !(data->set.prequote)) {
         /* The specified path ends with a slash, and therefore we think this
-           is a directory that is requested, use LIST. But before that we
+           is a directory that is requested, use LIST. Before that, we also
            need to set ASCII transfer mode. */
 
-        /* But only if a body transfer was requested. */
+        /* Only if a body transfer was requested. */
         if(ftp->transfer == PPTRANSFER_BODY) {
           result = ftp_nb_type(data, ftpc, ftp, TRUE, FTP_LIST_TYPE);
           if(result)
@@ -2490,24 +2494,32 @@
   return result;
 }
 
-static int twodigit(const char *p)
+/* return TRUE on error, FALSE on success */
+static bool twodigit(const char *p, int *val)
 {
-  return ((p[0] - '0') * 10) + (p[1] - '0');
+  if(!ISDIGIT(p[0]) || !ISDIGIT(p[1]))
+    return TRUE;
+  /* curlx_hexval() works fine here since we make sure it is decimal above */
+  *val = (curlx_hexval(p[0]) * 10) + curlx_hexval(p[1]);
+  return FALSE;
 }
 
-static bool ftp_213_date(const char *p, int *year, int *month, int *day,
-                         int *hour, int *minute, int *second)
+/*
+ * @unittest 1668
+ */
+UNITTEST bool ftp_213_date(const char *p, int *year, int *month, int *day,
+                           int *hour, int *minute, int *second);
+UNITTEST bool ftp_213_date(const char *p, int *year, int *month, int *day,
+                           int *hour, int *minute, int *second)
 {
-  size_t len = strlen(p);
-  if(len < 14)
+  int century;
+  if((strlen(p) < 14) || twodigit(&p[0], &century) || twodigit(&p[2], year) ||
+     twodigit(&p[4], month) || twodigit(&p[6], day) ||
+     twodigit(&p[8], hour) || twodigit(&p[10], minute) ||
+     twodigit(&p[12], second))
     return FALSE;
-  *year = (twodigit(&p[0]) * 100) + twodigit(&p[2]);
-  *month = twodigit(&p[4]);
-  *day = twodigit(&p[6]);
-  *hour = twodigit(&p[8]);
-  *minute = twodigit(&p[10]);
-  *second = twodigit(&p[12]);
 
+  *year += century * 100;
   if((*month > 12) || (*day > 31) || (*hour > 23) || (*minute > 59) ||
      (*second > 60))
     return FALSE;
@@ -2568,7 +2580,7 @@
     /* If we asked for a time of the file and we actually got one as well,
        we "emulate" an HTTP-style header in our output. */
 
-#if defined(__GNUC__) && (defined(__DJGPP__) || defined(__AMIGA__))
+#if defined(CURL_HAVE_DIAG) && (defined(__DJGPP__) || defined(__AMIGA__))
 #pragma GCC diagnostic push
 /* 'time_t' is unsigned in MSDOS and AmigaOS. Silence:
    warning: comparison of unsigned expression in '>= 0' is always true */
@@ -2576,7 +2588,7 @@
 #endif
     if(data->req.no_body && ftpc->file &&
        data->set.get_filetime && showtime) {
-#if defined(__GNUC__) && (defined(__DJGPP__) || defined(__AMIGA__))
+#if defined(CURL_HAVE_DIAG) && (defined(__DJGPP__) || defined(__AMIGA__))
 #pragma GCC diagnostic pop
 #endif
       char headerbuf[128];
@@ -3078,10 +3090,18 @@
             break; /* get out of this loop */
           }
         }
-        else
+        else {
+          if(ISCNTRL(*ptr)) {
+            /* control characters have no business in a path */
+            curlx_dyn_free(&out);
+            return CURLE_WEIRD_SERVER_REPLY;
+          }
           result = curlx_dyn_addn(&out, ptr, 1);
-        if(result)
+        }
+        if(result) {
+          curlx_dyn_free(&out);
           return result;
+        }
       }
     }
     if(entry_extracted) {
@@ -3709,7 +3729,7 @@
           if(data->set.ftp_filemethod == FTPFILE_NOCWD)
             pathLen = 0; /* relative path => working directory is FTP home */
           else
-            /* file is url-decoded */
+            /* file is URL-decoded */
             pathLen -= ftpc->file ? strlen(ftpc->file) : 0;
           ftpc->prevpath = curlx_memdup0(rawPath, pathLen);
         }
@@ -3867,12 +3887,12 @@
  * This is the actual DO function for FTP. Get a file/directory according to
  * the options previously setup.
  */
-static
-CURLcode ftp_perform(struct Curl_easy *data,
-                     struct ftp_conn *ftpc,
-                     struct FTP *ftp,
-                     bool *connected,  /* connect status after PASV / PORT */
-                     bool *dophase_done)
+static CURLcode ftp_perform(
+  struct Curl_easy *data,
+  struct ftp_conn *ftpc,
+  struct FTP *ftp,
+  bool *connected,  /* connect status after PASV / PORT */
+  bool *dophase_done)
 {
   /* this is FTP and no proxy */
   CURLcode result = CURLE_OK;
@@ -4006,7 +4026,7 @@
     Curl_ftp_parselist_data_free(&ftpwc->parser);
     curlx_free(ftpwc);
   }
-  Curl_safefree(wildcard->pattern);
+  curlx_safefree(wildcard->pattern);
   wildcard->dtor = ZERO_NULL;
   wildcard->ftpwc = NULL;
   return result;
@@ -4341,7 +4361,7 @@
   struct FTP *ftp = entry;
   (void)key;
   (void)klen;
-  Curl_safefree(ftp->pathalloc);
+  curlx_safefree(ftp->pathalloc);
   curlx_free(ftp);
 }
 
@@ -4351,11 +4371,11 @@
   (void)key;
   (void)klen;
   freedirs(ftpc);
-  Curl_safefree(ftpc->account);
-  Curl_safefree(ftpc->alternative_to_user);
-  Curl_safefree(ftpc->entrypath);
-  Curl_safefree(ftpc->prevpath);
-  Curl_safefree(ftpc->server_os);
+  curlx_safefree(ftpc->account);
+  curlx_safefree(ftpc->alternative_to_user);
+  curlx_safefree(ftpc->entrypath);
+  curlx_safefree(ftpc->prevpath);
+  curlx_safefree(ftpc->server_os);
   Curl_pp_disconnect(&ftpc->pp);
   curlx_free(ftpc);
 }
@@ -4418,7 +4438,7 @@
     ftpc->alternative_to_user =
       curlx_strdup(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]);
     if(!ftpc->alternative_to_user) {
-      Curl_safefree(ftpc->account);
+      curlx_safefree(ftpc->account);
       Curl_conn_meta_remove(conn, CURL_META_FTP_CONN);
       return CURLE_OUT_OF_MEMORY;
     }
@@ -4457,7 +4477,7 @@
 /*
  * FTP protocol.
  */
-static const struct Curl_protocol Curl_protocol_ftp = {
+const struct Curl_protocol Curl_protocol_ftp = {
   ftp_setup_connection,            /* setup_connection */
   ftp_do,                          /* do_it */
   ftp_done,                        /* done */
@@ -4472,46 +4492,9 @@
   ftp_disconnect,                  /* disconnect */
   ZERO_NULL,                       /* write_resp */
   ZERO_NULL,                       /* write_resp_hd */
-  ZERO_NULL,                       /* connection_check */
+  ZERO_NULL,                       /* connection_is_dead */
   ZERO_NULL,                       /* attach connection */
   ZERO_NULL,                       /* follow */
 };
 
 #endif /* CURL_DISABLE_FTP */
-
-/*
- * FTP protocol handler.
- */
-const struct Curl_scheme Curl_scheme_ftp = {
-  "ftp",                           /* scheme */
-#ifdef CURL_DISABLE_FTP
-  ZERO_NULL,
-#else
-  &Curl_protocol_ftp,
-#endif
-  CURLPROTO_FTP,                   /* protocol */
-  CURLPROTO_FTP,                   /* family */
-  PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD |
-  PROTOPT_NOURLQUERY | PROTOPT_PROXY_AS_HTTP |
-  PROTOPT_WILDCARD | PROTOPT_SSL_REUSE |
-  PROTOPT_CONN_REUSE, /* flags */
-  PORT_FTP,                        /* defport */
-};
-
-/*
- * FTPS protocol handler.
- */
-const struct Curl_scheme Curl_scheme_ftps = {
-  "ftps",                          /* scheme */
-#if defined(CURL_DISABLE_FTP) || !defined(USE_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_ftp,
-#endif
-  CURLPROTO_FTPS,                  /* protocol */
-  CURLPROTO_FTP,                   /* family */
-  PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION |
-  PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY | PROTOPT_WILDCARD |
-  PROTOPT_CONN_REUSE, /* flags */
-  PORT_FTPS,                       /* defport */
-};
diff --git a/Utilities/cmcurl/lib/ftp.h b/Utilities/cmcurl/lib/ftp.h
index 20b3600..257f595 100644
--- a/Utilities/cmcurl/lib/ftp.h
+++ b/Utilities/cmcurl/lib/ftp.h
@@ -25,73 +25,11 @@
  ***************************************************************************/
 #include "curl_setup.h"
 
-#include "pingpong.h"
-
-extern const struct Curl_scheme Curl_scheme_ftp;
-extern const struct Curl_scheme Curl_scheme_ftps;
-
 #ifndef CURL_DISABLE_FTP
+extern const struct Curl_protocol Curl_protocol_ftp;
+
 bool ftp_conns_match(struct connectdata *needle, struct connectdata *conn);
 
-#endif /* CURL_DISABLE_FTP */
-
-/****************************************************************************
- * FTP unique setup
- ***************************************************************************/
-enum {
-  FTP_STOP,    /* do nothing state, stops the state machine */
-  FTP_WAIT220, /* waiting for the initial 220 response immediately after
-                  a connect */
-  FTP_AUTH,
-  FTP_USER,
-  FTP_PASS,
-  FTP_ACCT,
-  FTP_PBSZ,
-  FTP_PROT,
-  FTP_CCC,
-  FTP_PWD,
-  FTP_SYST,
-  FTP_NAMEFMT,
-  FTP_QUOTE, /* waiting for a response to a command sent in a quote list */
-  FTP_RETR_PREQUOTE,
-  FTP_STOR_PREQUOTE,
-  FTP_LIST_PREQUOTE,
-  FTP_POSTQUOTE,
-  FTP_CWD,  /* change directory */
-  FTP_MKD,  /* if the directory did not exist */
-  FTP_MDTM, /* to figure out the datestamp */
-  FTP_TYPE, /* to set type when doing a head-like request */
-  FTP_LIST_TYPE, /* set type when about to do a directory list */
-  FTP_RETR_LIST_TYPE,
-  FTP_RETR_TYPE, /* set type when about to RETR a file */
-  FTP_STOR_TYPE, /* set type when about to STOR a file */
-  FTP_SIZE, /* get the remote file's size for head-like request */
-  FTP_RETR_SIZE, /* get the remote file's size for RETR */
-  FTP_STOR_SIZE, /* get the size for STOR */
-  FTP_REST, /* when used to check if the server supports it in head-like */
-  FTP_RETR_REST, /* when asking for "resume" in for RETR */
-  FTP_PORT, /* generic state for PORT, LPRT and EPRT, check count1 */
-  FTP_PRET, /* generic state for PRET RETR, PRET STOR and PRET LIST/NLST */
-  FTP_PASV, /* generic state for PASV and EPSV, check count1 */
-  FTP_LIST, /* generic state for LIST, NLST or a custom list command */
-  FTP_RETR,
-  FTP_STOR, /* generic state for STOR and APPE */
-  FTP_QUIT,
-  FTP_LAST  /* never used */
-};
-typedef unsigned char ftpstate; /* use the enum values */
-
-struct ftp_parselist_data; /* defined later in ftplistparser.c */
-
-struct ftp_wc {
-  struct ftp_parselist_data *parser;
-
-  struct {
-    curl_write_callback write_function;
-    FILE *file_descriptor;
-  } backup;
-};
-
 typedef enum {
   FTPFILE_MULTICWD  = 1, /* as defined by RFC1738 */
   FTPFILE_NOCWD     = 2, /* use SIZE / RETR / STOR on the full path */
@@ -99,74 +37,7 @@
                             file */
 } curl_ftpfile;
 
-/* This FTP struct is used in the Curl_easy. All FTP data that is
-   connection-oriented must be in FTP_conn to properly deal with the fact that
-   perhaps the Curl_easy is changed between the times the connection is
-   used. */
-struct FTP {
-  char *path;    /* points to the urlpieces struct field */
-  char *pathalloc; /* if non-NULL a pointer to an allocated path */
-
-  /* transfer a file/body or not, done as a typedefed enum to make debuggers
-     display the full symbol and not the numerical value */
-  curl_pp_transfer transfer;
-  curl_off_t downloadsize;
-};
-
-/* one struct entry for each path component (of 'rawpath') */
-struct pathcomp {
-  int start; /* start column */
-  int len;   /* length in bytes */
-};
-
-/* ftp_conn is used for struct connection-oriented data in the connectdata
-   struct */
-struct ftp_conn {
-  struct pingpong pp;
-  char *account;
-  char *alternative_to_user;
-  char *entrypath; /* the PWD reply when we logged on */
-  const char *file; /* url-decoded filename (or path), points into rawpath */
-  char *rawpath; /* URL decoded, allocated, version of the path */
-  struct pathcomp *dirs; /* allocated array for path components */
-  char *prevpath;   /* url-decoded conn->path from the previous transfer */
-  char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a
-                        and others (A/I or zero) */
-  char *server_os;     /* The target server operating system. */
-  curl_off_t known_filesize; /* file size is different from -1, if wildcard
-                                LIST parsing was done and wc_statemach set
-                                it */
-  int count1; /* general purpose counter for the state machine */
-  int count2; /* general purpose counter for the state machine */
-  int count3; /* general purpose counter for the state machine */
-  unsigned short dirdepth;  /* number of entries used in the 'dirs' array,
-                               < FTP_MAX_DIR_DEPTH */
-  unsigned short cwdcount;  /* number of CWD commands issued,
-                               < FTP_MAX_DIR_DEPTH */
-  unsigned char state; /* (ftpstate enum) always use ftp.c:state() to change
-                          state! */
-  unsigned char use_ssl;   /* if AUTH TLS is to be attempted etc, for FTP or
-                              IMAP or POP3 or others! (type: curl_usessl)*/
-  unsigned char ccc;       /* ccc level for this connection */
-  BIT(ftp_trying_alternative);
-  BIT(dont_check);  /* Set to TRUE to prevent the final (post-transfer)
-                       file size and 226/250 status check. It should still
-                       read the line, ignore the result. */
-  BIT(ctl_valid);   /* Tells Curl_ftp_quit() whether or not to do anything. If
-                       the connection has timed out or been closed, this
-                       should be FALSE when it gets to Curl_ftp_quit() */
-  BIT(cwddone);     /* if it has been determined that the proper CWD combo
-                       already has been done */
-  BIT(cwdfail);     /* set TRUE if a CWD command fails, as then we must prevent
-                       caching the current directory */
-  BIT(wait_data_conn); /* this is set TRUE if data connection is waited */
-  BIT(shutdown);    /* connection is being shutdown, e.g. QUIT */
-};
-
-/* meta key for storing `struct FTP` as easy meta data */
-#define CURL_META_FTP_EASY   "meta:proto:ftp:easy"
-/* meta key for storing `struct ftp_conn` as connection meta data */
-#define CURL_META_FTP_CONN   "meta:proto:ftp:conn"
+#endif /* CURL_DISABLE_FTP */
 
 #define DEFAULT_ACCEPT_TIMEOUT   60000 /* milliseconds == one minute */
 
diff --git a/Utilities/cmcurl/lib/ftplistparser.c b/Utilities/cmcurl/lib/ftplistparser.c
index f82895d..b5d9338 100644
--- a/Utilities/cmcurl/lib/ftplistparser.c
+++ b/Utilities/cmcurl/lib/ftplistparser.c
@@ -44,6 +44,7 @@
 #include "fileinfo.h"
 #include "llist.h"
 #include "ftp.h"
+#include "ftp-int.h"
 #include "ftplistparser.h"
 #include "curl_fnmatch.h"
 #include "multiif.h"
@@ -324,8 +325,7 @@
 
   /* filter pattern-corresponding filenames */
   Curl_set_in_callback(data, TRUE);
-  if(compare(data->set.fnmatch_data, wc->pattern,
-             finfo->filename) == 0) {
+  if(compare(data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) {
     /* discard symlink which is containing multiple " -> " */
     if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target &&
        (strstr(finfo->strings.target, " -> "))) {
diff --git a/Utilities/cmcurl/lib/getinfo.c b/Utilities/cmcurl/lib/getinfo.c
index dedafd3..fab63e6 100644
--- a/Utilities/cmcurl/lib/getinfo.c
+++ b/Utilities/cmcurl/lib/getinfo.c
@@ -78,6 +78,7 @@
 
   info->conn_scheme = 0;
   info->conn_protocol = 0;
+  info->used_proxy = 0;
 
 #ifdef USE_SSL
   Curl_ssl_free_certinfo(data);
@@ -414,6 +415,9 @@
   case CURLINFO_FILETIME_T:
     *param_offt = (curl_off_t)data->info.filetime;
     break;
+  case CURLINFO_SIZE_DELIVERED:
+    *param_offt = data->progress.deliver;
+    break;
   case CURLINFO_SIZE_UPLOAD_T:
     *param_offt = data->progress.ul.cur_size;
     break;
@@ -582,14 +586,16 @@
     break;
   case CURLINFO_TLS_SESSION:
   case CURLINFO_TLS_SSL_PTR: {
+    int query = (info == CURLINFO_TLS_SSL_PTR) ?
+      CF_QUERY_SSL_INFO : CF_QUERY_SSL_CTX_INFO;
     struct curl_tlssessioninfo **tsip = (struct curl_tlssessioninfo **)
-                                        param_slistp;
+      param_slistp;
     struct curl_tlssessioninfo *tsi = &data->tsi;
 
     /* we are exposing a pointer to internal memory with unknown
      * lifetime here. */
     *tsip = tsi;
-    if(!Curl_conn_get_ssl_info(data, data->conn, FIRSTSOCKET, tsi)) {
+    if(!Curl_conn_get_ssl_info(data, data->conn, FIRSTSOCKET, query, tsi)) {
       tsi->backend = Curl_ssl_backend();
       tsi->internals = NULL;
     }
diff --git a/Utilities/cmcurl/lib/gopher.c b/Utilities/cmcurl/lib/gopher.c
index 71ff392..f087121 100644
--- a/Utilities/cmcurl/lib/gopher.c
+++ b/Utilities/cmcurl/lib/gopher.c
@@ -172,7 +172,7 @@
  * connect-command-download protocols.
  */
 
-static const struct Curl_protocol Curl_protocol_gopher = {
+const struct Curl_protocol Curl_protocol_gopher = {
   ZERO_NULL,                            /* setup_connection */
   gopher_do,                            /* do_it */
   ZERO_NULL,                            /* done */
@@ -187,13 +187,13 @@
   ZERO_NULL,                            /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
 
 #ifdef USE_SSL
-static const struct Curl_protocol Curl_protocol_gophers = {
+const struct Curl_protocol Curl_protocol_gophers = {
   ZERO_NULL,                            /* setup_connection */
   gopher_do,                            /* do_it */
   ZERO_NULL,                            /* done */
@@ -208,36 +208,10 @@
   ZERO_NULL,                            /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
 #endif
 
 #endif /* CURL_DISABLE_GOPHER */
-
-const struct Curl_scheme Curl_scheme_gopher = {
-  "gopher",                             /* scheme */
-#ifdef CURL_DISABLE_GOPHER
-  ZERO_NULL,
-#else
-  &Curl_protocol_gopher,
-#endif
-  CURLPROTO_GOPHER,                     /* protocol */
-  CURLPROTO_GOPHER,                     /* family */
-  PROTOPT_NONE,                         /* flags */
-  PORT_GOPHER,                          /* defport */
-};
-
-const struct Curl_scheme Curl_scheme_gophers = {
-  "gophers",                            /* scheme */
-#if defined(CURL_DISABLE_GOPHER) || !defined(USE_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_gophers,
-#endif
-  CURLPROTO_GOPHERS,                    /* protocol */
-  CURLPROTO_GOPHER,                     /* family */
-  PROTOPT_SSL,                          /* flags */
-  PORT_GOPHER,                          /* defport */
-};
diff --git a/Utilities/cmcurl/lib/gopher.h b/Utilities/cmcurl/lib/gopher.h
index bc977bc..1c126be 100644
--- a/Utilities/cmcurl/lib/gopher.h
+++ b/Utilities/cmcurl/lib/gopher.h
@@ -23,7 +23,9 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_gopher;
-extern const struct Curl_scheme Curl_scheme_gophers;
+#ifndef CURL_DISABLE_GOPHER
+extern const struct Curl_protocol Curl_protocol_gopher;
+extern const struct Curl_protocol Curl_protocol_gophers;
+#endif
 
 #endif /* HEADER_CURL_GOPHER_H */
diff --git a/Utilities/cmcurl/lib/hash.c b/Utilities/cmcurl/lib/hash.c
index 8095ca2..3eae1d2 100644
--- a/Utilities/cmcurl/lib/hash.c
+++ b/Utilities/cmcurl/lib/hash.c
@@ -265,7 +265,7 @@
   DEBUGASSERT(h->init == HASHINIT);
   if(h->table) {
     Curl_hash_clean(h);
-    Curl_safefree(h->table);
+    curlx_safefree(h->table);
   }
   DEBUGASSERT(h->size == 0);
   h->slots = 0;
@@ -359,8 +359,8 @@
 #endif
 }
 
-struct Curl_hash_element *
-Curl_hash_next_element(struct Curl_hash_iterator *iter)
+struct Curl_hash_element *Curl_hash_next_element(
+  struct Curl_hash_iterator *iter)
 {
   struct Curl_hash *h;
   DEBUGASSERT(iter->init == ITERINIT);
diff --git a/Utilities/cmcurl/lib/hash.h b/Utilities/cmcurl/lib/hash.h
index 267f91d..1d7d3de 100644
--- a/Utilities/cmcurl/lib/hash.h
+++ b/Utilities/cmcurl/lib/hash.h
@@ -97,8 +97,8 @@
                              size_t key2_len);
 void Curl_hash_start_iterate(struct Curl_hash *hash,
                              struct Curl_hash_iterator *iter);
-struct Curl_hash_element *
-Curl_hash_next_element(struct Curl_hash_iterator *iter);
+struct Curl_hash_element *Curl_hash_next_element(
+  struct Curl_hash_iterator *iter);
 
 void Curl_hash_print(struct Curl_hash *h, void (*func)(void *));
 
diff --git a/Utilities/cmcurl/lib/headers.c b/Utilities/cmcurl/lib/headers.c
index 5a6b120..195e12b 100644
--- a/Utilities/cmcurl/lib/headers.c
+++ b/Utilities/cmcurl/lib/headers.c
@@ -52,23 +52,23 @@
 }
 
 /* public API */
-CURLHcode curl_easy_header(CURL *easy,
+CURLHcode curl_easy_header(CURL *curl,
                            const char *name,
                            size_t nameindex,
-                           unsigned int type,
+                           unsigned int origin,
                            int request,
                            struct curl_header **hout)
 {
   struct Curl_llist_node *e;
   struct Curl_llist_node *e_pick = NULL;
-  struct Curl_easy *data = easy;
+  struct Curl_easy *data = curl;
   size_t match = 0;
   size_t amount = 0;
   struct Curl_header_store *hs = NULL;
   struct Curl_header_store *pick = NULL;
   if(!name || !hout || !data ||
-     (type > (CURLH_HEADER | CURLH_TRAILER | CURLH_CONNECT | CURLH_1XX |
-              CURLH_PSEUDO)) || !type || (request < -1))
+     (origin > (CURLH_HEADER | CURLH_TRAILER | CURLH_CONNECT | CURLH_1XX |
+                CURLH_PSEUDO)) || !origin || (request < -1))
     return CURLHE_BAD_ARGUMENT;
   if(!Curl_llist_count(&data->state.httphdrs))
     return CURLHE_NOHEADERS; /* no headers available */
@@ -81,7 +81,7 @@
   for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) {
     hs = Curl_node_elem(e);
     if(curl_strequal(hs->name, name) &&
-       (hs->type & type) &&
+       (hs->type & origin) &&
        (hs->request == request)) {
       amount++;
       pick = hs;
@@ -100,7 +100,7 @@
     for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) {
       hs = Curl_node_elem(e);
       if(curl_strequal(hs->name, name) &&
-         (hs->type & type) &&
+         (hs->type & origin) &&
          (hs->request == request) &&
          (match++ == nameindex)) {
         e_pick = e;
@@ -118,12 +118,12 @@
 }
 
 /* public API */
-struct curl_header *curl_easy_nextheader(CURL *easy,
-                                         unsigned int type,
+struct curl_header *curl_easy_nextheader(CURL *curl,
+                                         unsigned int origin,
                                          int request,
                                          struct curl_header *prev)
 {
-  struct Curl_easy *data = easy;
+  struct Curl_easy *data = curl;
   struct Curl_llist_node *pick;
   struct Curl_llist_node *e;
   struct Curl_header_store *hs;
@@ -149,7 +149,7 @@
     /* make sure it is the next header of the desired type */
     do {
       hs = Curl_node_elem(pick);
-      if((hs->type & type) && (hs->request == request))
+      if((hs->type & origin) && (hs->request == request))
         break;
       pick = Curl_node_next(pick);
     } while(pick);
@@ -167,7 +167,7 @@
     struct Curl_header_store *check = Curl_node_elem(e);
     if(curl_strequal(hs->name, check->name) &&
        (check->request == request) &&
-       (check->type & type))
+       (check->type & origin))
       amount++;
     if(e == pick)
       index = amount - 1;
diff --git a/Utilities/cmcurl/lib/headers.h b/Utilities/cmcurl/lib/headers.h
index 85905e5..283179a 100644
--- a/Utilities/cmcurl/lib/headers.h
+++ b/Utilities/cmcurl/lib/headers.h
@@ -38,7 +38,7 @@
 
 /*
  * Initialize header collecting for a transfer.
- * Will add a client writer that catches CLIENTWRITE_HEADER writes.
+ * Add a client writer that catches CLIENTWRITE_HEADER writes.
  */
 CURLcode Curl_headers_init(struct Curl_easy *data);
 
diff --git a/Utilities/cmcurl/lib/hmac.c b/Utilities/cmcurl/lib/hmac.c
index 028b476..a4d6ebd 100644
--- a/Utilities/cmcurl/lib/hmac.c
+++ b/Utilities/cmcurl/lib/hmac.c
@@ -25,8 +25,8 @@
  ***************************************************************************/
 #include "curl_setup.h"
 
-#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) ||      \
-  !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) ||   \
+#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) ||    \
+  !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \
   defined(USE_SSL)
 
 #include "curl_hmac.h"
@@ -98,13 +98,12 @@
   return NULL;
 }
 
-int Curl_HMAC_update(struct HMAC_context *ctxt,
-                     const unsigned char *data,
-                     unsigned int len)
+void Curl_HMAC_update(struct HMAC_context *ctxt,
+                      const unsigned char *data,
+                      unsigned int len)
 {
   /* Update first hash calculation. */
   ctxt->hash->hupdate(ctxt->hashctxt1, data, len);
-  return 0;
 }
 
 int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *output)
@@ -143,17 +142,24 @@
  */
 CURLcode Curl_hmacit(const struct HMAC_params *hashparams,
                      const unsigned char *key, const size_t keylen,
-                     const unsigned char *data, const size_t datalen,
+                     const unsigned char *data, size_t datalen,
                      unsigned char *output)
 {
-  struct HMAC_context *ctxt =
-    Curl_HMAC_init(hashparams, key, curlx_uztoui(keylen));
+  struct HMAC_context *ctxt;
+  if(keylen > UINT_MAX) /* unlikely to ever happen */
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  ctxt = Curl_HMAC_init(hashparams, key, curlx_uztoui(keylen));
 
   if(!ctxt)
     return CURLE_OUT_OF_MEMORY;
 
   /* Update the digest with the given challenge */
-  Curl_HMAC_update(ctxt, data, curlx_uztoui(datalen));
+  do {
+    unsigned int ilen = (unsigned int)CURLMIN(datalen, UINT_MAX);
+    Curl_HMAC_update(ctxt, data, ilen);
+    datalen -= ilen;
+    data += ilen;
+  } while(datalen);
 
   /* Finalise the digest */
   Curl_HMAC_final(ctxt, output);
diff --git a/Utilities/cmcurl/lib/hostip.c b/Utilities/cmcurl/lib/hostip.c
index 542b655..85f53c4 100644
--- a/Utilities/cmcurl/lib/hostip.c
+++ b/Utilities/cmcurl/lib/hostip.c
@@ -46,21 +46,18 @@
 #include "urldata.h"
 #include "curl_addrinfo.h"
 #include "curl_trc.h"
-#include "connect.h"
+#include "dnscache.h"
 #include "hostip.h"
-#include "hash.h"
 #include "httpsrr.h"
-#include "rand.h"
-#include "curl_share.h"
 #include "url.h"
-#include "curlx/inet_ntop.h"
-#include "curlx/inet_pton.h"
 #include "multiif.h"
-#include "doh.h"
 #include "progress.h"
+#include "doh.h"
 #include "select.h"
 #include "strcase.h"
 #include "easy_lock.h"
+#include "curlx/inet_ntop.h"
+#include "curlx/inet_pton.h"
 #include "curlx/strcopy.h"
 #include "curlx/strparse.h"
 
@@ -77,6 +74,21 @@
 
 #define MAX_DNS_CACHE_SIZE 29999
 
+#define RESOLV_FAIL(for_proxy) \
+  ((for_proxy) ? CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST)
+
+#define IS_RESOLV_FAIL(result) \
+  (((result) == CURLE_COULDNT_RESOLVE_HOST) || \
+   ((result) == CURLE_COULDNT_RESOLVE_PROXY))
+/*
+ * ipv6works() returns TRUE if IPv6 seems to work.
+ */
+#ifdef USE_IPV6
+static bool ipv6works(struct Curl_easy *data);
+#else
+#define ipv6works(x) FALSE
+#endif
+
 /*
  * hostip.c explained
  * ==================
@@ -89,10 +101,10 @@
  * take that into account. Hosts that are not IPv6-enabled have CURLRES_IPV4
  * defined.
  *
- * CURLRES_ARES - is defined if libcurl is built to use c-ares for
+ * USE_RESOLV_ARES - is defined if libcurl is built to use c-ares for
  * asynchronous name resolves. This can be Windows or *nix.
  *
- * CURLRES_THREADED - is defined if libcurl is built to run under (native)
+ * USE_RESOLV_THREADED - is defined if libcurl is built to run under (native)
  * Windows, and then the name resolve will be done in a new thread, and the
  * supported API will be the same as for ares-builds.
  *
@@ -114,85 +126,49 @@
  * CURLRES_* defines based on the config*.h and curl_setup.h defines.
  */
 
+uint8_t Curl_resolv_dns_queries(struct Curl_easy *data, uint8_t ip_version)
+{
+  (void)data;
+  switch(ip_version) {
+  case CURL_IPRESOLVE_V6:
+    return CURL_DNSQ_AAAA;
+  case CURL_IPRESOLVE_V4:
+    return CURL_DNSQ_A;
+  default:
+    if(ipv6works(data))
+      return (CURL_DNSQ_A | CURL_DNSQ_AAAA);
+    else
+      return CURL_DNSQ_A;
+  }
+}
+
 #ifdef CURLVERBOSE
-static void show_resolve_info(struct Curl_easy *data,
-                              struct Curl_dns_entry *dns)
+const char *Curl_resolv_query_str(uint8_t dns_queries)
 {
-  const struct Curl_addrinfo *a;
-  CURLcode result = CURLE_OK;
-#ifdef CURLRES_IPV6
-  struct dynbuf out[2];
-#else
-  struct dynbuf out[1];
-#endif
-  DEBUGASSERT(data);
-  DEBUGASSERT(dns);
-
-  if(!data->set.verbose ||
-     /* ignore no name or numerical IP addresses */
-     !dns->hostname[0] || Curl_host_is_ipnum(dns->hostname))
-    return;
-
-  a = dns->addr;
-
-  infof(data, "Host %s:%d was resolved.",
-        (dns->hostname[0] ? dns->hostname : "(none)"), dns->hostport);
-
-  curlx_dyn_init(&out[0], 1024);
-#ifdef CURLRES_IPV6
-  curlx_dyn_init(&out[1], 1024);
-#endif
-
-  while(a) {
-    if(
-#ifdef CURLRES_IPV6
-       a->ai_family == PF_INET6 ||
-#endif
-       a->ai_family == PF_INET) {
-      char buf[MAX_IPADR_LEN];
-      struct dynbuf *d = &out[(a->ai_family != PF_INET)];
-      Curl_printable_address(a, buf, sizeof(buf));
-      if(curlx_dyn_len(d))
-        result = curlx_dyn_addn(d, ", ", 2);
-      if(!result)
-        result = curlx_dyn_add(d, buf);
-      if(result) {
-        infof(data, "too many IP, cannot show");
-        goto fail;
-      }
-    }
-    a = a->ai_next;
+  switch(dns_queries) {
+  case (CURL_DNSQ_A | CURL_DNSQ_AAAA | CURL_DNSQ_HTTPS):
+    return "A+AAAA+HTTPS";
+  case (CURL_DNSQ_A | CURL_DNSQ_AAAA):
+    return "A+AAAA";
+  case (CURL_DNSQ_AAAA | CURL_DNSQ_HTTPS):
+    return "AAAA+HTTPS";
+  case (CURL_DNSQ_AAAA):
+    return "AAAA";
+  case (CURL_DNSQ_A | CURL_DNSQ_HTTPS):
+    return "A+HTTPS";
+  case (CURL_DNSQ_A):
+    return "A";
+  case (CURL_DNSQ_HTTPS):
+    return "HTTPS";
+  case 0:
+    return "-";
+  default:
+    DEBUGASSERT(0);
+    return "???";
   }
-
-#ifdef CURLRES_IPV6
-  infof(data, "IPv6: %s",
-        (curlx_dyn_len(&out[1]) ? curlx_dyn_ptr(&out[1]) : "(none)"));
-#endif
-  infof(data, "IPv4: %s",
-        (curlx_dyn_len(&out[0]) ? curlx_dyn_ptr(&out[0]) : "(none)"));
-
-fail:
-  curlx_dyn_free(&out[0]);
-#ifdef CURLRES_IPV6
-  curlx_dyn_free(&out[1]);
-#endif
 }
-#else
-#define show_resolve_info(x, y) Curl_nop_stmt
 #endif
 
-static void dnscache_entry_free(struct Curl_dns_entry *dns)
-{
-  Curl_freeaddrinfo(dns->addr);
-#ifdef USE_HTTPSRR
-  if(dns->hinfo) {
-    Curl_httpsrr_cleanup(dns->hinfo);
-    curlx_free(dns->hinfo);
-  }
-#endif
-  curlx_free(dns);
-}
-
 /*
  * Curl_printable_address() stores a printable version of the 1st address
  * given in the 'ai' argument. The result will be stored in the buf that is
@@ -226,449 +202,17 @@
   }
 }
 
-/*
- * Create a hostcache id string for the provided host + port, to be used by
- * the DNS caching. Without alloc. Return length of the id string.
- */
-static size_t create_dnscache_id(const char *name,
-                                 size_t nlen, /* 0 or actual name length */
-                                 int port, char *ptr, size_t buflen)
-{
-  size_t len = nlen ? nlen : strlen(name);
-  DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN);
-  if(len > (buflen - 7))
-    len = buflen - 7;
-  /* store and lower case the name */
-  Curl_strntolower(ptr, name, len);
-  return curl_msnprintf(&ptr[len], 7, ":%u", port) + len;
-}
-
-struct dnscache_prune_data {
-  struct curltime now;
-  timediff_t oldest_ms; /* oldest time in cache not pruned. */
-  timediff_t max_age_ms;
-};
-
-/*
- * This function is set as a callback to be called for every entry in the DNS
- * cache when we want to prune old unused entries.
- *
- * Returning non-zero means remove the entry, return 0 to keep it in the
- * cache.
- */
-static int dnscache_entry_is_stale(void *datap, void *hc)
-{
-  struct dnscache_prune_data *prune = (struct dnscache_prune_data *)datap;
-  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)hc;
-
-  if(dns->timestamp.tv_sec || dns->timestamp.tv_usec) {
-    /* get age in milliseconds */
-    timediff_t age = curlx_ptimediff_ms(&prune->now, &dns->timestamp);
-    if(!dns->addr)
-      age *= 2; /* negative entries age twice as fast */
-    if(age >= prune->max_age_ms)
-      return TRUE;
-    if(age > prune->oldest_ms)
-      prune->oldest_ms = 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 - in milliseconds.
- */
-static timediff_t dnscache_prune(struct Curl_hash *hostcache,
-                                 timediff_t cache_timeout_ms,
-                                 struct curltime now)
-{
-  struct dnscache_prune_data user;
-
-  user.max_age_ms = cache_timeout_ms;
-  user.now = now;
-  user.oldest_ms = 0;
-
-  Curl_hash_clean_with_criterium(hostcache,
-                                 (void *)&user,
-                                 dnscache_entry_is_stale);
-
-  return user.oldest_ms;
-}
-
-static struct Curl_dnscache *dnscache_get(struct Curl_easy *data)
-{
-  if(data->share && data->share->specifier & (1 << CURL_LOCK_DATA_DNS))
-    return &data->share->dnscache;
-  if(data->multi)
-    return &data->multi->dnscache;
-  return NULL;
-}
-
-static void dnscache_lock(struct Curl_easy *data,
-                          struct Curl_dnscache *dnscache)
-{
-  if(data->share && dnscache == &data->share->dnscache)
-    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
-}
-
-static void dnscache_unlock(struct Curl_easy *data,
-                            struct Curl_dnscache *dnscache)
-{
-  if(data->share && dnscache == &data->share->dnscache)
-    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
-}
-
-/*
- * Library-wide function for pruning the DNS cache. This function takes and
- * returns the appropriate locks.
- */
-void Curl_dnscache_prune(struct Curl_easy *data)
-{
-  struct Curl_dnscache *dnscache = dnscache_get(data);
-  /* the timeout may be set -1 (forever) */
-  timediff_t timeout_ms = data->set.dns_cache_timeout_ms;
-
-  if(!dnscache || (timeout_ms == -1))
-    /* NULL hostcache means we cannot do it */
-    return;
-
-  dnscache_lock(data, dnscache);
-
-  do {
-    /* Remove outdated and unused entries from the hostcache */
-    timediff_t oldest_ms =
-      dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data));
-
-    if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE)
-      /* prune the ones over half this age */
-      timeout_ms = oldest_ms / 2;
-    else
-      break;
-
-    /* if the cache size is still too big, use the oldest age as new prune
-       limit */
-  } while(timeout_ms);
-
-  dnscache_unlock(data, dnscache);
-}
-
-void Curl_dnscache_clear(struct Curl_easy *data)
-{
-  struct Curl_dnscache *dnscache = dnscache_get(data);
-  if(dnscache) {
-    dnscache_lock(data, dnscache);
-    Curl_hash_clean(&dnscache->entries);
-    dnscache_unlock(data, dnscache);
-  }
-}
-
 #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. */
 static sigjmp_buf curl_jmpenv;
-static curl_simple_lock curl_jmpenv_lock;
+static curl_simple_lock curl_jmpenv_lock = CURL_SIMPLE_LOCK_INIT;
 #endif
 
-/* lookup address, returns entry if found and not stale */
-static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data,
-                                         struct Curl_dnscache *dnscache,
-                                         const char *hostname,
-                                         int port,
-                                         int ip_version)
-{
-  struct Curl_dns_entry *dns = NULL;
-  char entry_id[MAX_HOSTCACHE_LEN];
-  size_t entry_len;
-
-  if(!dnscache)
-    return NULL;
-
-  /* Create an entry id, based upon the hostname and port */
-  entry_len = create_dnscache_id(hostname, 0, port,
-                                 entry_id, sizeof(entry_id));
-
-  /* See if it is already in our dns cache */
-  dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
-
-  /* No entry found in cache, check if we might have a wildcard entry */
-  if(!dns && data->state.wildcard_resolve) {
-    entry_len = create_dnscache_id("*", 1, port, entry_id, sizeof(entry_id));
-
-    /* See if it is already in our dns cache */
-    dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
-  }
-
-  if(dns && (data->set.dns_cache_timeout_ms != -1)) {
-    /* See whether the returned entry is stale. Done before we release lock */
-    struct dnscache_prune_data user;
-
-    user.now = *Curl_pgrs_now(data);
-    user.max_age_ms = data->set.dns_cache_timeout_ms;
-    user.oldest_ms = 0;
-
-    if(dnscache_entry_is_stale(&user, dns)) {
-      infof(data, "Hostname in DNS cache was stale, zapped");
-      dns = NULL; /* the memory deallocation is being handled by the hash */
-      Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
-    }
-  }
-
-  /* See if the returned entry matches the required resolve mode */
-  if(dns && ip_version != CURL_IPRESOLVE_WHATEVER) {
-    int pf = PF_INET;
-    bool found = FALSE;
-    struct Curl_addrinfo *addr = dns->addr;
-
-#ifdef PF_INET6
-    if(ip_version == CURL_IPRESOLVE_V6)
-      pf = PF_INET6;
-#endif
-
-    while(addr) {
-      if(addr->ai_family == pf) {
-        found = TRUE;
-        break;
-      }
-      addr = addr->ai_next;
-    }
-
-    if(!found) {
-      infof(data, "Hostname in DNS cache does not have needed family, zapped");
-      dns = NULL; /* the memory deallocation is being handled by the hash */
-      Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
-    }
-  }
-  return dns;
-}
-
-/*
- * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
- *
- * Curl_resolv() checks initially and multi_runsingle() checks each time
- * it discovers the handle in the state WAITRESOLVE whether the hostname
- * has already been resolved and the address has already been stored in
- * the DNS cache. This short circuits waiting for a lot of pending
- * lookups for the same hostname requested by different handles.
- *
- * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
- *
- * The returned data *MUST* be "released" with Curl_resolv_unlink() after
- * use, or we will leak memory!
- */
-struct Curl_dns_entry *Curl_dnscache_get(struct Curl_easy *data,
-                                         const char *hostname,
-                                         int port,
-                                         int ip_version)
-{
-  struct Curl_dnscache *dnscache = dnscache_get(data);
-  struct Curl_dns_entry *dns = NULL;
-
-  dnscache_lock(data, dnscache);
-
-  dns = fetch_addr(data, dnscache, hostname, port, ip_version);
-  if(dns)
-    dns->refcount++; /* we use it! */
-
-  dnscache_unlock(data, dnscache);
-
-  return dns;
-}
-
-#ifndef CURL_DISABLE_SHUFFLE_DNS
-/*
- * Return # of addresses in a Curl_addrinfo struct
- */
-static int num_addresses(const struct Curl_addrinfo *addr)
-{
-  int i = 0;
-  while(addr) {
-    addr = addr->ai_next;
-    i++;
-  }
-  return i;
-}
-
-UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
-                                    struct Curl_addrinfo **addr);
-/*
- * Curl_shuffle_addr() shuffles the order of addresses in a 'Curl_addrinfo'
- * struct by re-linking its linked list.
- *
- * The addr argument should be the address of a pointer to the head node of a
- * `Curl_addrinfo` list and it will be modified to point to the new head after
- * shuffling.
- *
- * Not declared static only to make it easy to use in a unit test!
- *
- * @unittest: 1608
- */
-UNITTEST CURLcode Curl_shuffle_addr(struct Curl_easy *data,
-                                    struct Curl_addrinfo **addr)
-{
-  CURLcode result = CURLE_OK;
-  const int num_addrs = num_addresses(*addr);
-
-  if(num_addrs > 1) {
-    struct Curl_addrinfo **nodes;
-    infof(data, "Shuffling %i addresses", num_addrs);
-
-    nodes = curlx_malloc(num_addrs * sizeof(*nodes));
-    if(nodes) {
-      int i;
-      unsigned int *rnd;
-      const size_t rnd_size = num_addrs * sizeof(*rnd);
-
-      /* build a plain array of Curl_addrinfo pointers */
-      nodes[0] = *addr;
-      for(i = 1; i < num_addrs; i++) {
-        nodes[i] = nodes[i - 1]->ai_next;
-      }
-
-      rnd = curlx_malloc(rnd_size);
-      if(rnd) {
-        /* Fisher-Yates shuffle */
-        if(Curl_rand(data, (unsigned char *)rnd, rnd_size) == CURLE_OK) {
-          struct Curl_addrinfo *swap_tmp;
-          for(i = num_addrs - 1; i > 0; i--) {
-            swap_tmp = nodes[rnd[i] % (unsigned int)(i + 1)];
-            nodes[rnd[i] % (unsigned int)(i + 1)] = nodes[i];
-            nodes[i] = swap_tmp;
-          }
-
-          /* relink list in the new order */
-          for(i = 1; i < num_addrs; i++) {
-            nodes[i - 1]->ai_next = nodes[i];
-          }
-
-          nodes[num_addrs - 1]->ai_next = NULL;
-          *addr = nodes[0];
-        }
-        curlx_free(rnd);
-      }
-      else
-        result = CURLE_OUT_OF_MEMORY;
-      curlx_free(nodes);
-    }
-    else
-      result = CURLE_OUT_OF_MEMORY;
-  }
-  return result;
-}
-#endif
-
-struct Curl_dns_entry *
-Curl_dnscache_mk_entry(struct Curl_easy *data,
-                       struct Curl_addrinfo **paddr,
-                       const char *hostname,
-                       size_t hostlen, /* length or zero */
-                       int port,
-                       bool permanent)
-{
-  struct Curl_dns_entry *dns = NULL;
-
-#ifndef CURL_DISABLE_SHUFFLE_DNS
-  /* shuffle addresses if requested */
-  if(data->set.dns_shuffle_addresses && paddr) {
-    CURLcode result = Curl_shuffle_addr(data, paddr);
-    if(result)
-      goto out;
-  }
-#else
-  (void)data;
-#endif
-  if(!hostlen)
-    hostlen = strlen(hostname);
-
-  /* Create a new cache entry */
-  dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + hostlen);
-  if(!dns)
-    goto out;
-
-  dns->refcount = 1; /* the cache has the first reference */
-  dns->addr = paddr ? *paddr : NULL; /* this is the address(es) */
-  if(permanent) {
-    dns->timestamp.tv_sec = 0; /* an entry that never goes stale */
-    dns->timestamp.tv_usec = 0; /* an entry that never goes stale */
-  }
-  else {
-    dns->timestamp = *Curl_pgrs_now(data);
-  }
-  dns->hostport = port;
-  if(hostlen)
-    memcpy(dns->hostname, hostname, hostlen);
-
-out:
-  if(paddr) {
-    if(!dns)
-      Curl_freeaddrinfo(*paddr);
-    *paddr = NULL;
-  }
-  return dns;
-}
-
-static struct Curl_dns_entry *
-dnscache_add_addr(struct Curl_easy *data,
-                  struct Curl_dnscache *dnscache,
-                  struct Curl_addrinfo **paddr,
-                  const char *hostname,
-                  size_t hlen, /* length or zero */
-                  int port,
-                  bool permanent)
-{
-  char entry_id[MAX_HOSTCACHE_LEN];
-  size_t entry_len;
-  struct Curl_dns_entry *dns;
-  struct Curl_dns_entry *dns2;
-
-  dns = Curl_dnscache_mk_entry(data, paddr, hostname, hlen, port, permanent);
-  if(!dns)
-    return NULL;
-
-  /* Create an entry id, based upon the hostname and port */
-  entry_len = create_dnscache_id(hostname, hlen, port,
-                                 entry_id, sizeof(entry_id));
-
-  /* Store the resolved data in our DNS cache. */
-  dns2 = Curl_hash_add(&dnscache->entries, entry_id, entry_len + 1,
-                       (void *)dns);
-  if(!dns2) {
-    dnscache_entry_free(dns);
-    return NULL;
-  }
-
-  dns = dns2;
-  dns->refcount++;         /* mark entry as in-use */
-  return dns;
-}
-
-CURLcode Curl_dnscache_add(struct Curl_easy *data,
-                           struct Curl_dns_entry *entry)
-{
-  struct Curl_dnscache *dnscache = dnscache_get(data);
-  char id[MAX_HOSTCACHE_LEN];
-  size_t idlen;
-
-  if(!dnscache)
-    return CURLE_FAILED_INIT;
-  /* Create an entry id, based upon the hostname and port */
-  idlen = create_dnscache_id(entry->hostname, 0, entry->hostport,
-                             id, sizeof(id));
-
-  /* Store the resolved data in our DNS cache and up ref count */
-  dnscache_lock(data, dnscache);
-  if(!Curl_hash_add(&dnscache->entries, id, idlen + 1, (void *)entry)) {
-    dnscache_unlock(data, dnscache);
-    return CURLE_OUT_OF_MEMORY;
-  }
-  entry->refcount++;
-  dnscache_unlock(data, dnscache);
-  return CURLE_OK;
-}
-
 #ifdef USE_IPV6
 /* return a static IPv6 ::1 for the name */
-static struct Curl_addrinfo *get_localhost6(int port, const char *name)
+static struct Curl_addrinfo *get_localhost6(uint16_t port, const char *name)
 {
   struct Curl_addrinfo *ca;
   const size_t ss_size = sizeof(struct sockaddr_in6);
@@ -680,12 +224,9 @@
   if(!ca)
     return NULL;
 
+  memset(&sa6, 0, sizeof(sa6));
   sa6.sin6_family = AF_INET6;
   sa6.sin6_port = htons(port16);
-  sa6.sin6_flowinfo = 0;
-#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
-  sa6.sin6_scope_id = 0;
-#endif
 
   (void)curlx_inet_pton(AF_INET6, "::1", ipv6);
   memcpy(&sa6.sin6_addr, ipv6, sizeof(ipv6));
@@ -707,7 +248,7 @@
 #endif
 
 /* return a static IPv4 127.0.0.1 for the given name */
-static struct Curl_addrinfo *get_localhost(int port, const char *name)
+static struct Curl_addrinfo *get_localhost(uint16_t port, const char *name)
 {
   struct Curl_addrinfo *ca;
   struct Curl_addrinfo *ca6;
@@ -766,9 +307,9 @@
 }
 
 /*
- * Curl_ipv6works() returns TRUE if IPv6 seems to work.
+ * ipv6works() returns TRUE if IPv6 seems to work.
  */
-bool Curl_ipv6works(struct Curl_easy *data)
+static bool ipv6works(struct Curl_easy *data)
 {
   DEBUGASSERT(data);
   DEBUGASSERT(data->multi);
@@ -804,86 +345,354 @@
   return curl_strnequal(part, &full[flen - plen], plen);
 }
 
-static bool can_resolve_ip_version(struct Curl_easy *data, int ip_version)
+static CURLcode hostip_resolv_failed(struct Curl_easy *data,
+                                     const char *hostname,
+                                     bool for_proxy)
 {
-#ifdef CURLRES_IPV6
-  if(ip_version == CURL_IPRESOLVE_V6 && !Curl_ipv6works(data))
-    return FALSE;
-#elif defined(CURLRES_IPV4)
+  failf(data, "Could not resolve %s: %s",
+        for_proxy ? "proxy" : "host", hostname);
+  return RESOLV_FAIL(for_proxy);
+}
+
+static bool can_resolve_dns_queries(struct Curl_easy *data,
+                                    uint8_t dns_queries)
+{
   (void)data;
-  if(ip_version == CURL_IPRESOLVE_V6)
+  if((CURL_DNSQ_IP(dns_queries) == CURL_DNSQ_AAAA) && !ipv6works(data))
     return FALSE;
-#else
-#error either CURLRES_IPV6 or CURLRES_IPV4 need to be defined
-#endif
   return TRUE;
 }
 
-static CURLcode store_negative_resolve(struct Curl_easy *data,
-                                       const char *host,
-                                       int port)
+CURLcode Curl_resolv_announce_start(struct Curl_easy *data,
+                                    void *resolver)
 {
-  struct Curl_dnscache *dnscache = dnscache_get(data);
-  struct Curl_dns_entry *dns;
-  DEBUGASSERT(dnscache);
-  if(!dnscache)
-    return CURLE_FAILED_INIT;
+  if(data->set.resolver_start) {
+    int rc;
 
-  /* put this new host in the cache */
-  dns = dnscache_add_addr(data, dnscache, NULL, host, 0, port, FALSE);
-  if(dns) {
-    /* release the returned reference; the cache itself will keep the
-     * entry alive: */
-    dns->refcount--;
-    infof(data, "Store negative name resolve for %s:%d", host, port);
-    return CURLE_OK;
+    CURL_TRC_DNS(data, "announcing resolve to application");
+    Curl_set_in_callback(data, TRUE);
+    rc = data->set.resolver_start(resolver, NULL,
+                                  data->set.resolver_start_client);
+    Curl_set_in_callback(data, FALSE);
+    if(rc) {
+      CURL_TRC_DNS(data, "application aborted resolve");
+      return CURLE_ABORTED_BY_CALLBACK;
+    }
   }
-  return CURLE_OUT_OF_MEMORY;
+  return CURLE_OK;
 }
 
-/*
- * Curl_resolv() is the main name resolve function within libcurl. It resolves
- * a name and returns a pointer to the entry in the 'entry' argument. This
- * function might return immediately if we are using asynch resolves. See the
- * return codes.
- *
- * The cache entry we return will get its 'inuse' counter increased when this
- * function is used. You MUST call Curl_resolv_unlink() later (when you are
- * done using this struct) to decrease the reference counter again.
- *
- * Return codes:
- * CURLE_OK = success, *entry set to non-NULL
- * CURLE_AGAIN = resolving in progress, *entry == NULL
- * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL
- * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL
- */
-CURLcode Curl_resolv(struct Curl_easy *data,
-                     const char *hostname,
-                     int port,
-                     int ip_version,
-                     bool allowDOH,
-                     struct Curl_dns_entry **entry)
-{
-  struct Curl_dnscache *dnscache = dnscache_get(data);
-  struct Curl_dns_entry *dns = NULL;
-  struct Curl_addrinfo *addr = NULL;
-  bool respwait = FALSE;
-  size_t hostname_len;
-  CURLcode result = CURLE_COULDNT_RESOLVE_HOST;
+#ifdef USE_CURL_ASYNC
 
-  *entry = NULL;
+static struct Curl_resolv_async *hostip_async_new(struct Curl_easy *data,
+                                                  uint8_t dns_queries,
+                                                  const char *hostname,
+                                                  uint16_t port,
+                                                  uint8_t transport,
+                                                  bool for_proxy,
+                                                  timediff_t timeout_ms)
+{
+  struct Curl_resolv_async *async;
+  size_t hostlen = strlen(hostname);
+
+  if(!data->multi) {
+    DEBUGASSERT(0);
+    return NULL;
+  }
+
+  /* struct size already includes the NUL for hostname */
+  async = curlx_calloc(1, sizeof(*async) + hostlen);
+  if(!async)
+    return NULL;
+
+  /* Give every async resolve operation a "unique" id. This may
+   * wrap around after a long time, making collisions highly unlikely.
+   * As we keep the async structs at the easy handle, chances of
+   * easy `mid plus resolv->id` colliding should be astronomical.
+   * `resolv_id == 0` is never used. */
+  if(data->multi->last_resolv_id == UINT32_MAX)
+    data->multi->last_resolv_id = 1; /* wrap around */
+  else
+    data->multi->last_resolv_id++;
+  async->id = data->multi->last_resolv_id;
+  async->dns_queries = dns_queries;
+  async->port = port;
+  async->transport = transport;
+  async->for_proxy = for_proxy;
+  async->start = *Curl_pgrs_now(data);
+  async->timeout_ms = timeout_ms;
+  if(hostlen) {
+    memcpy(async->hostname, hostname, hostlen);
+    async->is_ipaddr = Curl_is_ipaddr(async->hostname);
+    if(async->is_ipaddr)
+      async->is_ipv4addr = Curl_is_ipv4addr(async->hostname);
+  }
+
+  return async;
+}
+
+static CURLcode hostip_resolv_take_result(struct Curl_easy *data,
+                                          struct Curl_resolv_async *async,
+                                          struct Curl_dns_entry **pdns)
+{
+  CURLcode result;
+
+  /* If async resolving is ongoing, this must be set */
+  if(!async)
+    return CURLE_FAILED_INIT;
 
 #ifndef CURL_DISABLE_DOH
-  data->conn->bits.doh = FALSE; /* default is not */
+  if(async->doh)
+    result = Curl_doh_take_result(data, async, pdns);
+  else
+#endif
+  result = Curl_async_take_result(data, async, pdns);
+
+  if(result == CURLE_AGAIN) {
+    CURL_TRC_DNS(data, "resolve incomplete, queries=%s, responses=%s, "
+                 "ongoing=%d for %s:%d",
+                 Curl_resolv_query_str(async->dns_queries),
+                 Curl_resolv_query_str(async->dns_responses),
+                 async->queries_ongoing, async->hostname, async->port);
+    result = CURLE_OK;
+  }
+  else if(result) {
+    result = Curl_async_failed(data, async, NULL);
+  }
+  else {
+    CURL_TRC_DNS(data, "resolve complete for %s:%u",
+                 async->hostname, async->port);
+    DEBUGASSERT(*pdns);
+  }
+
+  return result;
+}
+
+timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data,
+                                  uint32_t resolv_id)
+{
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
+  if(!async)
+    return CURL_TIMEOUT_RESOLVE_MS;
+  return curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start);
+}
+
+bool Curl_resolv_has_answers(struct Curl_easy *data,
+                             uint32_t resolv_id, uint8_t dns_queries)
+{
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
+  uint8_t check_queries;
+  /* a no longer existing/running resolve has all answers. */
+  if(!async || async->done)
+    return TRUE;
+  /* Relevant are only queries undertaken. Others are considered answered. */
+  check_queries = (dns_queries & async->dns_queries);
+  if((check_queries & async->dns_responses) != check_queries) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
+const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data,
+                                               uint32_t resolv_id,
+                                               int ai_family,
+                                               unsigned int index)
+{
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
+  (void)index;
+  if(!async)
+    return NULL;
+  if((ai_family == AF_INET) && !(async->dns_queries & CURL_DNSQ_A))
+    return NULL;
+#ifdef USE_IPV6
+  if((ai_family == AF_INET6) && !(async->dns_queries & CURL_DNSQ_AAAA))
+    return NULL;
+#endif
+  return Curl_async_get_ai(data, async, ai_family, index);
+}
+
+
+#ifdef USE_HTTPSRR
+const struct Curl_https_rrinfo *
+Curl_resolv_get_https(struct Curl_easy *data, uint32_t resolv_id)
+{
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
+  if(!async)
+    return NULL;
+  return Curl_async_get_https(data, async);
+}
+
+bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id)
+{
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
+  if(!async)
+    return TRUE;
+  return Curl_async_knows_https(data, async);
+}
+
+#endif /* USE_HTTPSRR */
+
+#endif /* USE_CURL_ASYNC */
+
+static CURLcode hostip_resolv_start(struct Curl_easy *data,
+                                    uint8_t dns_queries,
+                                    const char *hostname,
+                                    uint16_t port,
+                                    uint8_t transport,
+                                    bool for_proxy,
+                                    timediff_t timeout_ms,
+                                    bool allowDOH,
+                                    uint32_t *presolv_id,
+                                    struct Curl_dns_entry **pdns)
+{
+#ifdef USE_CURL_ASYNC
+  struct Curl_resolv_async *async = NULL;
+#endif
+  struct Curl_addrinfo *addr = NULL;
+  size_t hostname_len;
+  CURLcode result = CURLE_OK;
+
+  (void)timeout_ms; /* not in all ifdefs */
+  *presolv_id = 0;
+  *pdns = NULL;
+
+  /* Check for "known" things to resolve ourselves. */
+#ifndef USE_RESOLVE_ON_IPS
+  if(Curl_is_ipaddr(hostname)) {
+    /* test655 verifies that the announce is done, even though there
+     * is no real resolving. So, keep doing this. */
+    result = Curl_resolv_announce_start(data, NULL);
+    if(result)
+      goto out;
+    /* shortcut literal IP addresses, if we are not told to resolve them. */
+    result = Curl_str2addr(hostname, port, &addr);
+    goto out;
+  }
+#endif
+
+  hostname_len = strlen(hostname);
+  if(curl_strequal(hostname, "localhost") ||
+     curl_strequal(hostname, "localhost.") ||
+     tailmatch(hostname, hostname_len, STRCONST(".localhost")) ||
+     tailmatch(hostname, hostname_len, STRCONST(".localhost."))) {
+    result = Curl_resolv_announce_start(data, NULL);
+    if(result)
+      goto out;
+    addr = get_localhost(port, hostname);
+    if(!addr)
+      result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+#ifndef CURL_DISABLE_DOH
+  if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) {
+    result = Curl_resolv_announce_start(data, NULL);
+    if(result)
+      goto out;
+    if(!async) {
+      async = hostip_async_new(data, dns_queries, hostname, port,
+                               transport, for_proxy, timeout_ms);
+      if(!async) {
+        result = CURLE_OUT_OF_MEMORY;
+        goto out;
+      }
+    }
+    result = Curl_doh(data, async);
+    goto out;
+  }
 #else
   (void)allowDOH;
 #endif
-  DEBUGASSERT(dnscache);
-  if(!dnscache) {
-    result = CURLE_BAD_FUNCTION_ARGUMENT;
-    goto error;
+
+  /* Can we provide the requested IP specifics in resolving? */
+  if(!can_resolve_dns_queries(data, dns_queries)) {
+    result = RESOLV_FAIL(for_proxy);
+    goto out;
   }
 
+#ifdef CURLRES_ASYNCH
+  (void)addr;
+  if(!async) {
+    async = hostip_async_new(data, dns_queries, hostname, port,
+                             transport, for_proxy, timeout_ms);
+    if(!async) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto out;
+    }
+  }
+  result = Curl_async_getaddrinfo(data, async);
+  if(result == CURLE_AGAIN) {
+    /* the answer might be there already. Check. */
+    CURLcode r2 = hostip_resolv_take_result(data, async, pdns);
+    if(r2)
+      result = r2;
+    else if(*pdns)
+      result = CURLE_OK;
+  }
+#else
+  result = Curl_resolv_announce_start(data, NULL);
+  if(result)
+    goto out;
+  addr = Curl_sync_getaddrinfo(data, dns_queries, hostname, port, transport);
+  if(!addr)
+    result = RESOLV_FAIL(for_proxy);
+#endif
+
+out:
+  if(!result) {
+    if(addr) {
+      /* we got a response, create a dns entry, add to cache, return */
+      DEBUGASSERT(!*pdns);
+      *pdns = Curl_dnscache_mk_entry(data, dns_queries, &addr, hostname, port);
+      if(!*pdns)
+        result = CURLE_OUT_OF_MEMORY;
+    }
+    else if(!*pdns)
+      result = CURLE_AGAIN;
+  }
+  else if(*pdns)
+    Curl_dns_entry_unlink(data, pdns);
+  else if(addr)
+    Curl_freeaddrinfo(addr);
+
+#ifdef USE_CURL_ASYNC
+  if(async) {
+    if(result == CURLE_AGAIN) { /* still need it, link, return id. */
+      *presolv_id = async->id;
+      async->next = data->state.async;
+      data->state.async = async;
+    }
+    else {
+      Curl_async_destroy(data, async);
+    }
+  }
+#endif
+  return result;
+}
+
+static CURLcode hostip_resolv(struct Curl_easy *data,
+                              uint8_t dns_queries,
+                              const char *hostname,
+                              uint16_t port,
+                              uint8_t transport,
+                              bool for_proxy,
+                              timediff_t timeout_ms,
+                              bool allowDOH,
+                              uint32_t *presolv_id,
+                              struct Curl_dns_entry **pdns)
+{
+  size_t hostname_len;
+  CURLcode result = RESOLV_FAIL(for_proxy);
+  bool cache_dns = FALSE;
+
+  (void)timeout_ms; /* not used in all ifdefs */
+  *presolv_id = 0;
+  *pdns = NULL;
+
+#ifdef CURL_DISABLE_DOH
+  (void)allowDOH;
+#endif
+
   /* We should intentionally error and not resolve .onion TLDs */
   hostname_len = strlen(hostname);
   DEBUGASSERT(hostname_len);
@@ -891,152 +700,88 @@
      (curl_strequal(&hostname[hostname_len - 6], ".onion") ||
       curl_strequal(&hostname[hostname_len - 7], ".onion."))) {
     failf(data, "Not resolving .onion address (RFC 7686)");
-    goto error;
+    goto out;
   }
 
+#ifdef DEBUGBUILD
+  CURL_TRC_DNS(data, "hostip_resolv(%s:%u, queries=%s)",
+               hostname, port, Curl_resolv_query_str(dns_queries));
+  if((CURL_DNSQ_IP(dns_queries) == CURL_DNSQ_AAAA) &&
+     getenv("CURL_DBG_RESOLV_FAIL_IPV6")) {
+    infof(data, "DEBUG fail ipv6 resolve");
+    result = hostip_resolv_failed(data, hostname, for_proxy);
+    goto out;
+  }
+#endif
   /* Let's check our DNS cache first */
-  dnscache_lock(data, dnscache);
-  dns = fetch_addr(data, dnscache, hostname, port, ip_version);
-  if(dns)
-    dns->refcount++; /* we pass out the reference. */
-  dnscache_unlock(data, dnscache);
-  if(dns) {
+  result = Curl_dnscache_get(data, dns_queries, hostname, port, pdns);
+  if(*pdns) {
     infof(data, "Hostname %s was found in DNS cache", hostname);
     result = CURLE_OK;
-    goto out;
   }
-
-  /* No luck, we need to resolve hostname. Notify user callback. */
-  if(data->set.resolver_start) {
-    void *resolver = NULL;
-    int st;
-#ifdef CURLRES_ASYNCH
-    result = Curl_async_get_impl(data, &resolver);
-    if(result)
-      goto error;
-#endif
-    Curl_set_in_callback(data, TRUE);
-    st = data->set.resolver_start(resolver, NULL,
-                                  data->set.resolver_start_client);
-    Curl_set_in_callback(data, FALSE);
-    if(st) {
-      result = CURLE_ABORTED_BY_CALLBACK;
-      goto error;
-    }
+  else if(result) {
+    infof(data, "Negative DNS entry");
+    result = hostip_resolv_failed(data, hostname, for_proxy);
   }
-
-  if(Curl_is_ipaddr(hostname)) {
-#ifndef USE_RESOLVE_ON_IPS
-    /* shortcut literal IP addresses, if we are not told to resolve them. */
-    result = Curl_str2addr(hostname, port, &addr);
-    if(result)
-      goto error;
-    goto out;
-#endif
-  }
-
-  if(curl_strequal(hostname, "localhost") ||
-     curl_strequal(hostname, "localhost.") ||
-     tailmatch(hostname, hostname_len, STRCONST(".localhost")) ||
-     tailmatch(hostname, hostname_len, STRCONST(".localhost."))) {
-    addr = get_localhost(port, hostname);
-    result = addr ? CURLE_OK : CURLE_OUT_OF_MEMORY;
-  }
-#ifndef CURL_DISABLE_DOH
-  else if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) {
-    result = Curl_doh(data, hostname, port, ip_version);
-    respwait = TRUE;
-  }
-#endif
   else {
-    /* Can we provide the requested IP specifics in resolving? */
-    if(!can_resolve_ip_version(data, ip_version)) {
-      result = CURLE_COULDNT_RESOLVE_HOST;
-      goto error;
-    }
-
-#ifdef CURLRES_ASYNCH
-    result = Curl_async_getaddrinfo(data, hostname, port, ip_version);
-    respwait = TRUE;
-#else
-    respwait = FALSE; /* no async waiting here */
-    addr = Curl_sync_getaddrinfo(data, hostname, port, ip_version);
-    if(addr)
-      result = CURLE_OK;
-#endif
+    /* No luck, we need to start resolving. */
+    cache_dns = TRUE;
+    result = hostip_resolv_start(data, dns_queries, hostname, port,
+                                 transport, for_proxy, timeout_ms, allowDOH,
+                                 presolv_id, pdns);
   }
 
 out:
-  /* We either have found a `dns` or looked up the `addr` or `respwait` is set
-   * for an async operation. Everything else is a failure to resolve. */
-  if(result)
-    ;
-  else if(dns) {
-    if(!dns->addr) {
-      infof(data, "Negative DNS entry");
-      dns->refcount--;
-      return CURLE_COULDNT_RESOLVE_HOST;
+  if(result && (result != CURLE_AGAIN)) {
+    Curl_dns_entry_unlink(data, pdns);
+    if(IS_RESOLV_FAIL(result)) {
+      if(cache_dns)
+        Curl_dnscache_add_negative(data, dns_queries, hostname, port);
+      failf(data, "Could not resolve: %s:%u", hostname, port);
     }
-    *entry = dns;
-    return CURLE_OK;
-  }
-  else if(addr) {
-    /* we got a response, create a dns entry, add to cache, return */
-    dns = Curl_dnscache_mk_entry(data, &addr, hostname, 0, port, FALSE);
-    if(!dns || Curl_dnscache_add(data, dns)) {
-      /* this is OOM or similar, do not store such negative resolves */
-      result = CURLE_OUT_OF_MEMORY;
-      goto error;
+    else {
+      failf(data, "Error %d resolving %s:%u", result, hostname, port);
     }
-    show_resolve_info(data, dns);
-    *entry = dns;
-    return CURLE_OK;
   }
-  else if(respwait) {
-#ifdef USE_CURL_ASYNC
-    if(!Curl_resolv_check(data, &dns)) {
-      *entry = dns;
-      return dns ? CURLE_OK : CURLE_AGAIN;
-    }
-#endif
-    result = CURLE_COULDNT_RESOLVE_HOST;
+  else if(cache_dns && *pdns) {
+    result = Curl_dnscache_add(data, *pdns);
+    if(result)
+      Curl_dns_entry_unlink(data, pdns);
   }
-error:
-  if(dns)
-    Curl_resolv_unlink(data, &dns);
-  Curl_async_shutdown(data);
-  if(result == CURLE_COULDNT_RESOLVE_HOST)
-    store_negative_resolve(data, hostname, port);
-  DEBUGASSERT(result);
+
   return result;
 }
 
 CURLcode Curl_resolv_blocking(struct Curl_easy *data,
+                              uint8_t dns_queries,
                               const char *hostname,
-                              int port,
-                              int ip_version,
-                              struct Curl_dns_entry **entry)
+                              uint16_t port,
+                              uint8_t transport,
+                              struct Curl_dns_entry **pdns)
 {
   CURLcode result;
+  uint32_t resolv_id;
   DEBUGASSERT(hostname && *hostname);
-  *entry = NULL;
-  result = Curl_resolv(data, hostname, port, ip_version, FALSE, entry);
+  *pdns = NULL;
+  /* We cannot do a blocking resolve using DoH currently */
+  result = hostip_resolv(data, dns_queries,
+                         hostname, port, transport, FALSE, 0, FALSE,
+                         &resolv_id, pdns);
   switch(result) {
   case CURLE_OK:
-    DEBUGASSERT(*entry);
-    return CURLE_OK;
+    DEBUGASSERT(*pdns);
+    break;
+#ifdef USE_CURL_ASYNC
   case CURLE_AGAIN:
-    DEBUGASSERT(!*entry);
-    result = Curl_async_await(data, entry);
-    if(result || !*entry) {
-      /* close the connection, since we cannot return failure here without
-         cleaning up this connection properly. */
-      connclose(data->conn, "async resolve failed");
-    }
-    return result;
+    DEBUGASSERT(!*pdns);
+    result = Curl_async_await(data, resolv_id, pdns);
+    Curl_resolv_destroy(data, resolv_id);
+    break;
+#endif
   default:
-    return result;
+    break;
   }
+  return result;
 }
 
 #ifdef USE_ALARM_TIMEOUT
@@ -1052,35 +797,18 @@
 }
 #endif /* USE_ALARM_TIMEOUT */
 
-/*
- * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
- * timeout. This function might return immediately if we are using asynch
- * resolves. See the return codes.
- *
- * The cache entry we return will get its 'inuse' counter increased when this
- * function is used. You MUST call Curl_resolv_unlink() later (when you are
- * done using this struct) to decrease the reference counter again.
- *
- * If built with a synchronous resolver and use of signals is not
- * disabled by the application, then a nonzero timeout will cause a
- * timeout after the specified number of milliseconds. Otherwise, timeout
- * is ignored.
- *
- * Return codes:
- * CURLE_OK = success, *entry set to non-NULL
- * CURLE_AGAIN = resolving in progress, *entry == NULL
- * CURLE_COULDNT_RESOLVE_HOST = error, *entry == NULL
- * CURLE_OPERATION_TIMEDOUT = timeout expired, *entry == NULL
- */
-
-CURLcode Curl_resolv_timeout(struct Curl_easy *data,
-                             const char *hostname,
-                             int port,
-                             int ip_version,
-                             struct Curl_dns_entry **entry,
-                             timediff_t timeoutms)
-{
 #ifdef USE_ALARM_TIMEOUT
+
+static CURLcode resolv_alarm_timeout(struct Curl_easy *data,
+                                     uint8_t dns_queries,
+                                     const char *hostname,
+                                     uint16_t port,
+                                     uint8_t transport,
+                                     bool for_proxy,
+                                     timediff_t timeout_ms,
+                                     uint32_t *presolv_id,
+                                     struct Curl_dns_entry **entry)
+{
 #ifdef HAVE_SIGACTION
   struct sigaction keep_sigact; /* store the old struct here */
   volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */
@@ -1092,32 +820,17 @@
 #endif /* HAVE_SIGACTION */
   volatile long timeout;
   volatile unsigned int prev_alarm = 0;
-#endif /* USE_ALARM_TIMEOUT */
   CURLcode result;
 
   DEBUGASSERT(hostname && *hostname);
-  *entry = NULL;
-
-  if(timeoutms < 0)
-    /* got an already expired timeout */
-    return CURLE_OPERATION_TIMEDOUT;
-
-#ifdef USE_ALARM_TIMEOUT
-  if(data->set.no_signal)
-    /* Ignore the timeout when signals are disabled */
-    timeout = 0;
-  else
-    timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms;
-
-  if(!timeout
+  DEBUGASSERT(timeout_ms > 0);
+  DEBUGASSERT(!data->set.no_signal);
 #ifndef CURL_DISABLE_DOH
-     || data->set.doh
+  DEBUGASSERT(!data->set.doh);
 #endif
-    )
-    /* USE_ALARM_TIMEOUT defined, but no timeout actually requested or resolve
-       done using DoH */
-    return Curl_resolv(data, hostname, port, ip_version, TRUE, entry);
 
+  *entry = NULL;
+  timeout = (timeout_ms > LONG_MAX) ? LONG_MAX : (long)timeout_ms;
   if(timeout < 1000) {
     /* The alarm() function only provides integer second resolution, so if
        we want to wait less than one second we must bail out already now. */
@@ -1168,23 +881,12 @@
     prev_alarm = alarm(curlx_sltoui(timeout / 1000L));
   }
 
-#else /* !USE_ALARM_TIMEOUT */
-#ifndef CURLRES_ASYNCH
-  if(timeoutms)
-    infof(data, "timeout on name lookup is not supported");
-#else
-  (void)timeoutms;
-#endif
-#endif /* USE_ALARM_TIMEOUT */
-
   /* Perform the actual name resolution. This might be interrupted by an
-   * alarm if it takes too long.
-   */
-  result = Curl_resolv(data, hostname, port, ip_version, TRUE, entry);
+   * alarm if it takes too long. */
+  result = hostip_resolv(data, dns_queries, hostname, port, transport,
+                         for_proxy, timeout_ms, FALSE, presolv_id, entry);
 
-#ifdef USE_ALARM_TIMEOUT
 clean_up:
-
   if(!prev_alarm)
     /* deactivate a possibly active alarm before uninstalling the handler */
     alarm(0);
@@ -1227,364 +929,202 @@
     else
       alarm((unsigned int)alarm_set);
   }
-#endif /* USE_ALARM_TIMEOUT */
 
   return result;
 }
 
+#endif /* USE_ALARM_TIMEOUT */
+
 /*
- * Curl_resolv_unlink() releases a reference to the given cached DNS entry.
- * When the reference count reaches 0, the entry is destroyed. It is important
- * that only one unlink is made for each Curl_resolv() call.
+ * Curl_resolv() is the main name resolve function within libcurl. It resolves
+ * a name and returns a pointer to the entry in the 'entry' argument. This
+ * function might return immediately if we are using asynch resolves. See the
+ * return codes.
  *
- * May be called with 'data' == NULL for global cache.
+ * The cache entry we return will get its 'inuse' counter increased when this
+ * function is used. You MUST call Curl_dns_entry_unlink() later (when you are
+ * done using this struct) to decrease the reference counter again.
+ *
+ * If built with a synchronous resolver and use of signals is not
+ * disabled by the application, then a nonzero timeout will cause a
+ * timeout after the specified number of milliseconds. Otherwise, timeout
+ * is ignored.
+ *
+ * Return codes:
+ * CURLE_OK = success, *pdns set to non-NULL
+ * CURLE_AGAIN = resolving in progress, *pdns == NULL
+ * any other CURLcode error, *pdns == NULL
  */
-void Curl_resolv_unlink(struct Curl_easy *data, struct Curl_dns_entry **pdns)
+CURLcode Curl_resolv(struct Curl_easy *data,
+                     uint8_t dns_queries,
+                     const char *hostname,
+                     uint16_t port,
+                     uint8_t transport,
+                     bool for_proxy,
+                     timediff_t timeout_ms,
+                     uint32_t *presolv_id,
+                     struct Curl_dns_entry **pdns)
 {
-  if(*pdns) {
-    struct Curl_dnscache *dnscache = dnscache_get(data);
-    struct Curl_dns_entry *dns = *pdns;
-    *pdns = NULL;
-    dnscache_lock(data, dnscache);
-    dns->refcount--;
-    if(dns->refcount == 0)
-      dnscache_entry_free(dns);
-    dnscache_unlock(data, dnscache);
+  DEBUGASSERT(hostname && *hostname);
+  *presolv_id = 0;
+  *pdns = NULL;
+
+  if(timeout_ms < 0)
+    /* got an already expired timeout */
+    return CURLE_OPERATION_TIMEDOUT;
+  else if(!timeout_ms)
+    timeout_ms = CURL_TIMEOUT_RESOLVE_MS;
+
+#ifdef USE_ALARM_TIMEOUT
+  if(timeout_ms && data->set.no_signal) {
+    /* Cannot use ALARM when signals are disabled */
+    timeout_ms = 0;
   }
-}
+  if(timeout_ms && !Curl_doh_wanted(data)) {
+    return resolv_alarm_timeout(data, dns_queries, hostname, port, transport,
+                                for_proxy, timeout_ms, presolv_id, pdns);
+  }
+#endif /* !USE_ALARM_TIMEOUT */
 
-static void dnscache_entry_dtor(void *entry)
-{
-  struct Curl_dns_entry *dns = (struct Curl_dns_entry *)entry;
-  DEBUGASSERT(dns && (dns->refcount > 0));
-  dns->refcount--;
-  if(dns->refcount == 0)
-    dnscache_entry_free(dns);
-}
-
-/*
- * Curl_dnscache_init() inits a new DNS cache.
- */
-void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size)
-{
-  Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare,
-                 dnscache_entry_dtor);
-}
-
-void Curl_dnscache_destroy(struct Curl_dnscache *dns)
-{
-  Curl_hash_destroy(&dns->entries);
-}
-
-CURLcode Curl_loadhostpairs(struct Curl_easy *data)
-{
-  struct Curl_dnscache *dnscache = dnscache_get(data);
-  struct curl_slist *hostp;
-
-  if(!dnscache)
-    return CURLE_FAILED_INIT;
-
-  /* Default is no wildcard found */
-  data->state.wildcard_resolve = FALSE;
-
-  for(hostp = data->state.resolve; hostp; hostp = hostp->next) {
-    char entry_id[MAX_HOSTCACHE_LEN];
-    const char *host = hostp->data;
-    struct Curl_str source;
-    if(!host)
-      continue;
-    if(*host == '-') {
-      curl_off_t num = 0;
-      size_t entry_len;
-      host++;
-      if(!curlx_str_single(&host, '[')) {
-        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
-           curlx_str_single(&host, ']') ||
-           curlx_str_single(&host, ':'))
-          continue;
-      }
-      else {
-        if(curlx_str_until(&host, &source, 4096, ':') ||
-           curlx_str_single(&host, ':')) {
-          continue;
-        }
-      }
-
-      if(!curlx_str_number(&host, &num, 0xffff)) {
-        /* Create an entry id, based upon the hostname and port */
-        entry_len = create_dnscache_id(curlx_str(&source),
-                                       curlx_strlen(&source), (int)num,
-                                       entry_id, sizeof(entry_id));
-        dnscache_lock(data, dnscache);
-        /* delete entry, ignore if it did not exist */
-        Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
-        dnscache_unlock(data, dnscache);
-      }
-    }
-    else {
-      struct Curl_dns_entry *dns;
-      struct Curl_addrinfo *head = NULL, *tail = NULL;
-      size_t entry_len;
-      char address[64];
-      curl_off_t port = 0;
-      bool permanent = TRUE;
-      bool error = TRUE;
-      VERBOSE(const char *addresses = NULL);
-
-      if(*host == '+') {
-        host++;
-        permanent = FALSE;
-      }
-      if(!curlx_str_single(&host, '[')) {
-        if(curlx_str_until(&host, &source, MAX_IPADR_LEN, ']') ||
-           curlx_str_single(&host, ']'))
-          continue;
-      }
-      else {
-        if(curlx_str_until(&host, &source, 4096, ':'))
-          continue;
-      }
-      if(curlx_str_single(&host, ':') ||
-         curlx_str_number(&host, &port, 0xffff) ||
-         curlx_str_single(&host, ':'))
-        goto err;
-
-      VERBOSE(addresses = host);
-
-      /* start the address section */
-      while(*host) {
-        struct Curl_str target;
-        struct Curl_addrinfo *ai;
-        CURLcode result;
-
-        if(!curlx_str_single(&host, '[')) {
-          if(curlx_str_until(&host, &target, MAX_IPADR_LEN, ']') ||
-             curlx_str_single(&host, ']'))
-            goto err;
-        }
-        else {
-          if(curlx_str_until(&host, &target, 4096, ',')) {
-            if(curlx_str_single(&host, ','))
-              goto err;
-            /* survive nothing but a comma */
-            continue;
-          }
-        }
-#ifndef USE_IPV6
-        if(memchr(curlx_str(&target), ':', curlx_strlen(&target))) {
-          infof(data, "Ignoring resolve address '%.*s', missing IPv6 support.",
-                (int)curlx_strlen(&target), curlx_str(&target));
-          if(curlx_str_single(&host, ','))
-            goto err;
-          continue;
-        }
+#ifndef CURLRES_ASYNCH
+  if(timeout_ms)
+    infof(data, "timeout on name lookup is not supported");
 #endif
 
-        if(curlx_strlen(&target) >= sizeof(address))
-          goto err;
-
-        memcpy(address, curlx_str(&target), curlx_strlen(&target));
-        address[curlx_strlen(&target)] = '\0';
-
-        result = Curl_str2addr(address, (int)port, &ai);
-        if(result) {
-          infof(data, "Resolve address '%s' found illegal", address);
-          goto err;
-        }
-
-        if(tail) {
-          tail->ai_next = ai;
-          tail = tail->ai_next;
-        }
-        else {
-          head = tail = ai;
-        }
-        if(curlx_str_single(&host, ','))
-          break;
-      }
-
-      if(!head)
-        goto err;
-
-      error = FALSE;
-err:
-      if(error) {
-        failf(data, "Could not parse CURLOPT_RESOLVE entry '%s'", hostp->data);
-        Curl_freeaddrinfo(head);
-        return CURLE_SETOPT_OPTION_SYNTAX;
-      }
-
-      /* Create an entry id, based upon the hostname and port */
-      entry_len = create_dnscache_id(curlx_str(&source), curlx_strlen(&source),
-                                     (int)port,
-                                     entry_id, sizeof(entry_id));
-
-      dnscache_lock(data, dnscache);
-
-      /* See if it is already in our dns cache */
-      dns = Curl_hash_pick(&dnscache->entries, entry_id, entry_len + 1);
-
-      if(dns) {
-        infof(data, "RESOLVE %.*s:%" CURL_FORMAT_CURL_OFF_T
-              " - old addresses discarded", (int)curlx_strlen(&source),
-              curlx_str(&source), port);
-        /* delete old entry, there are two reasons for this
-         1. old entry may have different addresses.
-         2. even if entry with correct addresses is already in the cache,
-            but if it is close to expire, then by the time next http
-            request is made, it can get expired and pruned because old
-            entry is not necessarily marked as permanent.
-         3. when adding a non-permanent entry, we want it to remove and
-            replace an existing permanent entry.
-         4. when adding a non-permanent entry, we want it to get a "fresh"
-            timeout that starts _now_. */
-
-        Curl_hash_delete(&dnscache->entries, entry_id, entry_len + 1);
-      }
-
-      /* put this new host in the cache */
-      dns = dnscache_add_addr(data, dnscache, &head, curlx_str(&source),
-                              curlx_strlen(&source), (int)port, permanent);
-      if(dns)
-        /* release the returned reference; the cache itself will keep the
-         * entry alive: */
-        dns->refcount--;
-
-      dnscache_unlock(data, dnscache);
-
-      if(!dns)
-        return CURLE_OUT_OF_MEMORY;
-
-      infof(data, "Added %.*s:%" CURL_FORMAT_CURL_OFF_T ":%s to DNS cache%s",
-            (int)curlx_strlen(&source), curlx_str(&source), port, addresses,
-            permanent ? "" : " (non-permanent)");
-
-      /* Wildcard hostname */
-      if(curlx_str_casecompare(&source, "*")) {
-        infof(data, "RESOLVE *:%" CURL_FORMAT_CURL_OFF_T " using wildcard",
-              port);
-        data->state.wildcard_resolve = TRUE;
-      }
-    }
-  }
-  data->state.resolve = NULL; /* dealt with now */
-
-  return CURLE_OK;
+  return hostip_resolv(data, dns_queries, hostname, port, transport,
+                       for_proxy, timeout_ms, TRUE, presolv_id, pdns);
 }
 
 #ifdef USE_CURL_ASYNC
-CURLcode Curl_resolv_check(struct Curl_easy *data,
-                           struct Curl_dns_entry **dns)
+
+struct Curl_resolv_async *Curl_async_get(struct Curl_easy *data,
+                                         uint32_t resolv_id)
 {
+  struct Curl_resolv_async *async = data->state.async;
+  for(; async; async = async->next) {
+    if(async->id == resolv_id)
+      return async;
+  }
+  return NULL;
+}
+
+CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id,
+                                 struct Curl_dns_entry **pdns)
+{
+  struct Curl_resolv_async *async = Curl_async_get(data, resolv_id);
   CURLcode result;
 
   /* If async resolving is ongoing, this must be set */
-  if(!data->state.async.hostname)
+  if(!async)
     return CURLE_FAILED_INIT;
 
   /* check if we have the name resolved by now (from someone else) */
-  *dns = Curl_dnscache_get(data, data->state.async.hostname,
-                           data->state.async.port,
-                           data->state.async.ip_version);
-  if(*dns) {
+  result = Curl_dnscache_get(data, async->dns_queries,
+                             async->hostname, async->port, pdns);
+  if(*pdns) {
     /* Tell a possibly async resolver we no longer need the results. */
-    infof(data, "Hostname '%s' was found in DNS cache",
-          data->state.async.hostname);
-    Curl_async_shutdown(data);
-    data->state.async.dns = *dns;
-    data->state.async.done = TRUE;
+    infof(data, "Hostname '%s' was found in DNS cache", async->hostname);
+    Curl_async_shutdown(data, async);
     return CURLE_OK;
   }
-
-#ifndef CURL_DISABLE_DOH
-  if(data->conn->bits.doh) {
-    result = Curl_doh_is_resolved(data, dns);
-    if(result)
-      Curl_resolver_error(data, NULL);
+  else if(result) {
+    Curl_async_shutdown(data, async);
+    return Curl_async_failed(data, async, NULL);
   }
-  else
-#endif
-  result = Curl_async_is_resolved(data, dns);
-  if(*dns)
-    show_resolve_info(data, *dns);
-  if((result == CURLE_COULDNT_RESOLVE_HOST) ||
-     (result == CURLE_COULDNT_RESOLVE_PROXY))
-    store_negative_resolve(data, data->state.async.hostname,
-                           data->state.async.port);
+
+  result = hostip_resolv_take_result(data, async, pdns);
+
+  if(*pdns) {
+    /* Add to cache */
+    result = Curl_dnscache_add(data, *pdns);
+    if(result)
+      Curl_dns_entry_unlink(data, pdns);
+  }
+  else if(IS_RESOLV_FAIL(result)) {
+    Curl_dnscache_add_negative(data, async->dns_queries,
+                               async->hostname, async->port);
+    failf(data, "Could not resolve: %s:%u", async->hostname, async->port);
+  }
+  else if(result) {
+    failf(data, "Error %d resolving %s:%u",
+          result, async->hostname, async->port);
+  }
   return result;
 }
-#endif
 
 CURLcode Curl_resolv_pollset(struct Curl_easy *data,
                              struct easy_pollset *ps)
 {
-#ifdef CURLRES_ASYNCH
-#ifndef CURL_DISABLE_DOH
-  if(data->conn->bits.doh)
-    /* nothing to wait for during DoH resolve, those handles have their own
-       sockets */
-    return CURLE_OK;
-#endif
-  return Curl_async_pollset(data, ps);
-#else
-  (void)data;
+  struct Curl_resolv_async *async = data->state.async;
+  CURLcode result = CURLE_OK;
+
   (void)ps;
-  return CURLE_OK;
+  for(; async && !result; async = async->next) {
+#ifndef CURL_DISABLE_DOH
+    if(async->doh) /* DoH has nothing for the pollset */
+      continue;
 #endif
-}
-
-/* Call this function after Curl_connect() has returned async=TRUE and
-   then a successful name resolve has been received.
-
-   Note: this function disconnects and frees the conn data in case of
-   resolve failure */
-CURLcode Curl_once_resolved(struct Curl_easy *data,
-                            struct Curl_dns_entry *dns,
-                            bool *protocol_done)
-{
-  CURLcode result;
-  struct connectdata *conn = data->conn;
-
-#ifdef USE_CURL_ASYNC
-  if(data->state.async.dns) {
-    DEBUGASSERT(data->state.async.dns == dns);
-    data->state.async.dns = NULL;
-  }
-#endif
-
-  result = Curl_setup_conn(data, dns, protocol_done);
-
-  if(result) {
-    Curl_detach_connection(data);
-    Curl_conn_terminate(data, conn, TRUE);
+    result = Curl_async_pollset(data, async, ps);
   }
   return result;
 }
 
-/*
- * Curl_resolver_error() calls failf() with the appropriate message after a
- * resolve error
- */
-
-#ifdef USE_CURL_ASYNC
-CURLcode Curl_resolver_error(struct Curl_easy *data, const char *detail)
+void Curl_resolv_destroy(struct Curl_easy *data, uint32_t resolv_id)
 {
-  struct connectdata *conn = data->conn;
-  const char *host_or_proxy = "host";
-  const char *name = conn->host.dispname;
-  CURLcode result = CURLE_COULDNT_RESOLVE_HOST;
+  struct Curl_resolv_async **panchor = &data->state.async;
 
-#ifndef CURL_DISABLE_PROXY
-  if(conn->bits.proxy) {
-    host_or_proxy = "proxy";
-    result = CURLE_COULDNT_RESOLVE_PROXY;
-    name = conn->socks_proxy.host.name ? conn->socks_proxy.host.dispname :
-      conn->http_proxy.host.dispname;
+  for(; *panchor; panchor = &(*panchor)->next) {
+    struct Curl_resolv_async *async = *panchor;
+    if(async->id == resolv_id) {
+      *panchor = async->next;
+      Curl_async_destroy(data, async);
+      break;
+    }
   }
-#endif
-
-  failf(data, "Could not resolve %s: %s%s%s%s", host_or_proxy, name,
-        detail ? " (" : "", detail ? detail : "", detail ? ")" : "");
-  return result;
 }
+
+void Curl_resolv_shutdown_all(struct Curl_easy *data)
+{
+  struct Curl_resolv_async *async = data->state.async;
+  for(; async; async = async->next) {
+    Curl_async_shutdown(data, async);
+  }
+}
+
+void Curl_resolv_destroy_all(struct Curl_easy *data)
+{
+  while(data->state.async) {
+    struct Curl_resolv_async *async = data->state.async;
+    data->state.async = async->next;
+    Curl_async_destroy(data, async);
+  }
+}
+
 #endif /* USE_CURL_ASYNC */
+
+#ifdef USE_UNIX_SOCKETS
+CURLcode Curl_resolv_unix(struct Curl_easy *data,
+                          const char *unix_path,
+                          bool abstract_path,
+                          struct Curl_dns_entry **pdns)
+{
+  struct Curl_addrinfo *addr;
+  CURLcode result;
+
+  DEBUGASSERT(unix_path);
+  *pdns = NULL;
+
+  result = Curl_unix2addr(unix_path, abstract_path, &addr);
+  if(result) {
+    if(result == CURLE_TOO_LARGE) {
+      /* Long paths are not supported for now */
+      failf(data, "Unix socket path too long: '%s'", unix_path);
+      result = CURLE_COULDNT_RESOLVE_HOST;
+    }
+    return result;
+  }
+
+  *pdns = Curl_dnscache_mk_entry(data, 0, &addr, NULL, 0);
+  return *pdns ? CURLE_OK : CURLE_OUT_OF_MEMORY;
+}
+#endif /* USE_UNIX_SOCKETS */
diff --git a/Utilities/cmcurl/lib/hostip.h b/Utilities/cmcurl/lib/hostip.h
index 6c7f129..780fb4d 100644
--- a/Utilities/cmcurl/lib/hostip.h
+++ b/Utilities/cmcurl/lib/hostip.h
@@ -35,8 +35,7 @@
  */
 #define CURL_HOSTENT_SIZE 9000
 
-#define CURL_TIMEOUT_RESOLVE 300 /* when using asynch methods, we allow this
-                                    many seconds for a name resolve */
+#define CURL_TIMEOUT_RESOLVE_MS (300 * 1000)
 
 struct addrinfo;
 struct hostent;
@@ -45,6 +44,22 @@
 struct easy_pollset;
 struct Curl_https_rrinfo;
 struct Curl_multi;
+struct Curl_dns_entry;
+
+/* DNS query types */
+#define CURL_DNSQ_A           (1U << 0)
+#define CURL_DNSQ_AAAA        (1U << 1)
+#define CURL_DNSQ_HTTPS       (1U << 2)
+
+#define CURL_DNSQ_ALL         (CURL_DNSQ_A | CURL_DNSQ_AAAA | CURL_DNSQ_HTTPS)
+#define CURL_DNSQ_IP(x)       (uint8_t)((x)&(CURL_DNSQ_A | CURL_DNSQ_AAAA))
+
+#ifdef CURLVERBOSE
+const char *Curl_resolv_query_str(uint8_t dns_queries);
+#endif
+
+/* Return CURL_DNSQ_* bits for the transfer and ip_version. */
+uint8_t Curl_resolv_dns_queries(struct Curl_easy *data, uint8_t ip_version);
 
 enum alpnid {
   ALPN_none = 0,
@@ -53,151 +68,108 @@
   ALPN_h3 = CURLALTSVC_H3
 };
 
-struct Curl_dns_entry {
-  struct Curl_addrinfo *addr;
-#ifdef USE_HTTPSRR
-  struct Curl_https_rrinfo *hinfo;
-#endif
-  /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */
-  struct curltime timestamp;
-  /* reference counter, entry is freed on reaching 0 */
-  size_t refcount;
-  /* hostname port number that resolved to addr. */
-  int hostport;
-  /* hostname that resolved to addr. may be NULL (Unix domain sockets). */
-  char hostname[1];
-};
-
-struct Curl_dnscache {
-  struct Curl_hash entries;
-};
-
 bool Curl_host_is_ipnum(const char *hostname);
 
-/*
- * Curl_resolv() returns an entry with the info for the specified host
- * and port.
- *
- * The returned data *MUST* be "released" with Curl_resolv_unlink() after
- * use, or we will leak memory!
- */
-CURLcode Curl_resolv(struct Curl_easy *data,
-                     const char *hostname,
-                     int port,
-                     int ip_version,
-                     bool allowDOH,
-                     struct Curl_dns_entry **entry);
-
-CURLcode Curl_resolv_blocking(struct Curl_easy *data,
-                              const char *hostname,
-                              int port,
-                              int ip_version,
-                              struct Curl_dns_entry **entry);
-
-CURLcode Curl_resolv_timeout(struct Curl_easy *data,
-                             const char *hostname, int port,
-                             int ip_version,
-                             struct Curl_dns_entry **entry,
-                             timediff_t timeoutms);
-
 #ifdef USE_IPV6
-
 /* probe if it seems to work */
 CURLcode Curl_probeipv6(struct Curl_multi *multi);
-/*
- * Curl_ipv6works() returns TRUE if IPv6 seems to work.
- */
-bool Curl_ipv6works(struct Curl_easy *data);
 #else
 #define Curl_probeipv6(x) CURLE_OK
-#define Curl_ipv6works(x) FALSE
 #endif
 
-/* unlink a dns entry, potentially shared with a cache */
-void Curl_resolv_unlink(struct Curl_easy *data,
-                        struct Curl_dns_entry **pdns);
-
-/* init a new dns cache */
-void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size);
-
-void Curl_dnscache_destroy(struct Curl_dnscache *dns);
-
-/* prune old entries from the DNS cache */
-void Curl_dnscache_prune(struct Curl_easy *data);
-
-/* clear the DNS cache */
-void Curl_dnscache_clear(struct Curl_easy *data);
-
 /* IPv4 thread-safe resolve function used for synch and asynch builds */
-struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port);
-
-CURLcode Curl_once_resolved(struct Curl_easy *data,
-                            struct Curl_dns_entry *dns,
-                            bool *protocol_done);
+struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, uint16_t port);
 
 /*
  * Curl_printable_address() returns a printable version of the 1st address
- * given in the 'ip' argument. The result will be stored in the buf that is
+ * given in the 'ai' argument. The result will be stored in the buf that is
  * bufsize bytes big.
  */
 void Curl_printable_address(const struct Curl_addrinfo *ai,
                             char *buf, size_t bufsize);
 
-/*
- * Make a `Curl_dns_entry`.
- * Creates a dnscache entry *without* adding it to a dnscache. This allows
- * further modifications of the entry *before* then adding it to a cache.
- *
- * The entry is created with a reference count of 1.
- * Use `Curl_resolv_unlink()` to release your hold on it.
- *
- * The call takes ownership of `addr`, even in case of failure, and always
- * clears `*paddr`. It makes a copy of `hostname`.
- *
- * Returns entry or NULL on OOM.
+/* Start DNS resolving for the given parameters. Returns
+ * - CURLE_OK: `*pdns` is the resolved DNS entry (needs to be unlinked).
+    *          `*presolv_id` is 0.
+ * - CURLE_AGAIN: resolve is asynchronous and not finished yet.
+ *             `presolv_id` is the identifier for querying results later.
+ * - other: the operation failed, `*pdns` is NULL, `*presolv_id` is 0.
  */
-struct Curl_dns_entry *
-Curl_dnscache_mk_entry(struct Curl_easy *data,
-                       struct Curl_addrinfo **paddr,
-                       const char *hostname,
-                       size_t hostlen, /* length or zero */
-                       int port,
-                       bool permanent);
+CURLcode Curl_resolv(struct Curl_easy *data,
+                     uint8_t dns_queries,
+                     const char *hostname,
+                     uint16_t port,
+                     uint8_t transport,
+                     bool for_proxy,
+                     timediff_t timeout_ms,
+                     uint32_t *presolv_id,
+                     struct Curl_dns_entry **pdns);
 
-/*
- * Curl_dnscache_get() fetches a 'Curl_dns_entry' already in the DNS cache.
- *
- * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
- *
- * The returned data *MUST* be "released" with Curl_resolv_unlink() after
- * use, or we will leak memory!
- */
-struct Curl_dns_entry *Curl_dnscache_get(struct Curl_easy *data,
-                                         const char *hostname, int port,
-                                         int ip_version);
+CURLcode Curl_resolv_blocking(struct Curl_easy *data,
+                              uint8_t dns_queries,
+                              const char *hostname,
+                              uint16_t port,
+                              uint8_t transport,
+                              struct Curl_dns_entry **pdns);
 
-/*
- * Curl_dnscache_addr() adds `entry` to the cache, increasing its
- * reference count on success.
- */
-CURLcode Curl_dnscache_add(struct Curl_easy *data,
-                           struct Curl_dns_entry *entry);
-
-/*
- * Populate the cache with specified entries from CURLOPT_RESOLVE.
- */
-CURLcode Curl_loadhostpairs(struct Curl_easy *data);
+/* Announce start of a resolve operation to application callback,
+ * passing the resolver implementation (maybe NULL). */
+CURLcode Curl_resolv_announce_start(struct Curl_easy *data,
+                                    void *resolver);
 
 #ifdef USE_CURL_ASYNC
-CURLcode Curl_resolv_check(struct Curl_easy *data,
-                           struct Curl_dns_entry **dns);
-#else
-#define Curl_resolv_check(x, y) CURLE_NOT_BUILT_IN
-#endif
+
 CURLcode Curl_resolv_pollset(struct Curl_easy *data,
                              struct easy_pollset *ps);
 
-CURLcode Curl_resolver_error(struct Curl_easy *data, const char *detail);
+/* Get the `async` struct for the given `resolv_id`, if it exists. */
+struct Curl_resolv_async *Curl_async_get(struct Curl_easy *data,
+                                         uint32_t resolv_id);
+
+/* Shut down all resolves of the given easy handle. */
+void Curl_resolv_shutdown_all(struct Curl_easy *data);
+
+/* Destroy all resolve resources of the given easy handle. */
+void Curl_resolv_destroy_all(struct Curl_easy *data);
+
+CURLcode Curl_resolv_take_result(struct Curl_easy *data, uint32_t resolv_id,
+                                 struct Curl_dns_entry **pdns);
+
+void Curl_resolv_destroy(struct Curl_easy *data, uint32_t resolv_id);
+
+/* How much time has gone by since start of resolve.
+ * Returns CURL_TIMEOUT_RESOLVE_MS if `resolv_id` is no longer valid. */
+timediff_t Curl_resolv_elapsed_ms(struct Curl_easy *data,
+                                  uint32_t resolv_id);
+
+/* Return TRUE if `resolv_id` has answers (positive or negative) to
+ * all queries in `dns_queries`.
+ * Queries not requested are considered answered. */
+bool Curl_resolv_has_answers(struct Curl_easy *data,
+                             uint32_t resolv_id, uint8_t dns_queries);
+
+const struct Curl_addrinfo *Curl_resolv_get_ai(struct Curl_easy *data,
+                                               uint32_t resolv_id,
+                                               int ai_family,
+                                               unsigned int index);
+#ifdef USE_HTTPSRR
+const struct Curl_https_rrinfo *Curl_resolv_get_https(struct Curl_easy *data,
+                                                      uint32_t resolv_id);
+bool Curl_resolv_knows_https(struct Curl_easy *data, uint32_t resolv_id);
+#endif /* USE_HTTPSRR */
+
+#else /* !USE_CURL_ASYNC */
+#define Curl_resolv_shutdown_all(x)      Curl_nop_stmt
+#define Curl_resolv_destroy_all(x)       Curl_nop_stmt
+#define Curl_resolv_take_result(x, y, z) CURLE_NOT_BUILT_IN
+#define Curl_resolv_elapsed_ms(x, y)     CURL_TIMEOUT_RESOLVE_MS
+#define Curl_resolv_has_answers(x, y, z) TRUE
+#define Curl_resolv_get_ai(x, y, z, a)   NULL
+#define Curl_resolv_get_https(x, y)      NULL
+#define Curl_resolv_knows_https(x, y)    TRUE
+#define Curl_resolv_pollset(x, y)        CURLE_OK
+#define Curl_resolv_destroy(x, y)        Curl_nop_stmt
+#endif /* USE_CURL_ASYNC */
 
 #ifdef CURLRES_SYNCH
 /*
@@ -206,10 +178,17 @@
  * support and platform.
  */
 struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data,
+                                            uint8_t dns_queries,
                                             const char *hostname,
-                                            int port,
-                                            int ip_version);
+                                            uint16_t port,
+                                            uint8_t transport);
+#endif
 
+#ifdef USE_UNIX_SOCKETS
+CURLcode Curl_resolv_unix(struct Curl_easy *data,
+                          const char *unix_path,
+                          bool abstract_path,
+                          struct Curl_dns_entry **pdns);
 #endif
 
 #endif /* HEADER_CURL_HOSTIP_H */
diff --git a/Utilities/cmcurl/lib/hostip4.c b/Utilities/cmcurl/lib/hostip4.c
index f8cfac1..fb35e39 100644
--- a/Utilities/cmcurl/lib/hostip4.c
+++ b/Utilities/cmcurl/lib/hostip4.c
@@ -68,13 +68,15 @@
  *
  */
 struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data,
+                                            uint8_t dns_queries,
                                             const char *hostname,
-                                            int port,
-                                            int ip_version)
+                                            uint16_t port,
+                                            uint8_t transport)
 {
   struct Curl_addrinfo *ai = NULL;
 
-  (void)ip_version;
+  (void)dns_queries;
+  (void)transport;
 
   ai = Curl_ipv4_resolve_r(hostname, port);
   if(!ai)
@@ -85,7 +87,8 @@
 #endif /* CURLRES_SYNCH */
 #endif /* CURLRES_IPV4 */
 
-#if defined(CURLRES_IPV4) && !defined(CURLRES_ARES) && !defined(CURLRES_AMIGA)
+#if defined(CURLRES_IPV4) && !defined(USE_RESOLV_ARES) && \
+  !defined(CURLRES_AMIGA)
 
 /*
  * Curl_ipv4_resolve_r() - ipv4 thread-safe resolver function.
@@ -95,7 +98,7 @@
  *
  */
 struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname,
-                                          int port)
+                                          uint16_t port)
 {
 #if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) && \
   defined(HAVE_GETHOSTBYNAME_R_3)
@@ -277,4 +280,4 @@
 
   return ai;
 }
-#endif /* CURLRES_IPV4 && !CURLRES_ARES && !CURLRES_AMIGA */
+#endif /* CURLRES_IPV4 && !USE_RESOLV_ARES && !CURLRES_AMIGA */
diff --git a/Utilities/cmcurl/lib/hostip6.c b/Utilities/cmcurl/lib/hostip6.c
index 8aa1974..7412f42 100644
--- a/Utilities/cmcurl/lib/hostip6.c
+++ b/Utilities/cmcurl/lib/hostip6.c
@@ -63,9 +63,10 @@
  * Curl_freeaddrinfo(), nothing else.
  */
 struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data,
+                                            uint8_t dns_queries,
                                             const char *hostname,
-                                            int port,
-                                            int ip_version)
+                                            uint16_t port,
+                                            uint8_t transport)
 {
   struct addrinfo hints;
   struct Curl_addrinfo *res;
@@ -77,14 +78,12 @@
 #endif
   int pf = PF_INET;
 
-  if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data))
-    /* The stack seems to be IPv6-enabled */
+  if(dns_queries & CURL_DNSQ_AAAA)
     pf = PF_UNSPEC;
 
   memset(&hints, 0, sizeof(hints));
   hints.ai_family = pf;
-  hints.ai_socktype =
-    (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ?
+  hints.ai_socktype = (transport == TRNSPRT_TCP) ?
     SOCK_STREAM : SOCK_DGRAM;
 
 #ifndef USE_RESOLVE_ON_IPS
diff --git a/Utilities/cmcurl/lib/hsts.c b/Utilities/cmcurl/lib/hsts.c
index 03f51f8..400b442 100644
--- a/Utilities/cmcurl/lib/hsts.c
+++ b/Utilities/cmcurl/lib/hsts.c
@@ -40,7 +40,7 @@
 
 #define MAX_HSTS_LINE    4095
 #define MAX_HSTS_HOSTLEN 2048
-#define MAX_HSTS_DATELEN 256
+#define MAX_HSTS_DATELEN 17
 #define UNLIMITED        "unlimited"
 
 #if defined(DEBUGBUILD) || defined(UNITTESTS)
@@ -91,6 +91,20 @@
   }
 }
 
+/* append the new entry to the list after possibly removing an old entry
+   first */
+static void hsts_append(struct hsts *h, struct stsentry *sts)
+{
+  if(Curl_llist_count(&h->list) == MAX_HSTS_ENTRIES) {
+    /* It's full. Remove the first entry in the list */
+    struct Curl_llist_node *e = Curl_llist_head(&h->list);
+    struct stsentry *oldsts = Curl_node_elem(e);
+    Curl_node_remove(e);
+    hsts_free(oldsts);
+  }
+  Curl_llist_append(&h->list, sts, &sts->node);
+}
+
 static CURLcode hsts_create(struct hsts *h,
                             const char *hostname,
                             size_t hlen,
@@ -111,7 +125,7 @@
     memcpy(sts->host, hostname, hlen);
     sts->expires = expires;
     sts->includeSubDomains = subdomains;
-    Curl_llist_append(&h->list, sts, &sts->node);
+    hsts_append(h, sts);
   }
   return CURLE_OK;
 }
@@ -386,6 +400,61 @@
 }
 
 /* only returns SERIOUS errors */
+static CURLcode hsts_add_host_expire(struct hsts *h,
+                                     const char *host, size_t hostlen,
+                                     const char *expire, size_t explen,
+                                     bool subdomain) /* default */
+{
+  CURLcode result = CURLE_OK;
+  struct stsentry *e;
+  char dbuf[MAX_HSTS_DATELEN + 1];
+  time_t expires = 0;
+  time_t now = time(NULL);
+
+  /* The date parser works on a null-terminated string. */
+  if(explen > MAX_HSTS_DATELEN)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  memcpy(dbuf, expire, explen);
+  dbuf[explen] = 0;
+
+  if(!strcmp(dbuf, UNLIMITED))
+    expires = TIME_T_MAX;
+  else
+    Curl_getdate_capped(dbuf, &expires);
+
+  if(expires <= now)
+    /* this entry already expired */
+    return CURLE_OK;
+
+  if(host[0] == '.') {
+    host++;
+    hostlen--;
+    subdomain = TRUE;
+  }
+  if(hostlen && (host[hostlen - 1] == '.'))
+    /* strip off any trailing dot */
+    hostlen--;
+
+  if(hostlen) {
+    /* only add it if not already present */
+    e = Curl_hsts(h, host, hostlen, subdomain);
+    if(!e)
+      result = hsts_create(h, host, hostlen, subdomain, expires);
+    /* 'host' is not necessarily null terminated */
+    else if((hostlen == strlen(e->host) &&
+             curl_strnequal(host, e->host, hostlen))) {
+      /* the same hostname, use the largest expire time and keep the strictest
+         subdomain policy */
+      if(expires > e->expires)
+        e->expires = expires;
+      if(subdomain)
+        e->includeSubDomains = TRUE;
+    }
+  }
+  return result;
+}
+
+/* only returns SERIOUS errors */
 static CURLcode hsts_add(struct hsts *h, const char *line)
 {
   /* Example lines:
@@ -401,39 +470,9 @@
      curlx_str_newline(&line))
     ;
   else {
-    CURLcode result = CURLE_OK;
-    bool subdomain = FALSE;
-    struct stsentry *e;
-    char dbuf[MAX_HSTS_DATELEN + 1];
-    time_t expires = 0;
-    const char *hp = curlx_str(&host);
-
-    /* The date parser works on a null-terminated string. The maximum length
-       is upheld by curlx_str_quotedword(). */
-    memcpy(dbuf, curlx_str(&date), curlx_strlen(&date));
-    dbuf[curlx_strlen(&date)] = 0;
-
-    if(!strcmp(dbuf, UNLIMITED))
-      expires = TIME_T_MAX;
-    else
-      Curl_getdate_capped(dbuf, &expires);
-
-    if(hp[0] == '.') {
-      curlx_str_nudge(&host, 1);
-      subdomain = TRUE;
-    }
-    /* only add it if not already present */
-    e = Curl_hsts(h, curlx_str(&host), curlx_strlen(&host), subdomain);
-    if(!e)
-      result = hsts_create(h, curlx_str(&host), curlx_strlen(&host),
-                           subdomain, expires);
-    else if(curlx_str_casecompare(&host, e->host)) {
-      /* the same hostname, use the largest expire time */
-      if(expires > e->expires)
-        e->expires = expires;
-    }
-    if(result)
-      return result;
+    return hsts_add_host_expire(h, curlx_str(&host), curlx_strlen(&host),
+                                curlx_str(&date), curlx_strlen(&date),
+                                FALSE);
   }
 
   return CURLE_OK;
@@ -456,23 +495,23 @@
       e.namelen = sizeof(buffer) - 1;
       e.includeSubDomains = FALSE; /* default */
       e.expire[0] = 0;
+      e.expire[MAX_HSTS_DATELEN] = 0;
       e.name[0] = 0; /* to make it clean */
+      e.name[MAX_HSTS_HOSTLEN] = 0;
       sc = data->set.hsts_read(data, &e, data->set.hsts_read_userp);
       if(sc == CURLSTS_OK) {
-        time_t expires = 0;
         CURLcode result;
-        DEBUGASSERT(e.name[0]);
-        if(!e.name[0])
-          /* bail out if no name was stored */
+        const char *date = e.expire;
+        if(!e.name[0] || e.expire[MAX_HSTS_DATELEN] ||
+           e.name[MAX_HSTS_HOSTLEN])
+          /* bail out if no name was stored or if a null terminator is gone */
           return CURLE_BAD_FUNCTION_ARGUMENT;
-        if(e.expire[0])
-          Curl_getdate_capped(e.expire, &expires);
-        else
-          expires = TIME_T_MAX; /* the end of time */
-        result = hsts_create(h, e.name, strlen(e.name),
-                             /* bitfield to bool conversion: */
-                             e.includeSubDomains ? TRUE : FALSE,
-                             expires);
+        if(!date[0])
+          date = UNLIMITED;
+        result = hsts_add_host_expire(h, e.name, strlen(e.name),
+                                      date, strlen(date),
+                                      /* bitfield to bool conversion: */
+                                      e.includeSubDomains ? TRUE : FALSE);
         if(result)
           return result;
       }
@@ -505,26 +544,28 @@
 
   fp = curlx_fopen(file, FOPEN_READTEXT);
   if(fp) {
-    struct dynbuf buf;
-    bool eof = FALSE;
-    curlx_dyn_init(&buf, MAX_HSTS_LINE);
-    do {
-      result = Curl_get_line(&buf, fp, &eof);
-      if(!result) {
-        const char *lineptr = curlx_dyn_ptr(&buf);
-        curlx_str_passblanks(&lineptr);
+    curlx_struct_stat stat;
+    if((curlx_fstat(fileno(fp), &stat) == -1) || !S_ISDIR(stat.st_mode)) {
+      struct dynbuf buf;
+      bool eof = FALSE;
+      curlx_dyn_init(&buf, MAX_HSTS_LINE);
+      do {
+        result = Curl_get_line(&buf, fp, &eof);
+        if(!result) {
+          const char *lineptr = curlx_dyn_ptr(&buf);
+          curlx_str_passblanks(&lineptr);
 
-        /*
-         * Skip empty or commented lines, since we know the line will have a
-         * trailing newline from Curl_get_line we can treat length 1 as empty.
-         */
-        if((*lineptr == '#') || strlen(lineptr) <= 1)
-          continue;
+          /* Skip empty or commented lines, since we know the line will have
+             a trailing newline from Curl_get_line we can treat length 1 as
+             empty. */
+          if((*lineptr == '#') || strlen(lineptr) <= 1)
+            continue;
 
-        hsts_add(h, lineptr);
-      }
-    } while(!result && !eof);
-    curlx_dyn_free(&buf); /* free the line buffer */
+          hsts_add(h, lineptr);
+        }
+      } while(!result && !eof);
+      curlx_dyn_free(&buf); /* free the line buffer */
+    }
     curlx_fclose(fp);
   }
   return result;
diff --git a/Utilities/cmcurl/lib/hsts.h b/Utilities/cmcurl/lib/hsts.h
index e0f6363..0e6585f 100644
--- a/Utilities/cmcurl/lib/hsts.h
+++ b/Utilities/cmcurl/lib/hsts.h
@@ -28,6 +28,8 @@
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HSTS)
 #include "llist.h"
 
+#define MAX_HSTS_ENTRIES 10000
+
 #if defined(DEBUGBUILD) || defined(UNITTESTS)
 extern time_t deltatime;
 #endif
diff --git a/Utilities/cmcurl/lib/http.c b/Utilities/cmcurl/lib/http.c
index 188da5f..9118c7e 100644
--- a/Utilities/cmcurl/lib/http.c
+++ b/Utilities/cmcurl/lib/http.c
@@ -166,17 +166,29 @@
 }
 #endif
 
+/* If the header has a value, this function returns TRUE and the value is in
+   'outp' with blanks trimmed off.
+*/
+static bool header_has_value(const char **headerp, struct Curl_str *outp)
+{
+  bool value = !curlx_str_cspn(headerp, outp, ";:") &&
+    (!curlx_str_single(headerp, ':') || !curlx_str_single(headerp, ';'));
+
+  if(value) {
+    curlx_str_untilnl(headerp, outp, MAX_HTTP_RESP_HEADER_SIZE);
+    curlx_str_trimblanks(outp);
+  }
+  return value;
+}
+
 static bool http_header_is_empty(const char *header)
 {
   struct Curl_str out;
 
-  if(!curlx_str_cspn(&header, &out, ";:") &&
-     (!curlx_str_single(&header, ':') || !curlx_str_single(&header, ';'))) {
-    curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE);
-    curlx_str_trimblanks(&out);
+  if(header_has_value(&header, &out)) {
     return curlx_strlen(&out) == 0;
   }
-  return TRUE; /* invalid head format, treat as empty */
+  return TRUE; /* invalid header format, treat as empty */
 }
 
 /*
@@ -192,11 +204,7 @@
   struct Curl_str out;
 
   /* find the end of the header name */
-  if(!curlx_str_cspn(&header, &out, ";:") &&
-     (!curlx_str_single(&header, ':') || !curlx_str_single(&header, ';'))) {
-    curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE);
-    curlx_str_trimblanks(&out);
-
+  if(header_has_value(&header, &out)) {
     *valp = curlx_memdup0(curlx_str(&out), curlx_strlen(&out));
     if(*valp)
       return CURLE_OK;
@@ -214,6 +222,8 @@
  *
  * This function MUST be used after the header has already been confirmed to
  * lead with "word:".
+ *
+ * @unittest: 1626
  */
 char *Curl_copy_header_value(const char *header)
 {
@@ -254,7 +264,7 @@
      connection */
   if(proxy) {
 #ifndef CURL_DISABLE_PROXY
-    userp = &data->state.aptr.proxyuserpwd;
+    userp = &data->req.proxyuserpwd;
     user = data->state.aptr.proxyuser;
     pwd = data->state.aptr.proxypasswd;
 #else
@@ -262,7 +272,7 @@
 #endif
   }
   else {
-    userp = &data->state.aptr.userpwd;
+    userp = &data->req.userpwd;
     user = data->state.aptr.user;
     pwd = data->state.aptr.passwd;
   }
@@ -310,7 +320,7 @@
   char **userp;
   CURLcode result = CURLE_OK;
 
-  userp = &data->state.aptr.userpwd;
+  userp = &data->req.userpwd;
   curlx_free(*userp);
   *userp = curl_maprintf("Authorization: Bearer %s\r\n",
                          data->set.str[STRING_BEARER]);
@@ -470,53 +480,53 @@
   DEBUGASSERT(data->conn);
 
   /*
-  ** If we have not been asked to fail on error,
-  ** do not fail.
-  */
+   * If we have not been asked to fail on error,
+   * do not fail.
+   */
   if(!data->set.http_fail_on_error)
     return FALSE;
 
   /*
-  ** Any code < 400 is never terminal.
-  */
+   * Any code < 400 is never terminal.
+   */
   if(httpcode < 400)
     return FALSE;
 
   /*
-  ** A 416 response to a resume request is presumably because the file is
-  ** already completely downloaded and thus not actually a fail.
-  */
+   * A 416 response to a resume request is presumably because the file is
+   * already completely downloaded and thus not actually a fail.
+   */
   if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET &&
      httpcode == 416)
     return FALSE;
 
   /*
-  ** Any code >= 400 that is not 401 or 407 is always
-  ** a terminal error
-  */
+   * Any code >= 400 that is not 401 or 407 is always
+   * a terminal error
+   */
   if((httpcode != 401) && (httpcode != 407))
     return TRUE;
 
   /*
-  ** All we have left to deal with is 401 and 407
-  */
+   * All we have left to deal with is 401 and 407
+   */
   DEBUGASSERT((httpcode == 401) || (httpcode == 407));
 
   /*
-  ** Examine the current authentication state to see if this is an error. The
-  ** idea is for this function to get called after processing all the headers
-  ** in a response message. So, if we have been to asked to authenticate a
-  ** particular stage, and we have done it, we are OK. If we are already
-  ** completely authenticated, it is not OK to get another 401 or 407.
-  **
-  ** It is possible for authentication to go stale such that the client needs
-  ** to reauthenticate. Once that info is available, use it here.
-  */
+   * Examine the current authentication state to see if this is an error. The
+   * idea is for this function to get called after processing all the headers
+   * in a response message. If we have been to asked to authenticate
+   * a particular stage, and we have done it, we are OK. If we are already
+   * completely authenticated, it is not OK to get another 401 or 407.
+   *
+   * It is possible for authentication to go stale such that the client needs
+   * to reauthenticate. Once that info is available, use it here.
+   */
 
   /*
-  ** Either we are not authenticating, or we are supposed to be authenticating
-  ** something else. This is an error.
-  */
+   * Either we are not authenticating, or we are supposed to be authenticating
+   * something else. This is an error.
+   */
   if((httpcode == 401) && !data->state.aptr.user)
     return TRUE;
 #ifndef CURL_DISABLE_PROXY
@@ -721,6 +731,10 @@
 
   if(auth) {
 #ifndef CURL_DISABLE_PROXY
+    if(proxy)
+      data->info.proxyauthpicked = authstatus->picked;
+    else
+      data->info.httpauthpicked = authstatus->picked;
     infof(data, "%s auth using %s with user '%s'",
           proxy ? "Proxy" : "Server", auth,
           proxy ? (data->state.aptr.proxyuser ?
@@ -735,8 +749,13 @@
 #endif
     authstatus->multipass = !authstatus->done;
   }
-  else
+  else {
     authstatus->multipass = FALSE;
+    if(proxy)
+      data->info.proxyauthpicked = 0;
+    else
+      data->info.httpauthpicked = 0;
+  }
 
   return result;
 }
@@ -755,14 +774,14 @@
  *
  * @returns CURLcode
  */
-CURLcode
-Curl_http_output_auth(struct Curl_easy *data,
-                      struct connectdata *conn,
-                      const char *request,
-                      Curl_HttpReq httpreq,
-                      const char *path,
-                      bool proxytunnel) /* TRUE if this is the request setting
-                                           up the proxy tunnel */
+CURLcode Curl_http_output_auth(struct Curl_easy *data,
+                               struct connectdata *conn,
+                               const char *request,
+                               Curl_HttpReq httpreq,
+                               const char *path,
+                               bool proxytunnel) /* TRUE if this is
+                                                    the request setting up
+                                                    the proxy tunnel */
 {
   CURLcode result = CURLE_OK;
   struct auth *authhost;
@@ -943,8 +962,10 @@
                             struct auth *authp,
                             uint32_t *availp)
 {
-  if(authp->avail & CURLAUTH_DIGEST)
+  if(authp->avail & CURLAUTH_DIGEST) {
+    *availp |= CURLAUTH_DIGEST;
     infof(data, "Ignoring duplicate digest auth header.");
+  }
   else if(Curl_auth_is_digest_supported()) {
     CURLcode result;
 
@@ -1113,6 +1134,11 @@
   Curl_creader_set_rewind(data, FALSE);
 }
 
+#define HTTPREQ_IS_POST(data)                           \
+  ((data)->state.httpreq == HTTPREQ_POST ||             \
+   (data)->state.httpreq == HTTPREQ_POST_FORM ||        \
+   (data)->state.httpreq == HTTPREQ_POST_MIME)
+
 CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl,
                           followtype type)
 {
@@ -1201,60 +1227,43 @@
       return CURLE_OUT_OF_MEMORY;
   }
   else {
-    uc = curl_url_get(data->state.uh, CURLUPART_URL, &follow_url, 0);
-    if(uc)
+    bool same_origin;
+    CURLcode result;
+    CURLU *u = curl_url();
+    if(!u)
+      return CURLE_OUT_OF_MEMORY;
+    uc = curl_url_set(u, CURLUPART_URL,
+                      Curl_bufref_ptr(&data->state.url),
+                      CURLU_URLENCODE | CURLU_ALLOW_SPACE);
+    if(!uc)
+      uc = curl_url_get(data->state.uh, CURLUPART_URL, &follow_url, 0);
+    if(uc) {
+      curl_url_cleanup(u);
       return Curl_uc_to_curlcode(uc);
+    }
 
-    /* Clear auth if this redirects to a different port number or protocol,
-       unless permitted */
-    if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) {
-      int port;
-      bool clear = FALSE;
+    same_origin = Curl_url_same_origin(u, data->state.uh);
+    curl_url_cleanup(u);
 
-      if(data->set.use_port && data->state.allow_port)
-        /* a custom port is used */
-        port = (int)data->set.use_port;
-      else {
-        curl_off_t value;
-        char *portnum;
-        const char *p;
-        uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum,
-                          CURLU_DEFAULT_PORT);
-        if(uc) {
-          curlx_free(follow_url);
-          return Curl_uc_to_curlcode(uc);
-        }
-        p = portnum;
-        curlx_str_number(&p, &value, 0xffff);
-        port = (int)value;
-        curlx_free(portnum);
-      }
-      if(port != data->info.conn_remote_port) {
-        infof(data, "Clear auth, redirects to port from %u to %u",
-              data->info.conn_remote_port, port);
-        clear = TRUE;
-      }
-      else {
-        char *scheme;
-        const struct Curl_scheme *p;
-        uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0);
-        if(uc) {
-          curlx_free(follow_url);
-          return Curl_uc_to_curlcode(uc);
-        }
+#ifndef CURL_DISABLE_DIGEST_AUTH
+    if(!same_origin)
+      Curl_auth_digest_cleanup(&data->state.digest);
+#endif
 
-        p = Curl_get_scheme(scheme);
-        if(p && (p->protocol != data->info.conn_protocol)) {
-          infof(data, "Clear auth, redirects scheme from %s to %s",
-                data->info.conn_scheme, scheme);
-          clear = TRUE;
-        }
-        curlx_free(scheme);
+    if((!same_origin && !data->set.allow_auth_to_other_hosts) ||
+       !data->set.str[STRING_USERNAME]) {
+      result = Curl_reset_userpwd(data);
+      if(result) {
+        curlx_free(follow_url);
+        return result;
       }
-      if(clear) {
-        Curl_safefree(data->state.aptr.user);
-        Curl_safefree(data->state.aptr.passwd);
-      }
+      curlx_safefree(data->state.aptr.user);
+      curlx_safefree(data->state.aptr.passwd);
+    }
+    result = Curl_reset_proxypwd(data);
+    if(result) {
+      curlx_free(follow_url);
+      return result;
     }
   }
   DEBUGASSERT(follow_url);
@@ -1321,10 +1330,7 @@
      * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
      * can be overridden with CURLOPT_POSTREDIR.
      */
-    if((data->state.httpreq == HTTPREQ_POST ||
-        data->state.httpreq == HTTPREQ_POST_FORM ||
-        data->state.httpreq == HTTPREQ_POST_MIME) &&
-       !data->set.post301) {
+    if(HTTPREQ_IS_POST(data) && !data->set.post301) {
       http_switch_to_get(data, 301);
       switch_to_get = TRUE;
     }
@@ -1346,10 +1352,7 @@
      * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and
      * can be overridden with CURLOPT_POSTREDIR.
      */
-    if((data->state.httpreq == HTTPREQ_POST ||
-        data->state.httpreq == HTTPREQ_POST_FORM ||
-        data->state.httpreq == HTTPREQ_POST_MIME) &&
-       !data->set.post302) {
+    if(HTTPREQ_IS_POST(data) && !data->set.post302) {
       http_switch_to_get(data, 302);
       switch_to_get = TRUE;
     }
@@ -1359,13 +1362,8 @@
     /* 'See Other' location is not the resource but a substitute for the
      * resource. In this case we switch the method to GET/HEAD, unless the
      * method is POST and the user specified to keep it as POST.
-     * https://github.com/curl/curl/issues/5237#issuecomment-614641049
      */
-    if(data->state.httpreq != HTTPREQ_GET &&
-       ((data->state.httpreq != HTTPREQ_POST &&
-         data->state.httpreq != HTTPREQ_POST_FORM &&
-         data->state.httpreq != HTTPREQ_POST_MIME) ||
-        !data->set.post303)) {
+    if(!HTTPREQ_IS_POST(data) || !data->set.post303) {
       http_switch_to_get(data, 303);
       switch_to_get = TRUE;
     }
@@ -1400,8 +1398,10 @@
 /*
  * Curl_compareheader()
  *
- * Returns TRUE if 'headerline' contains the 'header' with given 'content'.
- * Pass headers WITH the colon.
+ * Returns TRUE if 'headerline' contains the 'header' with given 'content'
+ * (within a comma-separated list of tokens). Pass 'header' WITH the colon.
+ *
+ * @unittest: 1625
  */
 bool Curl_compareheader(const char *headerline, /* line to check */
                         const char *header, /* header keyword _with_ colon */
@@ -1435,9 +1435,24 @@
   if(curlx_strlen(&val) >= clen) {
     size_t len;
     p = curlx_str(&val);
-    for(len = curlx_strlen(&val); len >= curlx_strlen(&val); len--, p++) {
-      if(curl_strnequal(p, content, clen))
+    for(len = curlx_strlen(&val); len >= clen;) {
+      struct Curl_str next;
+      const char *o = p;
+      /* after a match there must be a comma, space, newline or null byte */
+      if(curl_strnequal(p, content, clen) &&
+         ((p[clen] == ',') || ISBLANK(p[clen]) || ISNEWLINE(p[clen]) ||
+          !p[clen]))
         return TRUE; /* match! */
+      /* advance to the next comma */
+      if(curlx_str_until(&p, &next, MAX_HTTP_RESP_HEADER_SIZE, ',') ||
+         curlx_str_single(&p, ','))
+        break; /* no comma, get out */
+
+      /* if there are more dummy commas, move over them as well */
+      do
+        curlx_str_passblanks(&p);
+      while(!curlx_str_single(&p, ','));
+      len -= (p - o);
     }
   }
   return FALSE; /* no match */
@@ -1482,7 +1497,7 @@
     /* We are now waiting for a reply from the server or
      * a timeout on our side IFF the request has been fully sent. */
     DEBUGF(infof(data, "cr_exp100_read, start AWAITING_CONTINUE, "
-           "timeout %dms", data->set.expect_100_timeout));
+                 "timeout %dms", data->set.expect_100_timeout));
     ctx->state = EXP100_AWAITING_CONTINUE;
     ctx->start = *Curl_pgrs_now(data);
     Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT);
@@ -1598,7 +1613,7 @@
   struct connectdata *conn = data->conn;
   CURLcode result = CURLE_OK;
 
-  if(CURL_WANT_RECV(data)) {
+  if(CURL_REQ_WANT_RECV(data)) {
     result = Curl_pollset_add_in(data, ps, conn->sock[FIRSTSOCKET]);
   }
 
@@ -1973,7 +1988,7 @@
 
 static CURLcode http_useragent(struct Curl_easy *data)
 {
-  /* The User-Agent string might have been allocated in url.c already, because
+  /* The User-Agent string might have been allocated already, because
      it might have been used in the proxy connect, but if we have got a header
      with the user-agent string specified, we erase the previously made string
      here. */
@@ -2001,7 +2016,10 @@
     data->state.first_remote_port = conn->remote_port;
     data->state.first_remote_protocol = conn->scheme->protocol;
   }
-  Curl_safefree(aptr->host);
+  curlx_safefree(aptr->host);
+#ifndef CURL_DISABLE_COOKIES
+  curlx_safefree(data->req.cookiehost);
+#endif
 
   ptr = Curl_checkheaders(data, STRCONST("Host"));
   if(ptr && (!data->state.this_is_a_follow ||
@@ -2037,8 +2055,7 @@
         if(colon)
           *colon = 0; /* The host must not include an embedded port number */
       }
-      curlx_free(aptr->cookiehost);
-      aptr->cookiehost = cookiehost;
+      data->req.cookiehost = cookiehost;
     }
 #endif
 
@@ -2049,9 +2066,9 @@
     }
   }
   else {
-    /* When building Host: headers, we must put the hostname within
-       [brackets] if the hostname is a plain IPv6-address. RFC2732-style. */
-    const char *host = conn->host.name;
+    /* Use the hostname as present in the URL if it was IPv6. */
+    char *host = (data->state.up.hostname[0] == '[') ?
+       data->state.up.hostname : conn->host.name;
 
     if(((conn->given->protocol & (CURLPROTO_HTTPS | CURLPROTO_WSS)) &&
         (conn->remote_port == PORT_HTTPS)) ||
@@ -2059,14 +2076,9 @@
         (conn->remote_port == PORT_HTTP)))
       /* if(HTTPS on port 443) OR (HTTP on port 80) then do not include
          the port number in the host string */
-      aptr->host = curl_maprintf("Host: %s%s%s\r\n",
-                                 conn->bits.ipv6_ip ? "[" : "",
-                                 host, conn->bits.ipv6_ip ? "]" : "");
+      aptr->host = curl_maprintf("Host: %s\r\n", host);
     else
-      aptr->host = curl_maprintf("Host: %s%s%s:%d\r\n",
-                                 conn->bits.ipv6_ip ? "[" : "",
-                                 host, conn->bits.ipv6_ip ? "]" : "",
-                                 conn->remote_port);
+      aptr->host = curl_maprintf("Host: %s:%d\r\n", host, conn->remote_port);
 
     if(!aptr->host)
       /* without Host: we cannot make a nice request */
@@ -2097,7 +2109,7 @@
   if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
     /* Using a proxy but does not tunnel through it */
 
-    /* The path sent to the proxy is in fact the entire URL. But if the remote
+    /* The path sent to the proxy is in fact the entire URL, but if the remote
        host is a IDN-name, we must make sure that the request we produce only
        uses the encoded hostname! */
 
@@ -2209,7 +2221,7 @@
       result = Curl_getformdata(data, data->state.formp, data->set.httppost,
                                 data->state.fread_func);
       if(result) {
-        Curl_safefree(data->state.formp);
+        curlx_safefree(data->state.formp);
         return result;
       }
       data->state.mimepost = data->state.formp;
@@ -2538,8 +2550,8 @@
 
     if(data->cookies && data->state.cookie_engine) {
       bool okay;
-      const char *host = data->state.aptr.cookiehost ?
-        data->state.aptr.cookiehost : data->conn->host.name;
+      const char *host = data->req.cookiehost ?
+        data->req.cookiehost : data->conn->host.name;
       Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
       result = Curl_cookie_getlist(data, data->conn, &okay, host, &list);
       if(!result && okay) {
@@ -2663,7 +2675,7 @@
     if(conn->bits.close) {
       /* Abort after the headers if "follow Location" is set
          and we are set to close anyway. */
-      k->keepon &= ~KEEP_RECV;
+      CURL_REQ_CLEAR_RECV(data);
       k->done = TRUE;
       return CURLE_OK;
     }
@@ -2682,7 +2694,7 @@
       infof(data, "The entire document is already downloaded");
       streamclose(conn, "already downloaded");
       /* Abort download */
-      k->keepon &= ~KEEP_RECV;
+      CURL_REQ_CLEAR_RECV(data);
       k->done = TRUE;
       return CURLE_OK;
     }
@@ -2885,14 +2897,14 @@
 
 #ifndef CURL_DISABLE_PROXY
   case H1_HD_PROXY_AUTH:
-    if(data->state.aptr.proxyuserpwd)
-      result = curlx_dyn_add(req, data->state.aptr.proxyuserpwd);
+    if(data->req.proxyuserpwd)
+      result = curlx_dyn_add(req, data->req.proxyuserpwd);
     break;
 #endif
 
   case H1_HD_USER_AUTH:
-    if(data->state.aptr.userpwd)
-      result = curlx_dyn_add(req, data->state.aptr.userpwd);
+    if(data->req.userpwd)
+      result = curlx_dyn_add(req, data->req.userpwd);
     break;
 
   case H1_HD_RANGE:
@@ -2923,7 +2935,7 @@
     break;
 
   case H1_HD_ACCEPT_ENCODING:
-    Curl_safefree(data->state.aptr.accept_encoding);
+    curlx_safefree(data->state.aptr.accept_encoding);
     if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) &&
        data->set.str[STRING_ENCODING])
       result = curlx_dyn_addf(req, "Accept-Encoding: %s\r\n",
@@ -2931,7 +2943,7 @@
     break;
 
   case H1_HD_REFERER:
-    Curl_safefree(data->state.aptr.ref);
+    curlx_safefree(data->state.aptr.ref);
     if(Curl_bufref_ptr(&data->state.referer) &&
        !Curl_checkheaders(data, STRCONST("Referer")))
       result = curlx_dyn_addf(req, "Referer: %s\r\n",
@@ -2955,7 +2967,7 @@
 #ifndef CURL_DISABLE_ALTSVC
   case H1_HD_ALT_USED:
     if(conn->bits.altused && !Curl_checkheaders(data, STRCONST("Alt-Used")))
-      result = curlx_dyn_addf(req, "Alt-Used: %s:%d\r\n",
+      result = curlx_dyn_addf(req, "Alt-Used: %s:%u\r\n",
                               conn->conn_to_host.name,
                               conn->conn_to_port);
     break;
@@ -3103,12 +3115,6 @@
   if(result == CURLE_TOO_LARGE)
     failf(data, "HTTP request too large");
 
-  /* clear userpwd and proxyuserpwd to avoid reusing old credentials
-   * from reused connections */
-  Curl_safefree(data->state.aptr.userpwd);
-#ifndef CURL_DISABLE_PROXY
-  Curl_safefree(data->state.aptr.proxyuserpwd);
-#endif
   curlx_dyn_free(&req);
   return result;
 }
@@ -3156,13 +3162,13 @@
 static statusline checkrtspprefix(struct Curl_easy *data,
                                   const char *s, size_t len)
 {
-  statusline result = STATUS_BAD;
+  statusline status = STATUS_BAD;
   statusline onmatch = len >= 5 ? STATUS_DONE : STATUS_UNKNOWN;
   (void)data;
   if(checkprefixmax("RTSP/", s, len))
-    result = onmatch;
+    status = onmatch;
 
-  return result;
+  return status;
 }
 #endif /* CURL_DISABLE_RTSP */
 
@@ -3205,7 +3211,7 @@
   const char *v;
   struct connectdata *conn = data->conn;
   v = (data->asi &&
-       (Curl_conn_is_ssl(data->conn, FIRSTSOCKET) ||
+       (Curl_xfer_is_secure(data) ||
 #ifdef DEBUGBUILD
         /* allow debug builds to circumvent the HTTPS restriction */
         getenv("CURL_ALTSVC_HTTP")
@@ -3545,8 +3551,8 @@
   if(v) {
     /* If there is a custom-set Host: name, use it here, or else use
      * real peer hostname. */
-    const char *host = data->state.aptr.cookiehost ?
-      data->state.aptr.cookiehost : conn->host.name;
+    const char *host = data->req.cookiehost ?
+      data->req.cookiehost : conn->host.name;
     const bool secure_context = Curl_secure_context(conn, host);
     CURLcode result;
     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
@@ -3559,7 +3565,7 @@
 #ifndef CURL_DISABLE_HSTS
   /* If enabled, the header is incoming and this is over HTTPS */
   v = (data->hsts &&
-       (Curl_conn_is_ssl(conn, FIRSTSOCKET) ||
+       (Curl_xfer_is_secure(data) ||
 #ifdef DEBUGBUILD
         /* allow debug builds to circumvent the HTTPS restriction */
         getenv("CURL_HSTS_HTTP")
@@ -3569,11 +3575,11 @@
          )
     ) ? HD_VAL(hd, hdlen, "Strict-Transport-Security:") : NULL;
   if(v) {
-    CURLcode check =
+    CURLcode result =
       Curl_hsts_parse(data->hsts, conn->host.name, v);
-    if(check) {
-      if(check == CURLE_OUT_OF_MEMORY)
-        return check;
+    if(result) {
+      if(result == CURLE_OUT_OF_MEMORY)
+        return result;
       infof(data, "Illegal STS header skipped");
     }
 #ifdef DEBUGBUILD
@@ -3726,13 +3732,13 @@
     /* no major version switch mid-connection */
     if(k->httpversion_sent &&
        (k->httpversion / 10 != k->httpversion_sent / 10)) {
-      failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)",
+      failf(data, "Version mismatch (from HTTP/%d to HTTP/%d)",
             k->httpversion_sent / 10, k->httpversion / 10);
       return CURLE_WEIRD_SERVER_REPLY;
     }
     break;
   default:
-    failf(data, "Unsupported HTTP version (%u.%d) in response",
+    failf(data, "Unsupported HTTP version (%d.%d) in response",
           k->httpversion / 10, k->httpversion % 10);
     return CURLE_UNSUPPORTED_PROTOCOL;
   }
@@ -3875,136 +3881,112 @@
   return CURLE_OK;
 }
 
-static CURLcode http_on_response(struct Curl_easy *data,
-                                 const char *last_hd, size_t last_hd_len,
-                                 const char *buf, size_t blen,
-                                 size_t *pconsumed)
+/*
+ * Handle a 101 Switching Protocols response. Performs the actual protocol
+ * upgrade to HTTP/2 or WebSocket based on what was requested.
+ */
+static CURLcode http_on_101_upgrade(struct Curl_easy *data,
+                                    const char *buf, size_t blen,
+                                    size_t *pconsumed,
+                                    bool *conn_changed)
 {
   struct connectdata *conn = data->conn;
-  CURLcode result = CURLE_OK;
   struct SingleRequest *k = &data->req;
-  bool conn_changed = FALSE;
 
-  (void)buf; /* not used without HTTP2 enabled */
-  *pconsumed = 0;
-
-  if(k->upgr101 == UPGR101_RECEIVED) {
-    /* supposedly upgraded to http2 now */
-    if(data->req.httpversion != 20)
-      infof(data, "Lying server, not serving HTTP/2");
-  }
-
-  if(k->httpcode < 200 && last_hd) {
-    /* Intermediate responses might trigger processing of more
-     * responses, write the last header to the client before
-     * proceeding. */
-    result = http_write_header(data, last_hd, last_hd_len);
-    last_hd = NULL; /* handled it */
-    if(result)
-      goto out;
-  }
-
-  if(k->httpcode < 100) {
-    failf(data, "Unsupported response code in HTTP response");
-    result = CURLE_UNSUPPORTED_PROTOCOL;
-    goto out;
-  }
-  else if(k->httpcode < 200) {
-    /* "A user agent MAY ignore unexpected 1xx status responses."
-     * By default, we expect to get more responses after this one. */
-    k->header = TRUE;
-    k->headerline = 0; /* restart the header line counter */
-
-    switch(k->httpcode) {
-    case 100:
-      /*
-       * We have made an HTTP PUT or POST and this is 1.1-lingo
-       * that tells us that the server is OK with this and ready
-       * to receive the data.
-       */
-      http_exp100_got100(data);
-      break;
-    case 101: {
-      int upgr101_requested = k->upgr101;
-
-      if(k->httpversion_sent != 11) {
-        /* invalid for other HTTP versions */
-        failf(data, "server sent 101 response while not talking HTTP/1.1");
-        result = CURLE_WEIRD_SERVER_REPLY;
-        goto out;
-      }
-
-      /* Whatever the success, upgrade was selected. */
-      k->upgr101 = UPGR101_RECEIVED;
-      data->conn->bits.upgrade_in_progress = FALSE;
-      conn_changed = TRUE;
-
-      /* To be fully conform, we would check the "Upgrade:" response header
-       * to mention the protocol we requested. */
-      switch(upgr101_requested) {
-      case UPGR101_H2:
-        /* Switch to HTTP/2, where we will get more responses.
-         * blen bytes in bug are already h2 protocol bytes */
-        infof(data, "Received 101, Switching to HTTP/2");
-        result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
-        if(result)
-          goto out;
-        *pconsumed += blen;
-        break;
-#ifndef CURL_DISABLE_WEBSOCKETS
-      case UPGR101_WS:
-        /* Switch to WebSocket, where we now stream ws frames.
-         * blen bytes in bug are already ws protocol bytes */
-        infof(data, "Received 101, Switching to WebSocket");
-        result = Curl_ws_accept(data, buf, blen);
-        if(result)
-          goto out;
-        *pconsumed += blen; /* ws accept handled the data */
-        break;
+#if !defined(USE_NGHTTP2) && defined(CURL_DISABLE_WEBSOCKETS)
+  (void)buf;
+  (void)blen;
+  (void)pconsumed;
+#else
+  CURLcode result;
+  int upgr101_requested = k->upgr101;
 #endif
-      default:
-        /* We silently accept this as the final response. What are we
-         * switching to if we did not ask for an Upgrade? Maybe the
-         * application provided an `Upgrade: xxx` header? */
-        k->header = FALSE;
-        break;
-      }
-      /* processed 101 */
-      break;
-    }
-    default:
-      /* The server may send us other 1xx responses, like informative
-       * 103. This have no influence on request processing and we expect
-       * to receive a final response eventually. */
-      break;
-    }
-    goto out;
+
+  if(k->httpversion_sent != 11) {
+    /* invalid for other HTTP versions */
+    failf(data, "server sent 101 response while not talking HTTP/1.1");
+    return CURLE_WEIRD_SERVER_REPLY;
   }
 
-  /* k->httpcode >= 200, final response */
+  /* Whatever the success, upgrade was selected. */
+  k->upgr101 = UPGR101_RECEIVED;
+  conn->bits.upgrade_in_progress = FALSE;
+  *conn_changed = TRUE;
+
+  /* To be fully compliant, we would check the "Upgrade:" response header to
+   * mention the protocol we requested. */
+#ifdef USE_NGHTTP2
+  if(upgr101_requested == UPGR101_H2) {
+    /* Switch to HTTP/2, where we will get more responses. blen bytes in buf
+     * are already h2 protocol bytes */
+    infof(data, "Received 101, Switching to HTTP/2");
+    result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
+    if(!result)
+      *pconsumed += blen;
+    return result;
+  }
+#endif
+#ifndef CURL_DISABLE_WEBSOCKETS
+  if(upgr101_requested == UPGR101_WS) {
+    /* Switch to WebSocket, where we now stream ws frames. blen bytes in buf
+     * are already ws protocol bytes */
+    infof(data, "Received 101, Switching to WebSocket");
+    result = Curl_ws_accept(data, buf, blen);
+    if(!result)
+      *pconsumed += blen; /* ws accept handled the data */
+    return result;
+  }
+#endif
+  /* We silently accept this as the final response. What are we switching to
+   * if we did not ask for an Upgrade? Maybe the application provided an
+   * `Upgrade: xxx` header? */
   k->header = FALSE;
-  if(data->conn->bits.upgrade_in_progress) {
-    /* Asked for protocol upgrade, but it was not selected by the server */
-    data->conn->bits.upgrade_in_progress = FALSE;
-    conn_changed = TRUE;
-  }
+  return CURLE_OK;
+}
 
-  if((k->size == -1) && !k->chunk && !conn->bits.close &&
-     (k->httpversion == 11) &&
-     !(conn->scheme->protocol & CURLPROTO_RTSP) &&
-     data->state.httpreq != HTTPREQ_HEAD) {
-    /* On HTTP 1.1, when connection is not to get closed, but no
-       Content-Length nor Transfer-Encoding chunked have been
-       received, according to RFC2616 section 4.4 point 5, we
-       assume that the server will close the connection to
-       signal the end of the document. */
-    infof(data, "no chunk, no close, no size. Assume close to "
-          "signal end");
-    streamclose(conn, "HTTP: No end-of-message indicator");
-  }
+/*
+ * Handle 1xx intermediate HTTP responses. Sets up state for more
+ * headers and processes 100-continue and 101 upgrade responses.
+ */
+static CURLcode http_on_1xx_response(struct Curl_easy *data,
+                                     const char *buf, size_t blen,
+                                     size_t *pconsumed,
+                                     bool *conn_changed)
+{
+  struct SingleRequest *k = &data->req;
 
-  /* At this point we have some idea about the fate of the connection.
-     If we are closing the connection it may result auth failure. */
+  /* "A user agent MAY ignore unexpected 1xx status responses."
+   * By default, we expect to get more responses after this one. */
+  k->header = TRUE;
+  k->headerline = 0; /* restart the header line counter */
+
+  switch(k->httpcode) {
+  case 100:
+    /* We have made an HTTP PUT or POST and this is 1.1-lingo that tells us
+     * that the server is OK with this and ready to receive the data. */
+    http_exp100_got100(data);
+    break;
+  case 101:
+    return http_on_101_upgrade(data, buf, blen, pconsumed, conn_changed);
+  default:
+    /* The server may send us other 1xx responses, like informative 103. This
+     * has no influence on request processing and we expect to receive a
+     * final response eventually. */
+    break;
+  }
+  return CURLE_OK;
+}
+
+#if defined(USE_NTLM) || defined(USE_SPNEGO)
+/*
+ * Check if NTLM or SPNEGO authentication negotiation failed due to
+ * connection closure (typically on HTTP/1.0 servers).
+ */
+static void http_check_auth_closure(struct Curl_easy *data,
+                                    struct connectdata *conn)
+{
+  /* At this point we have some idea about the fate of the connection. If we
+     are closing the connection it may result auth failure. */
 #ifdef USE_NTLM
   if(conn->bits.close &&
      (((data->req.httpcode == 401) &&
@@ -4033,9 +4015,154 @@
     conn->proxy_negotiate_state = GSS_AUTHSUCC;
   }
 #endif
+}
+#else
+#define http_check_auth_closure(x, y) /* empty */
+#endif
+
+/*
+ * Handle an error response (>= 300) received while still sending the
+ * request body. Deals with 417 Expectation Failed retries, keep-sending
+ * on error, and aborting the send.
+ */
+static CURLcode http_handle_send_error(struct Curl_easy *data)
+{
+  struct connectdata *conn = data->conn;
+  struct SingleRequest *k = &data->req;
+  CURLcode result = CURLE_OK;
+
+  if(!data->req.authneg && !conn->bits.close &&
+     !Curl_creader_will_rewind(data)) {
+    /*
+     * General treatment of errors when about to send data.
+     * Including: "417 Expectation Failed", while waiting for
+     * 100-continue.
+     *
+     * The check for close above is done because if something
+     * else has already deemed the connection to get closed then
+     * something else should have considered the big picture and
+     * we avoid this check.
+     */
+
+    switch(data->state.httpreq) {
+    case HTTPREQ_PUT:
+    case HTTPREQ_POST:
+    case HTTPREQ_POST_FORM:
+    case HTTPREQ_POST_MIME:
+      /* We got an error response. If this happened before the
+       * whole request body has been sent we stop sending and
+       * mark the connection for closure after we have read the
+       * entire response. */
+      if(!Curl_req_done_sending(data)) {
+        if((k->httpcode == 417) && http_exp100_is_selected(data)) {
+          /* 417 Expectation Failed - try again without the
+             Expect header */
+          if(!k->writebytecount && http_exp100_is_waiting(data)) {
+            infof(data, "Got HTTP failure 417 while waiting for a 100");
+          }
+          else {
+            infof(data, "Got HTTP failure 417 while sending data");
+            streamclose(conn, "Stop sending data before everything sent");
+            result = http_perhapsrewind(data, conn);
+            if(result)
+              return result;
+          }
+          data->state.disableexpect = TRUE;
+          Curl_req_abort_sending(data);
+          DEBUGASSERT(!data->req.newurl);
+          data->req.newurl = Curl_bufref_dup(&data->state.url);
+          if(!data->req.newurl)
+            return CURLE_OUT_OF_MEMORY;
+        }
+        else if(data->set.http_keep_sending_on_error) {
+          infof(data, "HTTP error before end of send, keep sending");
+          http_exp100_send_anyway(data);
+        }
+        else {
+          infof(data, "HTTP error before end of send, stop sending");
+          streamclose(conn, "Stop sending data before everything sent");
+          result = Curl_req_abort_sending(data);
+          if(result)
+            return result;
+        }
+      }
+      break;
+
+    default: /* default label present to avoid compiler warnings */
+      break;
+    }
+  }
+
+  if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) {
+    /* We rewind before next send, continue sending now */
+    infof(data, "Keep sending data to get tossed away");
+    CURL_REQ_SET_SEND(data);
+  }
+  return result;
+}
+
+static CURLcode http_on_response(struct Curl_easy *data,
+                                 const char *last_hd, size_t last_hd_len,
+                                 const char *buf, size_t blen,
+                                 size_t *pconsumed)
+{
+  struct connectdata *conn = data->conn;
+  CURLcode result = CURLE_OK;
+  struct SingleRequest *k = &data->req;
+  bool conn_changed = FALSE;
+
+  (void)buf; /* not used without HTTP2 enabled */
+  *pconsumed = 0;
+
+  if(k->upgr101 == UPGR101_RECEIVED) {
+    /* supposedly upgraded to http2 now */
+    if(data->req.httpversion != 20)
+      infof(data, "Lying server, not serving HTTP/2");
+  }
+
+  if(k->httpcode < 200 && last_hd) {
+    /* Intermediate responses might trigger processing of more responses,
+     * write the last header to the client before proceeding. */
+    result = http_write_header(data, last_hd, last_hd_len);
+    last_hd = NULL; /* handled it */
+    if(result)
+      goto out;
+  }
+
+  if(k->httpcode < 100) {
+    failf(data, "Unsupported response code in HTTP response");
+    result = CURLE_UNSUPPORTED_PROTOCOL;
+    goto out;
+  }
+  else if(k->httpcode < 200) {
+    result = http_on_1xx_response(data, buf, blen, pconsumed, &conn_changed);
+    goto out;
+  }
+
+  /* k->httpcode >= 200, final response */
+  k->header = FALSE;
+  if(conn->bits.upgrade_in_progress) {
+    /* Asked for protocol upgrade, but it was not selected */
+    conn->bits.upgrade_in_progress = FALSE;
+    conn_changed = TRUE;
+  }
+
+  if((k->size == -1) && !k->chunk && !conn->bits.close &&
+     (k->httpversion == 11) &&
+     !(conn->scheme->protocol & CURLPROTO_RTSP) &&
+     data->state.httpreq != HTTPREQ_HEAD) {
+    /* On HTTP 1.1, when connection is not to get closed, but no
+       Content-Length nor Transfer-Encoding chunked have been received,
+       according to RFC2616 section 4.4 point 5, we assume that the server
+       will close the connection to signal the end of the document. */
+    infof(data, "no chunk, no close, no size. Assume close to signal end");
+    streamclose(conn, "HTTP: No end-of-message indicator");
+  }
+
+  http_check_auth_closure(data, conn);
 
 #ifndef CURL_DISABLE_WEBSOCKETS
-  /* All >=200 HTTP status codes are errors when wanting WebSocket */
+  /* All >=200 HTTP status codes are errors when wanting ws */
   if(data->req.upgr101 == UPGR101_WS) {
     failf(data, "Refused WebSocket upgrade: %d", k->httpcode);
     result = CURLE_HTTP_RETURNED_ERROR;
@@ -4045,88 +4172,23 @@
 
   /* Check if this response means the transfer errored. */
   if(http_should_fail(data, data->req.httpcode)) {
-    failf(data, "The requested URL returned error: %d", k->httpcode);
+    failf(data, "The requested URL returned error: %d",
+          k->httpcode);
     result = CURLE_HTTP_RETURNED_ERROR;
     goto out;
   }
 
-  /* Curl_http_auth_act() checks what authentication methods
-   * that are available and decides which one (if any) to
-   * use. It will set 'newurl' if an auth method was picked. */
+  /* Curl_http_auth_act() checks what authentication methods that are
+   * available and decides which one (if any) to use. It will set 'newurl' if
+   * an auth method was picked. */
   result = Curl_http_auth_act(data);
   if(result)
     goto out;
 
   if(k->httpcode >= 300) {
-    if(!data->req.authneg && !conn->bits.close &&
-       !Curl_creader_will_rewind(data)) {
-      /*
-       * General treatment of errors when about to send data. Including :
-       * "417 Expectation Failed", while waiting for 100-continue.
-       *
-       * The check for close above is done because of something
-       * else has already deemed the connection to get closed then
-       * something else should have considered the big picture and we
-       * avoid this check.
-       *
-       */
-
-      switch(data->state.httpreq) {
-      case HTTPREQ_PUT:
-      case HTTPREQ_POST:
-      case HTTPREQ_POST_FORM:
-      case HTTPREQ_POST_MIME:
-        /* We got an error response. If this happened before the whole
-         * request body has been sent we stop sending and mark the
-         * connection for closure after we have read the entire response.
-         */
-        if(!Curl_req_done_sending(data)) {
-          if((k->httpcode == 417) && http_exp100_is_selected(data)) {
-            /* 417 Expectation Failed - try again without the Expect
-               header */
-            if(!k->writebytecount && http_exp100_is_waiting(data)) {
-              infof(data, "Got HTTP failure 417 while waiting for a 100");
-            }
-            else {
-              infof(data, "Got HTTP failure 417 while sending data");
-              streamclose(conn, "Stop sending data before everything sent");
-              result = http_perhapsrewind(data, conn);
-              if(result)
-                goto out;
-            }
-            data->state.disableexpect = TRUE;
-            Curl_req_abort_sending(data);
-            DEBUGASSERT(!data->req.newurl);
-            data->req.newurl = Curl_bufref_dup(&data->state.url);
-            if(!data->req.newurl) {
-              result = CURLE_OUT_OF_MEMORY;
-              goto out;
-            }
-          }
-          else if(data->set.http_keep_sending_on_error) {
-            infof(data, "HTTP error before end of send, keep sending");
-            http_exp100_send_anyway(data);
-          }
-          else {
-            infof(data, "HTTP error before end of send, stop sending");
-            streamclose(conn, "Stop sending data before everything sent");
-            result = Curl_req_abort_sending(data);
-            if(result)
-              goto out;
-          }
-        }
-        break;
-
-      default: /* default label present to avoid compiler warnings */
-        break;
-      }
-    }
-
-    if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) {
-      /* We rewind before next send, continue sending now */
-      infof(data, "Keep sending data to get tossed away");
-      k->keepon |= KEEP_SEND;
-    }
+    result = http_handle_send_error(data);
+    if(result)
+      goto out;
   }
 
   /* If we requested a "no body", this is a good time to get
@@ -4135,11 +4197,10 @@
   if(data->req.no_body)
     k->download_done = TRUE;
 
-  /* If max download size is *zero* (nothing) we already have
-     nothing and can safely return ok now! But for HTTP/2, we would
-     like to call http2_handle_stream_close to properly close a
-     stream. In order to do this, we keep reading until we
-     close the stream. */
+  /* If max download size is *zero* (nothing) we already have nothing and can
+     safely return ok now! For HTTP/2, we would like to call
+     http2_handle_stream_close to properly close a stream. In order to do
+     this, we keep reading until we close the stream. */
   if((k->maxdownload == 0) && (k->httpversion_sent < 20))
     k->download_done = TRUE;
 
@@ -4147,21 +4208,18 @@
   result = http_firstwrite(data);
 
   if(!result)
-    /* This is the last response that we get for the current request.
-     * Check on the body size and determine if the response is complete.
-     */
+    /* This is the last response that we get for the current request. Check on
+     * the body size and determine if the response is complete. */
     result = http_size(data);
 
 out:
-  if(last_hd) {
+  if(last_hd)
     /* if not written yet, write it now */
-    result = Curl_1st_err(
-      result, http_write_header(data, last_hd, last_hd_len));
-  }
-  if(conn_changed) {
-    /* poke the multi handle to allow any pending pipewait to retry now */
+    result = Curl_1st_fatal(result,
+                            http_write_header(data, last_hd, last_hd_len));
+  if(conn_changed)
+    /* poke the multi handle to allow pending pipewait to retry */
     Curl_multi_connchanged(data->multi);
-  }
   return result;
 }
 
@@ -4173,7 +4231,7 @@
   CURLcode result = CURLE_OK;
   struct SingleRequest *k = &data->req;
   int writetype;
-  DEBUGASSERT(!hd[hdlen]); /* null terminated */
+  DEBUGASSERT(!hd[hdlen]); /* null-terminated */
 
   *pconsumed = 0;
   if((0x0a == *hd) || (0x0d == *hd)) {
@@ -4534,18 +4592,18 @@
   CURLcode result;
   size_t consumed;
   char tmp = 0;
-  DEBUGASSERT(!hd[hdlen]); /* null terminated */
+  DEBUGASSERT(!hd[hdlen]); /* null-terminated */
 
   result = http_rw_hd(data, hd, hdlen, &tmp, 0, &consumed);
   if(!result && is_eos) {
-    result = Curl_client_write(data, (CLIENTWRITE_BODY|CLIENTWRITE_EOS),
+    result = Curl_client_write(data, (CLIENTWRITE_BODY | CLIENTWRITE_EOS),
                                &tmp, 0);
   }
   return result;
 }
 
 /*
- * HTTP protocol `write_resp` implementation. Will parse headers
+ * HTTP protocol `write_resp` implementation. Parse headers
  * when not done yet and otherwise return without consuming data.
  */
 CURLcode Curl_http_write_resp_hds(struct Curl_easy *data,
@@ -4892,7 +4950,7 @@
       infof(data, "set pseudo header %s to %s", HTTP_PSEUDO_SCHEME, scheme);
     }
     else {
-      scheme = Curl_conn_is_ssl(data->conn, FIRSTSOCKET) ? "https" : "http";
+      scheme = Curl_xfer_is_secure(data) ? "https" : "http";
     }
   }
 
@@ -4982,7 +5040,7 @@
 /*
  * HTTP handler interface.
  */
-static const struct Curl_protocol Curl_protocol_http = {
+const struct Curl_protocol Curl_protocol_http = {
   Curl_http_setup_conn,                 /* setup_connection */
   Curl_http,                            /* do_it */
   Curl_http_done,                       /* done */
@@ -4997,43 +5055,9 @@
   ZERO_NULL,                            /* disconnect */
   Curl_http_write_resp,                 /* write_resp */
   Curl_http_write_resp_hd,              /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   Curl_http_follow,                     /* follow */
 };
 
 #endif /* CURL_DISABLE_HTTP */
-
-/*
- * HTTP handler interface.
- */
-const struct Curl_scheme Curl_scheme_http = {
-  "http",                               /* scheme */
-#ifdef CURL_DISABLE_HTTP
-  ZERO_NULL,
-#else
-  &Curl_protocol_http,
-#endif
-  CURLPROTO_HTTP,                       /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_CREDSPERREQUEST |             /* flags */
-  PROTOPT_USERPWDCTRL | PROTOPT_CONN_REUSE,
-  PORT_HTTP,                            /* defport */
-};
-
-/*
- * HTTPS handler interface.
- */
-const struct Curl_scheme Curl_scheme_https = {
-  "https",                              /* scheme */
-#if defined(CURL_DISABLE_HTTP) || !defined(USE_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_http,
-#endif
-  CURLPROTO_HTTPS,                      /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN | /* flags */
-  PROTOPT_USERPWDCTRL | PROTOPT_CONN_REUSE,
-  PORT_HTTPS,                           /* defport */
-};
diff --git a/Utilities/cmcurl/lib/http.h b/Utilities/cmcurl/lib/http.h
index bb53595..6e33c00 100644
--- a/Utilities/cmcurl/lib/http.h
+++ b/Utilities/cmcurl/lib/http.h
@@ -37,27 +37,16 @@
   HTTPREQ_HEAD
 } Curl_HttpReq;
 
-/* When redirecting transfers. */
-typedef enum {
-  FOLLOW_NONE,  /* not used within the function, a placeholder to allow
-                   initing to this */
-  FOLLOW_FAKE,  /* only records stuff, not actually following */
-  FOLLOW_RETRY, /* set if this is a request retry as opposed to a real
-                   redirect following */
-  FOLLOW_REDIR /* a full true redirect */
-} followtype;
-
 #define CURL_HTTP_V1x   (1 << 0)
 #define CURL_HTTP_V2x   (1 << 1)
 #define CURL_HTTP_V3x   (1 << 2)
 /* bitmask of CURL_HTTP_V* values */
 typedef unsigned char http_majors;
 
-extern const struct Curl_scheme Curl_scheme_http;
-extern const struct Curl_scheme Curl_scheme_https;
-
 #ifndef CURL_DISABLE_HTTP
 
+extern const struct Curl_protocol Curl_protocol_http;
+
 struct dynhds;
 
 struct http_negotiation {
@@ -196,14 +185,14 @@
  *
  * @returns CURLcode
  */
-CURLcode
-Curl_http_output_auth(struct Curl_easy *data,
-                      struct connectdata *conn,
-                      const char *request,
-                      Curl_HttpReq httpreq,
-                      const char *path,
-                      bool proxytunnel); /* TRUE if this is the request setting
-                                            up the proxy tunnel */
+CURLcode Curl_http_output_auth(struct Curl_easy *data,
+                               struct connectdata *conn,
+                               const char *request,
+                               Curl_HttpReq httpreq,
+                               const char *path,
+                               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);
diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c
index e1c5798..7be5abd 100644
--- a/Utilities/cmcurl/lib/http2.c
+++ b/Utilities/cmcurl/lib/http2.c
@@ -154,7 +154,7 @@
   size_t i;
   for(i = 0; i < stream->push_headers_used; i++)
     curlx_free(stream->push_headers[i]);
-  Curl_safefree(stream->push_headers);
+  curlx_safefree(stream->push_headers);
   stream->push_headers_used = 0;
 }
 
@@ -203,6 +203,7 @@
 {
   if(ctx->h2) {
     nghttp2_session_del(ctx->h2);
+    ctx->h2 = NULL;
   }
 }
 
@@ -375,6 +376,10 @@
 
   (void)cf;
   DEBUGASSERT(data);
+
+  if(!data)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+
   stream = H2_STREAM_CTX(ctx, data);
   if(stream) {
     *pstream = stream;
@@ -713,8 +718,9 @@
   return second;
 }
 
-static int set_transfer_url(struct Curl_easy *data,
-                            struct curl_pushheaders *hp)
+static int set_transfer_url(struct Curl_easy *newhandle,
+                            struct curl_pushheaders *hp,
+                            struct Curl_easy *data)
 {
   const char *v;
   CURLUcode uc;
@@ -752,6 +758,13 @@
     }
   }
 
+  /* We can only allow PUSH of resource from the same origin, e.g.
+   * scheme + hostname + port */
+  if(!Curl_url_same_origin(data->state.uh, u)) {
+    rc = 1;
+    goto fail;
+  }
+
   uc = curl_url_get(u, CURLUPART_URL, &url, 0);
   if(uc)
     rc = 4;
@@ -760,7 +773,7 @@
   if(rc)
     return rc;
 
-  Curl_bufref_set(&data->state.url, url, 0, curl_free);
+  Curl_bufref_set(&newhandle->state.url, url, 0, curl_free);
   return 0;
 }
 
@@ -806,7 +819,7 @@
     heads.stream = stream;
     heads.frame = frame;
 
-    rv = set_transfer_url(newhandle, &heads);
+    rv = set_transfer_url(newhandle, &heads, data);
     if(rv) {
       CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE, failed to set URL -> %d",
                   frame->promised_stream_id, rv);
@@ -863,7 +876,7 @@
                                               newstream->id,
                                               newhandle);
     if(rv) {
-      infof(data, "failed to set user_data for stream %u",
+      infof(data, "failed to set user_data for stream %d",
             newstream->id);
       DEBUGASSERT(0);
       discard_newhandle(cf, newhandle);
@@ -1010,7 +1023,7 @@
     Curl_multi_mark_dirty(data);
     break;
   case NGHTTP2_WINDOW_UPDATE:
-    if(CURL_WANT_SEND(data) && Curl_bufq_is_empty(&stream->sendbuf)) {
+    if(CURL_REQ_WANT_SEND(data) && Curl_bufq_is_empty(&stream->sendbuf)) {
       /* need more data, force processing of transfer */
       Curl_multi_mark_dirty(data);
     }
@@ -1099,7 +1112,7 @@
       memcpy(scratch, frame->goaway.opaque_data, len);
     scratch[len] = '\0';
     return curl_msnprintf(buffer, blen,
-                          "FRAME[GOAWAY, error=%d, reason='%s', "
+                          "FRAME[GOAWAY, error=%u, reason='%s', "
                           "last_stream=%d]", frame->goaway.error_code,
                           scratch, frame->goaway.last_stream_id);
   }
@@ -1134,7 +1147,7 @@
   if((frame->hd.type == NGHTTP2_GOAWAY) && !ctx->sent_goaway) {
     /* A GOAWAY not initiated by us, but by nghttp2 itself on detecting
      * a protocol error on the connection */
-    failf(data, "nghttp2 shuts down connection with error %d: %s",
+    failf(data, "nghttp2 shuts down connection with error %u: %s",
           frame->goaway.error_code,
           nghttp2_http2_strerror(frame->goaway.error_code));
   }
@@ -1172,7 +1185,7 @@
           session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
         ctx->enable_push = nghttp2_session_get_remote_settings(
           session, NGHTTP2_SETTINGS_ENABLE_PUSH) != 0;
-        CURL_TRC_CF(data, cf, "[0] MAX_CONCURRENT_STREAMS: %d",
+        CURL_TRC_CF(data, cf, "[0] MAX_CONCURRENT_STREAMS: %u",
                     ctx->max_concurrent_streams);
         CURL_TRC_CF(data, cf, "[0] ENABLE_PUSH: %s",
                     ctx->enable_push ? "TRUE" : "false");
@@ -1187,7 +1200,7 @@
          * window and *assume* that we treat this like a WINDOW_UPDATE. Some
          * servers send an explicit WINDOW_UPDATE, but not all seem to do that.
          * To be safe, we UNHOLD a stream in order not to stall. */
-        if(CURL_WANT_SEND(data))
+        if(CURL_REQ_WANT_SEND(data))
           Curl_multi_mark_dirty(data);
       }
       break;
@@ -1197,7 +1210,7 @@
       ctx->goaway_error = frame->goaway.error_code;
       ctx->remote_max_sid = frame->goaway.last_stream_id;
       if(data) {
-        infof(data, "received GOAWAY, error=%u, last_stream=%u",
+        infof(data, "received GOAWAY, error=%u, last_stream=%d",
               ctx->goaway_error, ctx->remote_max_sid);
         Curl_multi_connchanged(data->multi);
       }
@@ -1327,7 +1340,7 @@
     stream->reset = TRUE;
 
   if(stream->error)
-    CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %d)",
+    CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %u)",
               stream_id, nghttp2_http2_strerror(error_code), error_code);
   else
     CURL_TRC_CF(data_s, cf, "[%d] CLOSED", stream_id);
@@ -1336,7 +1349,7 @@
   /* remove `data_s` from the nghttp2 stream */
   rv = nghttp2_session_set_stream_user_data(session, stream_id, 0);
   if(rv) {
-    infof(data_s, "http/2: failed to clear user_data for stream %u",
+    infof(data_s, "http/2: failed to clear user_data for stream %d",
           stream_id);
     DEBUGASSERT(0);
   }
@@ -1474,8 +1487,11 @@
       stream->push_headers = headp;
     }
     h = curl_maprintf("%s:%s", name, value);
-    if(h)
-      stream->push_headers[stream->push_headers_used++] = h;
+    if(!h) {
+      free_push_headers(stream);
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    stream->push_headers[stream->push_headers_used++] = h;
     return 0;
   }
 
@@ -1505,7 +1521,7 @@
       cf_h2_header_error(cf, data_s, stream, result);
       return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
-    hlen = curl_msnprintf(buffer, sizeof(buffer), HTTP_PSEUDO_STATUS ":%u\r",
+    hlen = curl_msnprintf(buffer, sizeof(buffer), HTTP_PSEUDO_STATUS ":%d\r",
                           stream->status_code);
     result = Curl_headers_push(data_s, buffer, hlen, CURLH_PSEUDO);
     if(result) {
@@ -1692,9 +1708,9 @@
         stream->close_handled = TRUE;
         return CURLE_OK;
     }
-    failf(data, "HTTP/2 stream %" PRIu32 " reset by %s (error 0x%" PRIx32
-          " %s)", stream->id, stream->reset_by_server ? "server" : "curl",
-           stream->error, nghttp2_http2_strerror(stream->error));
+    failf(data, "HTTP/2 stream %d reset by %s (error 0x%x %s)",
+          stream->id, stream->reset_by_server ? "server" : "curl",
+          stream->error, nghttp2_http2_strerror(stream->error));
     return stream->error ? CURLE_HTTP2_STREAM :
            (data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2);
   }
@@ -1717,8 +1733,8 @@
         break;
       curlx_dyn_reset(&dbuf);
       result = curlx_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a",
-                            (int)e->namelen, e->name,
-                            (int)e->valuelen, e->value);
+                              (int)e->namelen, e->name,
+                              (int)e->valuelen, e->value);
       if(result)
         break;
       Curl_debug(data, CURLINFO_HEADER_IN, curlx_dyn_ptr(&dbuf),
@@ -1817,7 +1833,7 @@
   /* Defer flushing during the connect phase so that the SETTINGS and
    * other initial frames are sent together with the first request.
    * Unless we are 'connect_only' where the request will never come. */
-  if(!cf->connected && !cf->conn->connect_only)
+  if(!cf->connected && !cf->conn->bits.connect_only)
     return CURLE_OK;
   return nw_out_flush(cf, data);
 }
@@ -1940,10 +1956,15 @@
                            char *buf, size_t len, size_t *pnread)
 {
   struct cf_h2_ctx *ctx = cf->ctx;
-  struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
+  struct h2_stream_ctx *stream;
   CURLcode result, r2;
   struct cf_call_data save;
 
+  if(!data)
+    return CURLE_HTTP2;
+
+  stream = H2_STREAM_CTX(ctx, data);
+
   *pnread = 0;
   if(!stream) {
     /* Abnormal call sequence: either this transfer has never opened a stream
@@ -1986,7 +2007,7 @@
     /* pending data to send, need to be called again. Ideally, we
      * monitor the socket for POLLOUT, but when not SENDING
      * any more, we force processing of the transfer. */
-    if(!CURL_WANT_SEND(data))
+    if(!CURL_REQ_WANT_SEND(data))
       Curl_multi_mark_dirty(data);
   }
   else if(r2) {
@@ -2031,7 +2052,7 @@
       return CURLE_OK;
     }
     /* Server closed before we got a response, this is an error */
-    infof(data, "stream %u closed", stream->id);
+    infof(data, "stream %d closed", stream->id);
     return CURLE_SEND_ERROR;
   }
 
@@ -2131,7 +2152,7 @@
   }
 
   if(stream_id < 0) {
-    CURL_TRC_CF(data, cf, "send: nghttp2_submit_request error (%s)%u",
+    CURL_TRC_CF(data, cf, "send: nghttp2_submit_request error (%s)%d",
                 nghttp2_strerror(stream_id), stream_id);
     result = CURLE_SEND_ERROR;
     goto out;
@@ -2180,7 +2201,7 @@
 out:
   CURL_TRC_CF(data, cf, "[%d] submit -> %d, %zu",
               stream ? stream->id : -1, result, *pnwritten);
-  Curl_safefree(nva);
+  curlx_safefree(nva);
   *pstream = stream;
   Curl_dynhds_free(&h2_headers);
   return result;
@@ -2231,7 +2252,7 @@
   /* if the stream has been closed in egress handling (nghttp2 does that
    * when it does not like the headers, for example */
   if(stream && stream->closed) {
-    infof(data, "stream %u closed", stream->id);
+    infof(data, "stream %d closed", stream->id);
     result = CURLE_SEND_ERROR;
     goto out;
   }
@@ -2440,7 +2461,7 @@
     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",
+      infof(data, "http/2: failed to set user_data for stream %d",
             stream->id);
       DEBUGASSERT(0);
     }
diff --git a/Utilities/cmcurl/lib/http2.h b/Utilities/cmcurl/lib/http2.h
index a14c848..e38dc57 100644
--- a/Utilities/cmcurl/lib/http2.h
+++ b/Utilities/cmcurl/lib/http2.h
@@ -70,7 +70,6 @@
 
 #define Curl_http2_request_upgrade(x, y)  CURLE_UNSUPPORTED_PROTOCOL
 #define Curl_http2_switch(a)              CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_upgrade(a, b, c, d, e) CURLE_UNSUPPORTED_PROTOCOL
 #define Curl_h2_http_1_1_error(x)         0
 #endif
 
diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.c b/Utilities/cmcurl/lib/http_aws_sigv4.c
index 492be1a..55efa12 100644
--- a/Utilities/cmcurl/lib/http_aws_sigv4.c
+++ b/Utilities/cmcurl/lib/http_aws_sigv4.c
@@ -679,6 +679,10 @@
   return compare;
 }
 
+/* @unittest 1979 */
+UNITTEST CURLcode canon_path(const char *q, size_t len,
+                             struct dynbuf *new_path,
+                             bool do_uri_encode);
 UNITTEST CURLcode canon_path(const char *q, size_t len,
                              struct dynbuf *new_path,
                              bool do_uri_encode)
@@ -705,6 +709,8 @@
   return result;
 }
 
+/* @unittest 1980 */
+UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq);
 UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq)
 {
   CURLcode result = CURLE_OK;
@@ -1107,8 +1113,8 @@
   Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1],
                    curlx_str(&provider0), curlx_strlen(&provider0));
 
-  curlx_free(data->state.aptr.userpwd);
-  data->state.aptr.userpwd = auth_headers;
+  curlx_free(data->req.userpwd);
+  data->req.userpwd = auth_headers;
   data->state.authhost.done = TRUE;
   result = CURLE_OK;
 
diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.h b/Utilities/cmcurl/lib/http_aws_sigv4.h
index 25009c6..7dd0024 100644
--- a/Utilities/cmcurl/lib/http_aws_sigv4.h
+++ b/Utilities/cmcurl/lib/http_aws_sigv4.h
@@ -31,10 +31,4 @@
 /* this is for creating aws_sigv4 header output */
 CURLcode Curl_output_aws_sigv4(struct Curl_easy *data);
 
-#ifdef UNITTESTS
-UNITTEST CURLcode canon_path(const char *q, size_t len,
-                             struct dynbuf *new_path, bool normalize);
-UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq);
-#endif
-
 #endif /* HEADER_CURL_HTTP_AWS_SIGV4_H */
diff --git a/Utilities/cmcurl/lib/http_chunks.c b/Utilities/cmcurl/lib/http_chunks.c
index db650b1..aa45c79 100644
--- a/Utilities/cmcurl/lib/http_chunks.c
+++ b/Utilities/cmcurl/lib/http_chunks.c
@@ -258,12 +258,12 @@
             size_t trlen = curlx_dyn_len(&ch->trailer);
             if(cw_next)
               result = Curl_cwriter_write(data, cw_next,
-                                          CLIENTWRITE_HEADER|
+                                          CLIENTWRITE_HEADER |
                                           CLIENTWRITE_TRAILER,
                                           tr, trlen);
             else
               result = Curl_client_write(data,
-                                         CLIENTWRITE_HEADER|
+                                         CLIENTWRITE_HEADER |
                                          CLIENTWRITE_TRAILER,
                                          tr, trlen);
             if(result) {
diff --git a/Utilities/cmcurl/lib/http_digest.c b/Utilities/cmcurl/lib/http_digest.c
index f5b20c5..b700707 100644
--- a/Utilities/cmcurl/lib/http_digest.c
+++ b/Utilities/cmcurl/lib/http_digest.c
@@ -91,7 +91,7 @@
     return CURLE_NOT_BUILT_IN;
 #else
     digest = &data->state.proxydigest;
-    allocuserpwd = &data->state.aptr.proxyuserpwd;
+    allocuserpwd = &data->req.proxyuserpwd;
     userp = data->state.aptr.proxyuser;
     passwdp = data->state.aptr.proxypasswd;
     authp = &data->state.authproxy;
@@ -99,13 +99,13 @@
   }
   else {
     digest = &data->state.digest;
-    allocuserpwd = &data->state.aptr.userpwd;
+    allocuserpwd = &data->req.userpwd;
     userp = data->state.aptr.user;
     passwdp = data->state.aptr.passwd;
     authp = &data->state.authhost;
   }
 
-  Curl_safefree(*allocuserpwd);
+  curlx_safefree(*allocuserpwd);
 
   /* not set means empty */
   if(!userp)
@@ -125,7 +125,7 @@
     return CURLE_OK;
   }
 
-  /* So IE browsers < v7 cut off the URI part at the query part when they
+  /* IE browsers < v7 cut off the URI part at the query part when they
      evaluate the MD5 and some (IIS?) servers work with them so we may need to
      do the Digest IE-style. Note that the different ways cause different MD5
      sums to get sent.
diff --git a/Utilities/cmcurl/lib/http_negotiate.c b/Utilities/cmcurl/lib/http_negotiate.c
index a6e5c19..74d63d6 100644
--- a/Utilities/cmcurl/lib/http_negotiate.c
+++ b/Utilities/cmcurl/lib/http_negotiate.c
@@ -217,13 +217,13 @@
 
     if(proxy) {
 #ifndef CURL_DISABLE_PROXY
-      curlx_free(data->state.aptr.proxyuserpwd);
-      data->state.aptr.proxyuserpwd = userp;
+      curlx_free(data->req.proxyuserpwd);
+      data->req.proxyuserpwd = userp;
 #endif
     }
     else {
-      curlx_free(data->state.aptr.userpwd);
-      data->state.aptr.userpwd = userp;
+      curlx_free(data->req.userpwd);
+      data->req.userpwd = userp;
     }
 
     curlx_free(base64);
diff --git a/Utilities/cmcurl/lib/http_ntlm.c b/Utilities/cmcurl/lib/http_ntlm.c
index c4dbdf9..82b0505 100644
--- a/Utilities/cmcurl/lib/http_ntlm.c
+++ b/Utilities/cmcurl/lib/http_ntlm.c
@@ -139,7 +139,7 @@
 
   if(proxy) {
 #ifndef CURL_DISABLE_PROXY
-    allocuserpwd = &data->state.aptr.proxyuserpwd;
+    allocuserpwd = &data->req.proxyuserpwd;
     userp = data->state.aptr.proxyuser;
     passwdp = data->state.aptr.proxypasswd;
     service = data->set.str[STRING_PROXY_SERVICE_NAME] ?
@@ -152,7 +152,7 @@
 #endif
   }
   else {
-    allocuserpwd = &data->state.aptr.userpwd;
+    allocuserpwd = &data->req.userpwd;
     userp = data->state.aptr.user;
     passwdp = data->state.aptr.passwd;
     service = data->set.str[STRING_SERVICE_NAME] ?
@@ -176,9 +176,9 @@
 #ifdef USE_WINDOWS_SSPI
   if(!Curl_pSecFn) {
     /* not thread-safe and leaks - use curl_global_init() to avoid */
-    CURLcode err = Curl_sspi_global_init();
+    result = Curl_sspi_global_init();
     if(!Curl_pSecFn)
-      return err;
+      return result;
   }
 #ifdef SECPKG_ATTR_ENDPOINT_BINDINGS
   ntlm->sslContext = conn->sslContext;
@@ -242,7 +242,7 @@
       data->info.proxyauthpicked = CURLAUTH_NTLM;
     else
       data->info.httpauthpicked = CURLAUTH_NTLM;
-    Curl_safefree(*allocuserpwd);
+    curlx_safefree(*allocuserpwd);
     authp->done = TRUE;
     break;
   }
diff --git a/Utilities/cmcurl/lib/http_proxy.c b/Utilities/cmcurl/lib/http_proxy.c
index a9521c9..a4bdd7e 100644
--- a/Utilities/cmcurl/lib/http_proxy.c
+++ b/Utilities/cmcurl/lib/http_proxy.c
@@ -164,7 +164,7 @@
 
 void Curl_http_proxy_get_destination(struct Curl_cfilter *cf,
                                      const char **phostname,
-                                     int *pport, bool *pipv6_ip)
+                                     uint16_t *pport, bool *pipv6_ip)
 {
   DEBUGASSERT(cf);
   DEBUGASSERT(cf->conn);
@@ -183,10 +183,7 @@
   else
     *pport = cf->conn->remote_port;
 
-  if(*phostname != cf->conn->host.name)
-    *pipv6_ip = (strchr(*phostname, ':') != NULL);
-  else
-    *pipv6_ip = (bool)cf->conn->bits.ipv6_ip;
+  *pipv6_ip = (strchr(*phostname, ':') != NULL);
 }
 
 struct cf_proxy_ctx {
@@ -202,14 +199,14 @@
   struct cf_proxy_ctx *ctx = cf->ctx;
   const char *hostname = NULL;
   char *authority = NULL;
-  int port;
+  uint16_t port;
   bool ipv6_ip;
   CURLcode result;
   struct httpreq *req = NULL;
 
   Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip);
 
-  authority = curl_maprintf("%s%s%s:%d", ipv6_ip ? "[" : "", hostname,
+  authority = curl_maprintf("%s%s%s:%u", ipv6_ip ? "[" : "", hostname,
                             ipv6_ip ? "]" : "", port);
   if(!authority) {
     result = CURLE_OUT_OF_MEMORY;
@@ -236,9 +233,9 @@
       goto out;
   }
 
-  if(data->state.aptr.proxyuserpwd) {
+  if(data->req.proxyuserpwd) {
     result = Curl_dynhds_h1_cadd_line(&req->headers,
-                                      data->state.aptr.proxyuserpwd);
+                                      data->req.proxyuserpwd);
     if(result)
       goto out;
   }
@@ -394,7 +391,7 @@
 
 struct Curl_cftype Curl_cft_http_proxy = {
   "HTTP-PROXY",
-  CF_TYPE_IP_CONNECT | CF_TYPE_PROXY,
+  CF_TYPE_IP_CONNECT | CF_TYPE_PROXY | CF_TYPE_SETUP,
   0,
   http_proxy_cf_destroy,
   http_proxy_cf_connect,
diff --git a/Utilities/cmcurl/lib/http_proxy.h b/Utilities/cmcurl/lib/http_proxy.h
index c27787b..155b222 100644
--- a/Utilities/cmcurl/lib/http_proxy.h
+++ b/Utilities/cmcurl/lib/http_proxy.h
@@ -37,7 +37,7 @@
 
 void Curl_http_proxy_get_destination(struct Curl_cfilter *cf,
                                      const char **phostname,
-                                     int *pport, bool *pipv6_ip);
+                                     uint16_t *pport, bool *pipv6_ip);
 
 CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
                                         struct Curl_cfilter *cf,
diff --git a/Utilities/cmcurl/lib/httpsrr.c b/Utilities/cmcurl/lib/httpsrr.c
index 169a0b8..90fcb52 100644
--- a/Utilities/cmcurl/lib/httpsrr.c
+++ b/Utilities/cmcurl/lib/httpsrr.c
@@ -30,6 +30,7 @@
 #include "connect.h"
 #include "curl_trc.h"
 #include "curlx/strdup.h"
+#include "curlx/inet_ntop.h"
 
 static CURLcode httpsrr_decode_alpn(const uint8_t *cp, size_t len,
                                     unsigned char *alpns)
@@ -68,71 +69,164 @@
   return CURLE_OK;
 }
 
-CURLcode Curl_httpsrr_set(struct Curl_easy *data,
-                          struct Curl_https_rrinfo *hi,
+#ifdef CURLVERBOSE
+
+static CURLcode httpsrr_print_addr(struct dynbuf *dyn,
+                                   int ai_family,
+                                   const uint8_t *addr,
+                                   size_t total_len)
+{
+  char buf[MAX_IPADR_LEN];
+  size_t i, alen = (ai_family == AF_INET6) ? 16 : 4;
+  const char *sep = "";
+  CURLcode result = CURLE_OK;
+
+  for(i = 0; (i < (total_len / alen)) && !result; ++i) {
+    if(!curlx_inet_ntop(ai_family, addr + (i * alen), buf, sizeof(buf)))
+      result = curlx_dyn_add(dyn, "<error parsing address>");
+    else
+      result = curlx_dyn_addf(dyn, "%s%s", sep, buf);
+    sep = ",";
+  }
+  return result;
+}
+
+void Curl_httpsrr_trace(struct Curl_easy *data,
+                        struct Curl_https_rrinfo *rr)
+{
+  struct dynbuf tmp;
+  CURLcode result;
+
+  if(!rr || !rr->complete) {
+    CURL_TRC_DNS(data, "[HTTPS-RR] not available");
+    return;
+  }
+  curlx_dyn_init(&tmp, 1024);
+  result = Curl_httpsrr_print(&tmp, rr);
+  if(!result)
+    CURL_TRC_DNS(data, "HTTPS-RR: %s", curlx_dyn_ptr(&tmp));
+  else
+    CURL_TRC_DNS(data, "Error printing HTTPS-RR information");
+  curlx_dyn_free(&tmp);
+}
+
+CURLcode Curl_httpsrr_print(struct dynbuf *tmp,
+                            struct Curl_https_rrinfo *rr)
+{
+  CURLcode result;
+  int i;
+
+  curlx_dyn_reset(tmp);
+  result = curlx_dyn_addf(tmp, "%u %s", rr->priority,
+                          rr->target ? rr->target : ".");
+  if(!result && rr->mandatory)
+    result = curlx_dyn_add(tmp, " mandatory-keys(ignored)");
+  if(!result && rr->alpns[0]) {
+    const char *sep = "", *name;
+    result = curlx_dyn_add(tmp, " alpn=");
+    for(i = 0; !result && (i < 4); ++i) {
+      switch(rr->alpns[i]) {
+      case ALPN_h1:
+        name = "http/1.1";
+        break;
+      case ALPN_h2:
+        name = "h2";
+        break;
+      case ALPN_h3:
+        name = "h3";
+        break;
+      default:
+        name = NULL;
+      }
+      if(name) {
+        result = curlx_dyn_addf(tmp, "%s%s", sep, name);
+        sep = ",";
+      }
+    }
+  }
+  if(!result && rr->port_set) {
+    result = curlx_dyn_addf(tmp, " port=%u", rr->port);
+  }
+  if(!result && rr->no_def_alpn)
+    result = curlx_dyn_add(tmp, " no-default-alpn");
+  if(!result && rr->ipv6hints_len) {
+    result = curlx_dyn_add(tmp, " ipv6hint=");
+    if(!result)
+      result = httpsrr_print_addr(
+        tmp, AF_INET6, rr->ipv6hints, rr->ipv6hints_len);
+  }
+  if(!result && rr->ipv4hints_len) {
+    result = curlx_dyn_add(tmp, " ipv4hint=");
+    if(!result)
+      result = httpsrr_print_addr(
+        tmp, AF_INET, rr->ipv4hints, rr->ipv4hints_len);
+  }
+  if(!result && rr->echconfiglist_len)
+    result = curlx_dyn_addf(tmp, " ech=<%zu bytes>", rr->echconfiglist_len);
+
+  return result;
+}
+
+#endif /* CURLVERBOSE */
+
+CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr,
                           uint16_t rrkey, const uint8_t *val, size_t vlen)
 {
   CURLcode result = CURLE_OK;
   switch(rrkey) {
   case HTTPS_RR_CODE_MANDATORY:
-    CURL_TRC_DNS(data, "HTTPS RR MANDATORY left to implement");
+    rr->mandatory = TRUE;
     break;
   case HTTPS_RR_CODE_ALPN: /* str_list */
-    result = httpsrr_decode_alpn(val, vlen, hi->alpns);
-    CURL_TRC_DNS(data, "HTTPS RR ALPN: %u %u %u %u",
-                 hi->alpns[0], hi->alpns[1], hi->alpns[2], hi->alpns[3]);
+    result = httpsrr_decode_alpn(val, vlen, rr->alpns);
     break;
   case HTTPS_RR_CODE_NO_DEF_ALPN:
     if(vlen) /* no data */
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    hi->no_def_alpn = TRUE;
-    CURL_TRC_DNS(data, "HTTPS RR no-def-alpn");
+    rr->no_def_alpn = TRUE;
     break;
   case HTTPS_RR_CODE_IPV4: /* addr4 list */
     if(!vlen || (vlen & 3)) /* the size must be 4-byte aligned */
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    curlx_free(hi->ipv4hints);
-    hi->ipv4hints = curlx_memdup(val, vlen);
-    if(!hi->ipv4hints)
+    curlx_free(rr->ipv4hints);
+    rr->ipv4hints = curlx_memdup(val, vlen);
+    if(!rr->ipv4hints)
       return CURLE_OUT_OF_MEMORY;
-    hi->ipv4hints_len = vlen;
-    CURL_TRC_DNS(data, "HTTPS RR IPv4");
+    rr->ipv4hints_len = vlen;
     break;
   case HTTPS_RR_CODE_ECH:
     if(!vlen)
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    curlx_free(hi->echconfiglist);
-    hi->echconfiglist = curlx_memdup(val, vlen);
-    if(!hi->echconfiglist)
+    curlx_free(rr->echconfiglist);
+    rr->echconfiglist = curlx_memdup(val, vlen);
+    if(!rr->echconfiglist)
       return CURLE_OUT_OF_MEMORY;
-    hi->echconfiglist_len = vlen;
-    CURL_TRC_DNS(data, "HTTPS RR ECH");
+    rr->echconfiglist_len = vlen;
     break;
   case HTTPS_RR_CODE_IPV6: /* addr6 list */
     if(!vlen || (vlen & 15)) /* the size must be 16-byte aligned */
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    curlx_free(hi->ipv6hints);
-    hi->ipv6hints = curlx_memdup(val, vlen);
-    if(!hi->ipv6hints)
+    curlx_free(rr->ipv6hints);
+    rr->ipv6hints = curlx_memdup(val, vlen);
+    if(!rr->ipv6hints)
       return CURLE_OUT_OF_MEMORY;
-    hi->ipv6hints_len = vlen;
-    CURL_TRC_DNS(data, "HTTPS RR IPv6");
+    rr->ipv6hints_len = vlen;
     break;
   case HTTPS_RR_CODE_PORT:
     if(vlen != 2)
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    hi->port = (unsigned short)((val[0] << 8) | val[1]);
-    CURL_TRC_DNS(data, "HTTPS RR port %u", hi->port);
+    rr->port = (uint16_t)((val[0] << 8) | val[1]);
+    rr->port_set = TRUE;
     break;
   default:
-    CURL_TRC_DNS(data, "HTTPS RR unknown code");
+    /* unknown code */
     break;
   }
   return result;
 }
 
-struct Curl_https_rrinfo *
-Curl_httpsrr_dup_move(struct Curl_https_rrinfo *rrinfo)
+struct Curl_https_rrinfo *Curl_httpsrr_dup_move(
+  struct Curl_https_rrinfo *rrinfo)
 {
   struct Curl_https_rrinfo *dup = curlx_memdup(rrinfo, sizeof(*rrinfo));
   if(dup)
@@ -142,17 +236,27 @@
 
 void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo)
 {
-  Curl_safefree(rrinfo->target);
-  Curl_safefree(rrinfo->echconfiglist);
-  Curl_safefree(rrinfo->ipv4hints);
-  Curl_safefree(rrinfo->ipv6hints);
-  Curl_safefree(rrinfo->rrname);
+  curlx_safefree(rrinfo->target);
+  curlx_safefree(rrinfo->echconfiglist);
+  curlx_safefree(rrinfo->ipv4hints);
+  curlx_safefree(rrinfo->ipv6hints);
+  curlx_safefree(rrinfo->rrname);
+  rrinfo->complete = FALSE;
+}
+
+bool Curl_httpsrr_applicable(struct Curl_easy *data,
+                             const struct Curl_https_rrinfo *rr)
+{
+  if(!data->conn || !rr)
+    return FALSE;
+  return (!rr->target || !rr->target[0] ||
+          (rr->target[0] == '.' && !rr->target[1])) &&
+         (!rr->port_set || rr->port == data->conn->remote_port);
 }
 
 #ifdef USE_ARES
 
-static CURLcode httpsrr_opt(struct Curl_easy *data,
-                            const ares_dns_rr_t *rr,
+static CURLcode httpsrr_opt(const ares_dns_rr_t *rr,
                             ares_dns_rr_key_t key, size_t idx,
                             struct Curl_https_rrinfo *hinfo)
 {
@@ -161,11 +265,10 @@
   size_t len = 0;
 
   code = ares_dns_rr_get_opt(rr, key, idx, &val, &len);
-  return Curl_httpsrr_set(data, hinfo, code, val, len);
+  return Curl_httpsrr_set(hinfo, code, val, len);
 }
 
-CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data,
-                                const ares_dns_record_t *dnsrec,
+CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec,
                                 struct Curl_https_rrinfo *hinfo)
 {
   CURLcode result = CURLE_OK;
@@ -188,19 +291,18 @@
         result = CURLE_OUT_OF_MEMORY;
         goto out;
       }
-      CURL_TRC_DNS(data, "HTTPS RR target: %s", hinfo->target);
     }
-    CURL_TRC_DNS(data, "HTTPS RR priority: %u",
-                 ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY));
+    hinfo->priority = ares_dns_rr_get_u16(rr, ARES_RR_HTTPS_PRIORITY);
     for(opt = 0; opt < ares_dns_rr_get_opt_cnt(rr, ARES_RR_HTTPS_PARAMS);
         opt++) {
-      result = httpsrr_opt(data, rr, ARES_RR_HTTPS_PARAMS, opt, hinfo);
+      result = httpsrr_opt(rr, ARES_RR_HTTPS_PARAMS, opt, hinfo);
       if(result)
         break;
     }
   }
 out:
-  Curl_safefree(hinfo->rrname);
+  hinfo->complete = !result;
+  curlx_safefree(hinfo->rrname);
   return result;
 }
 
diff --git a/Utilities/cmcurl/lib/httpsrr.h b/Utilities/cmcurl/lib/httpsrr.h
index 8fe7c0c..28a790d 100644
--- a/Utilities/cmcurl/lib/httpsrr.h
+++ b/Utilities/cmcurl/lib/httpsrr.h
@@ -35,6 +35,7 @@
 #define MAX_HTTPSRR_ALPNS     4
 
 struct Curl_easy;
+struct dynbuf;
 
 struct Curl_https_rrinfo {
   char *rrname; /* if NULL, the same as the URL hostname */
@@ -51,20 +52,26 @@
   size_t ipv6hints_len;
   unsigned char alpns[MAX_HTTPSRR_ALPNS]; /* keytag = 1 */
   /* store parsed alpnid entries in the array, end with ALPN_none */
-  int port; /* -1 means not set */
+  uint16_t port;
   uint16_t priority;
   BIT(no_def_alpn); /* keytag = 2 */
+  BIT(mandatory); /* keytag = 0 */
+  BIT(port_set); /* port value has been assigned */
+  BIT(complete); /* values have been successfully assigned */
 };
 
-CURLcode Curl_httpsrr_set(struct Curl_easy *data,
-                          struct Curl_https_rrinfo *hi,
+CURLcode Curl_httpsrr_set(struct Curl_https_rrinfo *rr,
                           uint16_t rrkey, const uint8_t *val, size_t vlen);
 
-struct Curl_https_rrinfo *
-Curl_httpsrr_dup_move(struct Curl_https_rrinfo *rrinfo);
+struct Curl_https_rrinfo *Curl_httpsrr_dup_move(
+  struct Curl_https_rrinfo *rrinfo);
 
 void Curl_httpsrr_cleanup(struct Curl_https_rrinfo *rrinfo);
 
+/* TRUE if the record is applicable to the transfer and its connection. */
+bool Curl_httpsrr_applicable(struct Curl_easy *data,
+                             const struct Curl_https_rrinfo *rr);
+
 /*
  * Code points for DNS wire format SvcParams as per RFC 9460
  */
@@ -77,10 +84,19 @@
 #define HTTPS_RR_CODE_IPV6            0x06
 
 #ifdef USE_ARES
-CURLcode Curl_httpsrr_from_ares(struct Curl_easy *data,
-                                const ares_dns_record_t *dnsrec,
+CURLcode Curl_httpsrr_from_ares(const ares_dns_record_t *dnsrec,
                                 struct Curl_https_rrinfo *hinfo);
 #endif /* USE_ARES */
+
+#ifdef CURLVERBOSE
+CURLcode Curl_httpsrr_print(struct dynbuf *tmp,
+                            struct Curl_https_rrinfo *rr);
+void Curl_httpsrr_trace(struct Curl_easy *data,
+                        struct Curl_https_rrinfo *rr);
+#else
+#define Curl_httpsrr_trace(a, b) Curl_nop_stmt
+#endif
+
 #endif /* USE_HTTPSRR */
 
 #endif /* HEADER_CURL_HTTPSRR_H */
diff --git a/Utilities/cmcurl/lib/idn.c b/Utilities/cmcurl/lib/idn.c
index 2db4f6d..f2b954e 100644
--- a/Utilities/cmcurl/lib/idn.c
+++ b/Utilities/cmcurl/lib/idn.c
@@ -33,10 +33,10 @@
 #include <idn2.h>
 
 #if defined(_WIN32) && defined(UNICODE)
-#define IDN2_LOOKUP(name, host, flags)                                  \
+#define IDN2_LOOKUP(name, host, flags)                           \
   idn2_lookup_u8((const uint8_t *)name, (uint8_t **)host, flags)
 #else
-#define IDN2_LOOKUP(name, host, flags)                          \
+#define IDN2_LOOKUP(name, host, flags)                         \
   idn2_lookup_ul((const char *)(name), (char **)(host), flags)
 #endif
 #endif /* USE_LIBIDN2 */
@@ -348,7 +348,7 @@
  */
 void Curl_free_idnconverted_hostname(struct hostname *host)
 {
-  Curl_safefree(host->encalloc);
+  curlx_safefree(host->encalloc);
 }
 
 #endif /* USE_IDN */
diff --git a/Utilities/cmcurl/lib/if2ip.c b/Utilities/cmcurl/lib/if2ip.c
index 05e3f84..b71254a 100644
--- a/Utilities/cmcurl/lib/if2ip.c
+++ b/Utilities/cmcurl/lib/if2ip.c
@@ -210,13 +210,13 @@
   memcpy(req.ifr_name, interf, len + 1);
   req.ifr_addr.sa_family = AF_INET;
 
-#if defined(__GNUC__) && defined(_AIX)
+#if defined(CURL_HAVE_DIAG) && defined(_AIX)
 /* Suppress warning inside system headers */
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wshift-sign-overflow"
 #endif
   if(ioctl(dummy, SIOCGIFADDR, &req) < 0) {
-#if defined(__GNUC__) && defined(_AIX)
+#if defined(CURL_HAVE_DIAG) && defined(_AIX)
 #pragma GCC diagnostic pop
 #endif
     sclose(dummy);
diff --git a/Utilities/cmcurl/lib/imap.c b/Utilities/cmcurl/lib/imap.c
index 0546595..5ef2a2c 100644
--- a/Utilities/cmcurl/lib/imap.c
+++ b/Utilities/cmcurl/lib/imap.c
@@ -775,7 +775,8 @@
   char *mailbox;
 
   /* Invalidate old information as we are switching mailboxes */
-  Curl_safefree(imapc->mailbox);
+  curlx_safefree(imapc->mailbox);
+  imapc->mb_uidvalidity_set = FALSE;
 
   /* Check we have a mailbox */
   if(!imap->mailbox) {
@@ -1024,7 +1025,6 @@
                                            imapstate instate)
 {
   CURLcode result = CURLE_OK;
-  struct connectdata *conn = data->conn;
   const char *line = curlx_dyn_ptr(&imapc->pp.recvbuf);
 
   (void)instate;
@@ -1076,7 +1076,7 @@
       line += wordlen;
     }
   }
-  else if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
+  else if(data->set.use_ssl && !Curl_xfer_is_secure(data)) {
     /* PREAUTH is not compatible with STARTTLS. */
     if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) {
       /* Switch to TLS connection now */
@@ -1452,8 +1452,8 @@
       if(result)
         return result;
 
-      infof(data, "Written %zu bytes, %" FMT_OFF_TU
-            " bytes are left for transfer", chunk, size - chunk);
+      infof(data, "Written %zu bytes, %" FMT_OFF_T
+            " bytes are left for transfer", chunk, (curl_off_t)(size - chunk));
 
       /* Have we used the entire overflow or part of it?*/
       if(pp->overflow > chunk) {
@@ -1696,14 +1696,15 @@
 
 static void imap_easy_reset(struct IMAP *imap)
 {
-  Curl_safefree(imap->mailbox);
-  Curl_safefree(imap->uid);
-  Curl_safefree(imap->mindex);
-  Curl_safefree(imap->section);
-  Curl_safefree(imap->partial);
-  Curl_safefree(imap->query);
-  Curl_safefree(imap->custom);
-  Curl_safefree(imap->custom_params);
+  curlx_safefree(imap->mailbox);
+  curlx_safefree(imap->uid);
+  curlx_safefree(imap->mindex);
+  curlx_safefree(imap->section);
+  curlx_safefree(imap->partial);
+  curlx_safefree(imap->query);
+  curlx_safefree(imap->custom);
+  curlx_safefree(imap->custom_params);
+  imap->uidvalidity_set = FALSE;
   /* Clear the transfer mode for the next request */
   imap->transfer = PPTRANSFER_BODY;
 }
@@ -2249,7 +2250,7 @@
   (void)klen;
   Curl_pp_disconnect(&imapc->pp);
   curlx_dyn_free(&imapc->dyn);
-  Curl_safefree(imapc->mailbox);
+  curlx_safefree(imapc->mailbox);
   curlx_free(imapc);
 }
 
@@ -2302,7 +2303,7 @@
 /*
  * IMAP protocol.
  */
-static const struct Curl_protocol Curl_protocol_imap = {
+const struct Curl_protocol Curl_protocol_imap = {
   imap_setup_connection,            /* setup_connection */
   imap_do,                          /* do_it */
   imap_done,                        /* done */
@@ -2317,44 +2318,9 @@
   imap_disconnect,                  /* disconnect */
   ZERO_NULL,                        /* write_resp */
   ZERO_NULL,                        /* write_resp_hd */
-  ZERO_NULL,                        /* connection_check */
+  ZERO_NULL,                        /* connection_is_dead */
   ZERO_NULL,                        /* attach connection */
   ZERO_NULL,                        /* follow */
 };
 
 #endif /* CURL_DISABLE_IMAP */
-
-/*
- * IMAP protocol handler.
- */
-const struct Curl_scheme Curl_scheme_imap = {
-  "imap",                           /* scheme */
-#ifdef CURL_DISABLE_IMAP
-  ZERO_NULL,
-#else
-  &Curl_protocol_imap,
-#endif
-  CURLPROTO_IMAP,                   /* protocol */
-  CURLPROTO_IMAP,                   /* family */
-  PROTOPT_CLOSEACTION |             /* flags */
-  PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE |
-  PROTOPT_CONN_REUSE,
-  PORT_IMAP,                        /* defport */
-};
-
-/*
- * IMAPS protocol handler.
- */
-const struct Curl_scheme Curl_scheme_imaps = {
-  "imaps",                          /* scheme */
-#if defined(CURL_DISABLE_IMAP) || !defined(USE_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_imap,
-#endif
-  CURLPROTO_IMAPS,                  /* protocol */
-  CURLPROTO_IMAP,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
-  PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE,
-  PORT_IMAPS,                       /* defport */
-};
diff --git a/Utilities/cmcurl/lib/imap.h b/Utilities/cmcurl/lib/imap.h
index f179991..ecfb1ec 100644
--- a/Utilities/cmcurl/lib/imap.h
+++ b/Utilities/cmcurl/lib/imap.h
@@ -23,8 +23,6 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_imap;
-extern const struct Curl_scheme Curl_scheme_imaps;
 
 /* Authentication type flags */
 #define IMAP_TYPE_CLEARTEXT (1 << 0)
@@ -34,4 +32,8 @@
 #define IMAP_TYPE_NONE      0
 #define IMAP_TYPE_ANY       (IMAP_TYPE_CLEARTEXT | IMAP_TYPE_SASL)
 
+#ifndef CURL_DISABLE_IMAP
+extern const struct Curl_protocol Curl_protocol_imap;
+#endif
+
 #endif /* HEADER_CURL_IMAP_H */
diff --git a/Utilities/cmcurl/lib/ldap.c b/Utilities/cmcurl/lib/ldap.c
index e223078..16c93ee 100644
--- a/Utilities/cmcurl/lib/ldap.c
+++ b/Utilities/cmcurl/lib/ldap.c
@@ -27,7 +27,7 @@
 
 #if !defined(CURL_DISABLE_LDAP) && !defined(USE_OPENLDAP)
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 #endif
@@ -115,7 +115,7 @@
 
 static curl_ldap_num_t ldap_url_parse_low(struct Curl_easy *data,
                                           const struct connectdata *conn,
-                                          LDAPURLDesc **ludp);
+                                          LDAPURLDesc **ludpp);
 static void ldap_free_urldesc_low(LDAPURLDesc *ludp);
 
 #undef ldap_free_urldesc
@@ -185,8 +185,8 @@
   }
 
   if(method && user && passwd) {
-    CURLcode res = Curl_create_sspi_identity(user, passwd, &cred);
-    if(!res) {
+    CURLcode result = Curl_create_sspi_identity(user, passwd, &cred);
+    if(!result) {
       rc = ldap_bind_s(server, NULL, (TCHAR *)&cred, method);
       Curl_sspi_free_identity(&cred);
     }
@@ -303,9 +303,8 @@
   if(ldap_ssl)
     server = ldap_sslinit(host, (curl_ldap_num_t)ipquad.remote_port, 1);
   else
-#else
-    server = ldap_init(host, (curl_ldap_num_t)ipquad.remote_port);
 #endif
+    server = ldap_init(host, (curl_ldap_num_t)ipquad.remote_port);
   if(!server) {
     failf(data, "LDAP: cannot setup connect to %s:%u",
           conn->host.dispname, ipquad.remote_port);
@@ -388,7 +387,6 @@
   }
 
 #ifdef USE_WIN32_LDAP
-  ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto);
   rc = ldap_win_bind(data, server, user, passwd);
 #else
   rc = ldap_simple_bind_s(server, user, passwd);
@@ -991,45 +989,13 @@
   ZERO_NULL,                            /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic pop
 #endif
 
 #endif /* !CURL_DISABLE_LDAP && !USE_OPENLDAP */
-
-/*
- * LDAP
- */
-const struct Curl_scheme Curl_scheme_ldap = {
-  "ldap",                               /* scheme */
-#ifdef CURL_DISABLE_LDAP
-  ZERO_NULL,
-#else
-  &Curl_protocol_ldap,
-#endif
-  CURLPROTO_LDAP,                       /* protocol */
-  CURLPROTO_LDAP,                       /* family */
-  PROTOPT_SSL_REUSE,                    /* flags */
-  PORT_LDAP,                            /* defport */
-};
-
-/*
- * LDAPS
- */
-const struct Curl_scheme Curl_scheme_ldaps = {
-  "ldaps",                              /* scheme */
-#if defined(CURL_DISABLE_LDAP) || !defined(HAVE_LDAP_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_ldap,
-#endif
-  CURLPROTO_LDAPS,                      /* protocol */
-  CURLPROTO_LDAP,                       /* family */
-  PROTOPT_SSL,                          /* flags */
-  PORT_LDAPS,                           /* defport */
-};
diff --git a/Utilities/cmcurl/lib/llist.c b/Utilities/cmcurl/lib/llist.c
index 0e3084b..3ec85e4 100644
--- a/Utilities/cmcurl/lib/llist.c
+++ b/Utilities/cmcurl/lib/llist.c
@@ -40,7 +40,7 @@
 #define VERIFYNODE(x) x
 #endif
 /*
- * @unittest: 1300
+ * @unittest 1300
  */
 void Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor)
 {
@@ -62,7 +62,7 @@
  *
  * The 'ne' argument should be a pointer into the object to store.
  *
- * @unittest: 1300
+ * @unittest 1300
  */
 void Curl_llist_insert_next(struct Curl_llist *list,
                             struct Curl_llist_node *e, /* may be NULL */
@@ -112,7 +112,7 @@
  *
  * The 'ne' argument should be a pointer into the object to store.
  *
- * @unittest: 1300
+ * @unittest 1300
  */
 void Curl_llist_append(struct Curl_llist *list, const void *p,
                        struct Curl_llist_node *ne)
@@ -168,11 +168,7 @@
   return ptr;
 }
 
-/*
- * @unittest: 1300
- */
-UNITTEST void Curl_node_uremove(struct Curl_llist_node *e, void *user);
-UNITTEST void Curl_node_uremove(struct Curl_llist_node *e, void *user)
+static void node_uremove(struct Curl_llist_node *e, void *user)
 {
   struct Curl_llist *list;
   void *ptr;
@@ -190,7 +186,7 @@
 
 void Curl_node_remove(struct Curl_llist_node *e)
 {
-  Curl_node_uremove(e, NULL);
+  node_uremove(e, NULL);
 }
 
 void Curl_llist_destroy(struct Curl_llist *list, void *user)
@@ -198,7 +194,7 @@
   if(list) {
     DEBUGASSERT(list->_init == LLISTINIT);
     while(list->_size > 0)
-      Curl_node_uremove(list->_tail, user);
+      node_uremove(list->_tail, user);
   }
 }
 
@@ -212,9 +208,13 @@
 }
 
 #ifdef UNITTESTS
-/* Curl_llist_tail() returns the last 'struct Curl_llist_node *', which
-   might be NULL */
-struct Curl_llist_node *Curl_llist_tail(struct Curl_llist *list)
+/* llist_tail() returns the last 'struct Curl_llist_node *', which might be
+   NULL
+
+   @unittest 1300
+*/
+UNITTEST struct Curl_llist_node *llist_tail(struct Curl_llist *list);
+UNITTEST struct Curl_llist_node *llist_tail(struct Curl_llist *list)
 {
   DEBUGASSERT(list);
   DEBUGASSERT(list->_init == LLISTINIT);
@@ -248,16 +248,18 @@
 }
 
 #ifdef UNITTESTS
+/* llist_node_prev() returns the previous element in a list from a given
+   Curl_llist_node
 
-/* Curl_node_prev() returns the previous element in a list from a given
-   Curl_llist_node */
-struct Curl_llist_node *Curl_node_prev(struct Curl_llist_node *n)
+   @unittest 1300
+*/
+UNITTEST struct Curl_llist_node *llist_node_prev(struct Curl_llist_node *n);
+UNITTEST struct Curl_llist_node *llist_node_prev(struct Curl_llist_node *n)
 {
   DEBUGASSERT(n);
   DEBUGASSERT(n->_init == NODEINIT);
   return VERIFYNODE(n->_prev);
 }
-
 #endif
 
 struct Curl_llist *Curl_node_llist(struct Curl_llist_node *n)
diff --git a/Utilities/cmcurl/lib/llist.h b/Utilities/cmcurl/lib/llist.h
index 09c695d..28e958d 100644
--- a/Utilities/cmcurl/lib/llist.h
+++ b/Utilities/cmcurl/lib/llist.h
@@ -62,10 +62,6 @@
    might be NULL */
 struct Curl_llist_node *Curl_llist_head(struct Curl_llist *list);
 
-/* Curl_llist_tail() returns the last 'struct Curl_llist_node *', which
-   might be NULL */
-struct Curl_llist_node *Curl_llist_tail(struct Curl_llist *list);
-
 /* Curl_llist_count() returns a size_t the number of nodes in the list */
 size_t Curl_llist_count(struct Curl_llist *list);
 
@@ -73,17 +69,13 @@
 void *Curl_node_elem(struct Curl_llist_node *n);
 
 /* Remove the node from the list and return the custom data
- * from a Curl_llist_node. Will NOT invoke a registered `dtor`. */
+ * from a Curl_llist_node. Does NOT invoke a registered `dtor`. */
 void *Curl_node_take_elem(struct Curl_llist_node *e);
 
 /* Curl_node_next() returns the next element in a list from a given
    Curl_llist_node */
 struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n);
 
-/* Curl_node_prev() returns the previous element in a list from a given
-   Curl_llist_node */
-struct Curl_llist_node *Curl_node_prev(struct Curl_llist_node *n);
-
 /* Curl_node_llist() return the list the node is in or NULL. */
 struct Curl_llist *Curl_node_llist(struct Curl_llist_node *n);
 
diff --git a/Utilities/cmcurl/lib/md4.c b/Utilities/cmcurl/lib/md4.c
index 1ac6ef1..0213483 100644
--- a/Utilities/cmcurl/lib/md4.c
+++ b/Utilities/cmcurl/lib/md4.c
@@ -33,7 +33,7 @@
 /* OpenSSL 3.0.0 marks the MD4 functions as deprecated */
 #define OPENSSL_NO_MD4
 #else
-/* Cover also OPENSSL_NO_MD4 configured in openssl */
+/* Cover also OPENSSL_NO_MD4 configured in OpenSSL */
 #include <openssl/opensslconf.h>
 #endif
 #endif /* USE_OPENSSL */
@@ -44,19 +44,41 @@
 
 /* When OpenSSL or wolfSSL is available, we use their MD4 functions. */
 
-#if defined(USE_WOLFSSL) && !defined(NO_MD4)
-#include <wolfssl/openssl/md4.h>
-#define VOID_MD4_INIT
+#if defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4)
+#include <openssl/md4.h>
 
-#ifdef OPENSSL_COEXIST
-#  define MD4_CTX    WOLFSSL_MD4_CTX
-#  define MD4_Init   wolfSSL_MD4_Init
-#  define MD4_Update wolfSSL_MD4_Update
-#  define MD4_Final  wolfSSL_MD4_Final
+/* Map directly to OpenSSL implementation */
+#define my_md4_ctx    MD4_CTX
+#define my_md4_init   MD4_Init
+#define my_md4_update MD4_Update
+#define my_md4_final  MD4_Final
+
+#elif defined(USE_WOLFSSL) && !defined(NO_MD4)
+#include <wolfssl/wolfcrypt/md4.h>
+#include <wolfssl/version.h>
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x05007006
+typedef wc_Md4 my_md4_ctx;
+#else
+typedef Md4 my_md4_ctx;
 #endif
 
-#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4)
-#include <openssl/md4.h>
+static int my_md4_init(my_md4_ctx *ctx)
+{
+  wc_InitMd4(ctx);
+  return 1;
+}
+
+static void my_md4_update(my_md4_ctx *ctx,
+                          const void *input, unsigned long len)
+{
+  wc_Md4Update(ctx, input, (word32)len);
+}
+
+static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx)
+{
+  wc_Md4Final(ctx, digest);
+}
 
 #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \
               (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) && \
@@ -68,19 +90,20 @@
               (__IPHONE_OS_VERSION_MIN_REQUIRED < 130000))
 #include <CommonCrypto/CommonDigest.h>
 
-typedef CC_MD4_CTX MD4_CTX;
+typedef CC_MD4_CTX my_md4_ctx;
 
-static int MD4_Init(MD4_CTX *ctx)
+static int my_md4_init(my_md4_ctx *ctx)
 {
   return CC_MD4_Init(ctx);
 }
 
-static void MD4_Update(MD4_CTX *ctx, const void *input, unsigned long len)
+static void my_md4_update(my_md4_ctx *ctx,
+                          const void *input, unsigned long len)
 {
   (void)CC_MD4_Update(ctx, input, (CC_LONG)len);
 }
 
-static void MD4_Final(unsigned char *digest, MD4_CTX *ctx)
+static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx)
 {
   (void)CC_MD4_Final(digest, ctx);
 }
@@ -92,9 +115,9 @@
   HCRYPTPROV hCryptProv;
   HCRYPTHASH hHash;
 };
-typedef struct md4_ctx MD4_CTX;
+typedef struct md4_ctx my_md4_ctx;
 
-static int MD4_Init(MD4_CTX *ctx)
+static int my_md4_init(my_md4_ctx *ctx)
 {
   ctx->hCryptProv = 0;
   ctx->hHash = 0;
@@ -112,12 +135,13 @@
   return 1;
 }
 
-static void MD4_Update(MD4_CTX *ctx, const void *input, unsigned long len)
+static void my_md4_update(my_md4_ctx *ctx,
+                          const void *input, unsigned long len)
 {
   CryptHashData(ctx->hHash, (const BYTE *)input, (unsigned int)len, 0);
 }
 
-static void MD4_Final(unsigned char *digest, MD4_CTX *ctx)
+static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx)
 {
   unsigned long length = 0;
 
@@ -135,20 +159,21 @@
 #elif defined(USE_GNUTLS)
 #include <nettle/md4.h>
 
-typedef struct md4_ctx MD4_CTX;
+typedef struct md4_ctx my_md4_ctx;
 
-static int MD4_Init(MD4_CTX *ctx)
+static int my_md4_init(my_md4_ctx *ctx)
 {
   md4_init(ctx);
   return 1;
 }
 
-static void MD4_Update(MD4_CTX *ctx, const void *input, unsigned long len)
+static void my_md4_update(my_md4_ctx *ctx,
+                          const void *input, unsigned long len)
 {
   md4_update(ctx, len, input);
 }
 
-static void MD4_Final(unsigned char *digest, MD4_CTX *ctx)
+static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx)
 {
   md4_digest(ctx, MD4_DIGEST_SIZE, digest);
 }
@@ -187,7 +212,7 @@
   unsigned char buffer[64];
   uint32_t block[16];
 };
-typedef struct md4_ctx MD4_CTX;
+typedef struct md4_ctx my_md4_ctx;
 
 /*
  * The basic MD4 functions.
@@ -229,7 +254,7 @@
  * This processes one or more 64-byte data blocks, but does NOT update
  * the bit counters. There are no alignment requirements.
  */
-static const void *my_md4_body(MD4_CTX *ctx,
+static const void *my_md4_body(my_md4_ctx *ctx,
                                const void *input, unsigned long size)
 {
   const unsigned char *ptr;
@@ -320,7 +345,7 @@
   return ptr;
 }
 
-static int MD4_Init(MD4_CTX *ctx)
+static int my_md4_init(my_md4_ctx *ctx)
 {
   ctx->a = 0x67452301;
   ctx->b = 0xefcdab89;
@@ -333,7 +358,8 @@
   return 1;
 }
 
-static void MD4_Update(MD4_CTX *ctx, const void *input, unsigned long len)
+static void my_md4_update(my_md4_ctx *ctx,
+                          const void *input, unsigned long len)
 {
   uint32_t saved_lo;
   unsigned long used;
@@ -368,7 +394,7 @@
   memcpy(ctx->buffer, input, len);
 }
 
-static void MD4_Final(unsigned char *digest, MD4_CTX *ctx)
+static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx)
 {
   unsigned long used, available;
 
@@ -424,17 +450,11 @@
 CURLcode Curl_md4it(unsigned char *output,
                     const unsigned char *input, const size_t len)
 {
-  MD4_CTX ctx;
-
-#ifdef VOID_MD4_INIT
-  MD4_Init(&ctx);
-#else
-  if(!MD4_Init(&ctx))
+  my_md4_ctx ctx;
+  if(!my_md4_init(&ctx))
     return CURLE_FAILED_INIT;
-#endif
-
-  MD4_Update(&ctx, input, curlx_uztoui(len));
-  MD4_Final(output, &ctx);
+  my_md4_update(&ctx, input, curlx_uztoui(len));
+  my_md4_final(output, &ctx);
   return CURLE_OK;
 }
 
diff --git a/Utilities/cmcurl/lib/md5.c b/Utilities/cmcurl/lib/md5.c
index c2bd176..4dd0d7c 100644
--- a/Utilities/cmcurl/lib/md5.c
+++ b/Utilities/cmcurl/lib/md5.c
@@ -67,14 +67,9 @@
   md5_digest(ctx, 16, digest);
 }
 
-#elif (defined(USE_OPENSSL) && \
-  !defined(OPENSSL_NO_MD5) && !defined(OPENSSL_NO_DEPRECATED_3_0)) || \
-  (defined(USE_WOLFSSL) && !defined(NO_MD5) && !defined(OPENSSL_COEXIST))
-#ifdef USE_OPENSSL
+#elif defined(USE_OPENSSL) && \
+  !defined(OPENSSL_NO_MD5) && !defined(OPENSSL_NO_DEPRECATED_3_0)
 #include <openssl/md5.h>
-#else
-#include <wolfssl/openssl/md5.h>
-#endif
 
 typedef MD5_CTX my_md5_ctx;
 
@@ -98,27 +93,26 @@
 }
 
 #elif defined(USE_WOLFSSL) && !defined(NO_MD5)
-#include <wolfssl/openssl/md5.h>
+#include <wolfssl/wolfcrypt/md5.h>
 
-typedef WOLFSSL_MD5_CTX my_md5_ctx;
+typedef wc_Md5 my_md5_ctx;
 
 static CURLcode my_md5_init(void *ctx)
 {
-  if(!wolfSSL_MD5_Init(ctx))
+  if(wc_InitMd5(ctx))
     return CURLE_OUT_OF_MEMORY;
-
   return CURLE_OK;
 }
 
 static void my_md5_update(void *ctx,
                           const unsigned char *input, unsigned int len)
 {
-  (void)wolfSSL_MD5_Update(ctx, input, len);
+  (void)wc_Md5Update(ctx, input, (word32)len);
 }
 
 static void my_md5_final(unsigned char *digest, void *ctx)
 {
-  (void)wolfSSL_MD5_Final(digest, ctx);
+  (void)wc_Md5Final(ctx, digest);
 }
 
 #elif defined(USE_MBEDTLS) && \
@@ -546,14 +540,19 @@
  * Returns CURLE_OK on success.
  */
 CURLcode Curl_md5it(unsigned char *output,
-                    const unsigned char *input, const size_t len)
+                    const unsigned char *input, size_t len)
 {
   CURLcode result;
   my_md5_ctx ctx;
 
   result = my_md5_init(&ctx);
   if(!result) {
-    my_md5_update(&ctx, input, curlx_uztoui(len));
+    do {
+      unsigned int ilen = (unsigned int)CURLMIN(len, UINT_MAX);
+      my_md5_update(&ctx, input, ilen);
+      input += ilen;
+      len -= ilen;
+    } while(len);
     my_md5_final(output, &ctx);
   }
   return result;
diff --git a/Utilities/cmcurl/lib/memdebug.c b/Utilities/cmcurl/lib/memdebug.c
index af4fc9d..6eda7a8 100644
--- a/Utilities/cmcurl/lib/memdebug.c
+++ b/Utilities/cmcurl/lib/memdebug.c
@@ -65,14 +65,14 @@
 static char membuf[10000];
 static size_t memwidx = 0; /* write index */
 
-#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
+#ifdef USE_MUTEX
 static bool dbg_mutex_init = 0;
 static curl_mutex_t dbg_mutex;
 #endif
 
 static bool curl_dbg_lock(void)
 {
-#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
+#ifdef USE_MUTEX
   if(dbg_mutex_init) {
     Curl_mutex_acquire(&dbg_mutex);
     return TRUE;
@@ -83,7 +83,7 @@
 
 static void curl_dbg_unlock(bool was_locked)
 {
-#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
+#ifdef USE_MUTEX
   if(was_locked)
     Curl_mutex_release(&dbg_mutex);
 #else
@@ -99,6 +99,7 @@
    _exit() comes after the atexit handlers are called. curl/curl#6620 */
 static void curl_dbg_cleanup(void)
 {
+  bool locked = curl_dbg_lock();
   if(curl_dbg_logfile &&
      curl_dbg_logfile != stderr &&
      curl_dbg_logfile != stdout) {
@@ -108,7 +109,8 @@
     fclose(curl_dbg_logfile);
   }
   curl_dbg_logfile = NULL;
-#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
+  curl_dbg_unlock(locked);
+#ifdef USE_MUTEX
   if(dbg_mutex_init) {
     Curl_mutex_destroy(&dbg_mutex);
     dbg_mutex_init = FALSE;
@@ -157,7 +159,7 @@
       setbuf(curl_dbg_logfile, (char *)NULL);
 #endif
   }
-#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
+#ifdef USE_MUTEX
   if(!dbg_mutex_init) {
     dbg_mutex_init = TRUE;
     Curl_mutex_init(&dbg_mutex);
diff --git a/Utilities/cmcurl/lib/mime.c b/Utilities/cmcurl/lib/mime.c
index 37ea514..b7e51aa 100644
--- a/Utilities/cmcurl/lib/mime.c
+++ b/Utilities/cmcurl/lib/mime.c
@@ -44,11 +44,14 @@
 #include "slist.h"
 #include "curlx/dynbuf.h"
 
+#define MAX_MIME_LEVELS 40 /* avoid deep nesting */
+
 #define READ_ERROR   ((size_t)-1)
 #define STOP_FILLING ((size_t)-2)
 
 static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems,
-                                 void *instream, bool *hasread);
+                                 void *instream, bool *hasread,
+                                 size_t call_depth);
 static curl_off_t mime_size(curl_mimepart *part);
 
 /* Quoted-printable character class table.
@@ -162,19 +165,20 @@
 static FILE *vmsfopenread(const char *file, const char *mode)
 {
   curlx_struct_stat statbuf;
-  int result;
+  int res = curlx_stat(file, &statbuf);
 
-  result = curlx_stat(file, &statbuf);
-
-  switch(statbuf.st_fab_rfm) {
-  case FAB$C_VAR:
-  case FAB$C_VFC:
-  case FAB$C_STMCR:
-    return curlx_fopen(file, FOPEN_READTEXT); /* VMS */
-    break;
-  default:
-    return curlx_fopen(file, FOPEN_READTEXT, "rfm=stmlf", "ctx=stm");
+  if(res != -1) {
+    switch(statbuf.st_fab_rfm) {
+    case FAB$C_VAR:
+    case FAB$C_VFC:
+    case FAB$C_STMCR:
+      return curlx_fopen(file, FOPEN_READTEXT); /* VMS */
+      break;
+    default:
+      return curlx_fopen(file, FOPEN_READTEXT, "rfm=stmlf", "ctx=stm");
+    }
   }
+  return NULL;
 }
 
 #define fopen_read vmsfopenread
@@ -599,7 +603,7 @@
 
 static void mime_mem_free(void *ptr)
 {
-  Curl_safefree(((curl_mimepart *)ptr)->data);
+  curlx_safefree(((curl_mimepart *)ptr)->data);
 }
 
 /* Named file callbacks. */
@@ -650,7 +654,7 @@
     curlx_fclose(part->fp);
     part->fp = NULL;
   }
-  Curl_safefree(part->data);
+  curlx_safefree(part->data);
 }
 
 /* Subparts callbacks. */
@@ -686,11 +690,16 @@
 }
 
 /* Read a non-encoded part content. */
-static size_t read_part_content(curl_mimepart *part,
-                                char *buffer, size_t bufsize, bool *hasread)
+
+static size_t read_part_content(curl_mimepart *part, char *buffer,
+                                size_t bufsize, bool *hasread,
+                                size_t call_depth)
 {
   size_t sz = 0;
 
+  if(++call_depth > MAX_MIME_LEVELS)
+    return READ_ERROR;
+
   switch(part->lastreadstatus) {
   case 0:
   case CURL_READFUNC_ABORT:
@@ -713,7 +722,8 @@
        * Cannot be processed as other kinds since read function requires
        * an additional parameter and is highly recursive.
        */
-      sz = mime_subparts_read(buffer, 1, bufsize, part->arg, hasread);
+      sz = mime_subparts_read(buffer, 1, bufsize, part->arg, hasread,
+                              call_depth);
       break;
     case MIMEKIND_FILE:
       if(part->fp && feof(part->fp))
@@ -752,13 +762,17 @@
 
 /* Read and encode part content. */
 static size_t read_encoded_part_content(curl_mimepart *part, char *buffer,
-                                        size_t bufsize, bool *hasread)
+                                        size_t bufsize, bool *hasread,
+                                        size_t call_depth)
 {
   struct mime_encoder_state *st = &part->encstate;
   size_t cursize = 0;
   size_t sz;
   bool ateof = FALSE;
 
+  if(++call_depth > MAX_MIME_LEVELS)
+    return READ_ERROR;
+
   for(;;) {
     if(st->bufbeg < st->bufend || ateof) {
       /* Encode buffered data. */
@@ -791,7 +805,7 @@
     if(st->bufend >= sizeof(st->buf))
       return cursize ? cursize : READ_ERROR;    /* Buffer full. */
     sz = read_part_content(part, st->buf + st->bufend,
-                           sizeof(st->buf) - st->bufend, hasread);
+                           sizeof(st->buf) - st->bufend, hasread, call_depth);
     switch(sz) {
     case 0:
       ateof = TRUE;
@@ -812,12 +826,15 @@
 
 /* Readback a mime part. */
 static size_t readback_part(curl_mimepart *part,
-                            char *buffer, size_t bufsize, bool *hasread)
+                            char *buffer, size_t bufsize, bool *hasread,
+                            size_t call_depth)
 {
   size_t cursize = 0;
 
-  /* Readback from part. */
+  if(++call_depth > MAX_MIME_LEVELS)
+    return READ_ERROR;
 
+  /* Readback from part. */
   while(bufsize) {
     size_t sz = 0;
     struct curl_slist *hdr = (struct curl_slist *)part->state.ptr;
@@ -860,9 +877,10 @@
       break;
     case MIMESTATE_CONTENT:
       if(part->encoder)
-        sz = read_encoded_part_content(part, buffer, bufsize, hasread);
+        sz = read_encoded_part_content(part, buffer, bufsize, hasread,
+                                       call_depth);
       else
-        sz = read_part_content(part, buffer, bufsize, hasread);
+        sz = read_part_content(part, buffer, bufsize, hasread, call_depth);
       switch(sz) {
       case 0:
         mimesetstate(&part->state, MIMESTATE_END, NULL);
@@ -896,12 +914,16 @@
 
 /* Readback from mime. Warning: not a read callback function. */
 static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems,
-                                 void *instream, bool *hasread)
+                                 void *instream, bool *hasread,
+                                 size_t call_depth)
 {
   curl_mime *mime = (curl_mime *)instream;
   size_t cursize = 0;
   (void)size;  /* Always 1 */
 
+  if(++call_depth > MAX_MIME_LEVELS)
+    return READ_ERROR;
+
   while(nitems) {
     size_t sz = 0;
     curl_mimepart *part = mime->state.ptr;
@@ -936,7 +958,7 @@
         mimesetstate(&mime->state, MIMESTATE_END, NULL);
         break;
       }
-      sz = readback_part(part, buffer, nitems, hasread);
+      sz = readback_part(part, buffer, nitems, hasread, call_depth);
       switch(sz) {
       case CURL_READFUNC_ABORT:
       case CURL_READFUNC_PAUSE:
@@ -1001,7 +1023,7 @@
 {
   curl_mime *mime = (curl_mime *)instream;
   curl_mimepart *part;
-  int result = CURL_SEEKFUNC_OK;
+  int rc = CURL_SEEKFUNC_OK;
 
   if(whence != SEEK_SET || offset)
     return CURL_SEEKFUNC_CANTSEEK;    /* Only support full rewind. */
@@ -1012,13 +1034,13 @@
   for(part = mime->firstpart; part; part = part->nextpart) {
     int res = mime_part_rewind(part);
     if(res != CURL_SEEKFUNC_OK)
-      result = res;
+      rc = res;
   }
 
-  if(result == CURL_SEEKFUNC_OK)
+  if(rc == CURL_SEEKFUNC_OK)
     mimesetstate(&mime->state, MIMESTATE_BEGIN, NULL);
 
-  return result;
+  return rc;
 }
 
 /* Release part content. */
@@ -1071,9 +1093,9 @@
     curl_slist_free_all(part->curlheaders);
     if(part->flags & MIME_USERHEADERS_OWNER)
       curl_slist_free_all(part->userheaders);
-    Curl_safefree(part->mimetype);
-    Curl_safefree(part->name);
-    Curl_safefree(part->filename);
+    curlx_safefree(part->mimetype);
+    curlx_safefree(part->name);
+    curlx_safefree(part->filename);
     Curl_mime_initpart(part);
   }
 }
@@ -1241,7 +1263,7 @@
   if(!part)
     return CURLE_BAD_FUNCTION_ARGUMENT;
 
-  Curl_safefree(part->name);
+  curlx_safefree(part->name);
 
   if(name) {
     part->name = curlx_strdup(name);
@@ -1258,7 +1280,7 @@
   if(!part)
     return CURLE_BAD_FUNCTION_ARGUMENT;
 
-  Curl_safefree(part->filename);
+  curlx_safefree(part->filename);
 
   if(filename) {
     part->filename = curlx_strdup(filename);
@@ -1350,7 +1372,7 @@
   if(!part)
     return CURLE_BAD_FUNCTION_ARGUMENT;
 
-  Curl_safefree(part->mimetype);
+  curlx_safefree(part->mimetype);
 
   if(mimetype) {
     part->mimetype = curlx_strdup(mimetype);
@@ -1505,7 +1527,7 @@
    * adding any data and this loops infinitely. */
   do {
     hasread = FALSE;
-    ret = readback_part(part, buffer, nitems, &hasread);
+    ret = readback_part(part, buffer, nitems, &hasread, 0);
     /*
      * If this is not possible to get some data without calling more than
      * one read callback (probably because a content encoder is not able to
@@ -1680,7 +1702,7 @@
   const char *boundary = NULL;
   char *customct;
   const char *cte = NULL;
-  CURLcode ret = CURLE_OK;
+  CURLcode result = CURLE_OK;
 
   /* Get rid of previously prepared headers. */
   curl_slist_free_all(part->curlheaders);
@@ -1742,35 +1764,35 @@
       if(part->name) {
         name = escape_string(data, part->name, strategy);
         if(!name)
-          ret = CURLE_OUT_OF_MEMORY;
+          result = CURLE_OUT_OF_MEMORY;
       }
-      if(!ret && part->filename) {
+      if(!result && part->filename) {
         filename = escape_string(data, part->filename, strategy);
         if(!filename)
-          ret = CURLE_OUT_OF_MEMORY;
+          result = CURLE_OUT_OF_MEMORY;
       }
-      if(!ret)
-        ret = Curl_mime_add_header(&part->curlheaders,
-                                   "Content-Disposition: %s%s%s%s%s%s%s",
-                                   disposition,
-                                   name ? "; name=\"" : "",
-                                   name ? name : "",
-                                   name ? "\"" : "",
-                                   filename ? "; filename=\"" : "",
-                                   filename ? filename : "",
-                                   filename ? "\"" : "");
-      Curl_safefree(name);
-      Curl_safefree(filename);
-      if(ret)
-        return ret;
+      if(!result)
+        result = Curl_mime_add_header(&part->curlheaders,
+                                      "Content-Disposition: %s%s%s%s%s%s%s",
+                                      disposition,
+                                      name ? "; name=\"" : "",
+                                      name ? name : "",
+                                      name ? "\"" : "",
+                                      filename ? "; filename=\"" : "",
+                                      filename ? filename : "",
+                                      filename ? "\"" : "");
+      curlx_safefree(name);
+      curlx_safefree(filename);
+      if(result)
+        return result;
     }
   }
 
   /* Issue Content-Type header. */
   if(contenttype) {
-    ret = add_content_type(&part->curlheaders, contenttype, boundary);
-    if(ret)
-      return ret;
+    result = add_content_type(&part->curlheaders, contenttype, boundary);
+    if(result)
+      return result;
   }
 
   /* Content-Transfer-Encoding header. */
@@ -1782,10 +1804,10 @@
             part->kind != MIMEKIND_MULTIPART)
       cte = "8bit";
     if(cte) {
-      ret = Curl_mime_add_header(&part->curlheaders,
-                                 "Content-Transfer-Encoding: %s", cte);
-      if(ret)
-        return ret;
+      result = Curl_mime_add_header(&part->curlheaders,
+                                    "Content-Transfer-Encoding: %s", cte);
+      if(result)
+        return result;
     }
   }
 
@@ -1802,13 +1824,13 @@
     if(content_type_match(contenttype, STRCONST("multipart/form-data")))
       disposition = "form-data";
     for(subpart = mime->firstpart; subpart; subpart = subpart->nextpart) {
-      ret = Curl_mime_prepare_headers(data, subpart, NULL,
-                                      disposition, strategy);
-      if(ret)
-        return ret;
+      result = Curl_mime_prepare_headers(data, subpart, NULL,
+                                         disposition, strategy);
+      if(result)
+        return result;
     }
   }
-  return ret;
+  return result;
 }
 
 /* Recursively reset paused status in the given part. */
@@ -1929,14 +1951,14 @@
   else
     nread = Curl_mime_read(buf, 1, blen, ctx->part);
 
-  CURL_TRC_READ(data, "cr_mime_read(len=%zu), mime_read() -> %zd",
+  CURL_TRC_READ(data, "cr_mime_read(len=%zu), mime_read() -> %zu",
                 blen, nread);
 
   switch(nread) {
   case 0:
     if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) {
       failf(data, "client mime read EOF fail, "
-            "only %"FMT_OFF_T"/%"FMT_OFF_T
+            "only %" FMT_OFF_T "/%" FMT_OFF_T
             " of needed bytes read", ctx->read_len, ctx->total_len);
       return CURLE_READ_ERROR;
     }
@@ -1991,7 +2013,7 @@
   }
 
   CURL_TRC_READ(data, "cr_mime_read(len=%zu, total=%" FMT_OFF_T
-                ", read=%"FMT_OFF_T") -> %d, %zu, %d",
+                ", read=%" FMT_OFF_T ") -> %d, %zu, %d",
                 blen, ctx->total_len, ctx->read_len, result, *pnread, *peos);
   return result;
 }
diff --git a/Utilities/cmcurl/lib/mprintf.c b/Utilities/cmcurl/lib/mprintf.c
index c6a4a49..6eaea66 100644
--- a/Utilities/cmcurl/lib/mprintf.c
+++ b/Utilities/cmcurl/lib/mprintf.c
@@ -39,11 +39,11 @@
 /* Upper-case digits. */
 const unsigned char Curl_udigits[] = "0123456789ABCDEF";
 
-#define OUTCHAR(x)                                       \
-  do {                                                   \
-    if(stream((unsigned char)(x), userp))                \
-      return TRUE;                                       \
-    (*donep)++;                                          \
+#define OUTCHAR(x)                        \
+  do {                                    \
+    if(stream((unsigned char)(x), userp)) \
+      return TRUE;                        \
+    (*donep)++;                           \
   } while(0)
 
 /* Data type to read from the arglist */
@@ -169,6 +169,261 @@
 #define PFMT_PRECARG    10 /* attempted to use same arg twice, for prec */
 #define PFMT_MANYSEGS   11 /* maxed out output segments */
 
+static int parse_flags(const char **fmtp, unsigned int *flagsp, int use_dollar,
+                       int *precp, int *widthp)
+{
+  const char *fmt = *fmtp;
+  bool loopit = TRUE;
+  unsigned int flags = 0;
+  int width = 0;
+  int precision = 0;
+
+  /* Handle the flags */
+  do {
+    switch(*fmt++) {
+    case ' ':
+      flags |= FLAGS_SPACE;
+      break;
+    case '+':
+      flags |= FLAGS_SHOWSIGN;
+      break;
+    case '-':
+      flags |= FLAGS_LEFT;
+      flags &= ~(unsigned int)FLAGS_PAD_NIL;
+      break;
+    case '#':
+      flags |= FLAGS_ALT;
+      break;
+    case '.':
+      if('*' == *fmt) {
+        /* The precision is picked from a specified parameter */
+        flags |= FLAGS_PRECPARAM;
+        fmt++;
+
+        if(use_dollar == DOLLAR_USE) {
+          precision = dollarstring(fmt, &fmt);
+          if(precision < 0)
+            /* illegal combo */
+            return PFMT_DOLLARPREC;
+        }
+        else
+          /* get it from the next argument */
+          precision = -1;
+      }
+      else {
+        bool is_neg;
+        curl_off_t num;
+        flags |= FLAGS_PREC;
+        is_neg = ('-' == *fmt);
+        if(is_neg)
+          fmt++;
+        if(curlx_str_number(&fmt, &num, INT_MAX))
+          return PFMT_PREC;
+        precision = (int)num;
+        if(is_neg)
+          precision = -precision;
+      }
+      if((flags & (FLAGS_PREC | FLAGS_PRECPARAM)) ==
+         (FLAGS_PREC | FLAGS_PRECPARAM))
+        /* it is not permitted to use both kinds of precision for the same
+           argument */
+        return PFMT_PRECMIX;
+      break;
+    case 'h':
+      flags |= FLAGS_SHORT;
+      break;
+#ifdef _WIN32
+    case 'I':
+      /* Non-ANSI integer extensions I32 I64 */
+      if((fmt[0] == '3') && (fmt[1] == '2')) {
+        flags |= FLAGS_LONG;
+        fmt += 2;
+      }
+      else if((fmt[0] == '6') && (fmt[1] == '4')) {
+        flags |= FLAGS_LONGLONG;
+        fmt += 2;
+      }
+      else {
+#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG)
+        flags |= FLAGS_LONGLONG;
+#else
+        flags |= FLAGS_LONG;
+#endif
+      }
+      break;
+#endif /* _WIN32 */
+    case 'l':
+      if(flags & FLAGS_LONG)
+        flags |= FLAGS_LONGLONG;
+      else
+        flags |= FLAGS_LONG;
+      break;
+    case 'L':
+      flags |= FLAGS_LONGDOUBLE;
+      break;
+    case 'q':
+      flags |= FLAGS_LONGLONG;
+      break;
+    case 'z':
+      /* the code below generates a warning if -Wunreachable-code is
+         used */
+#if (SIZEOF_SIZE_T > SIZEOF_LONG)
+      flags |= FLAGS_LONGLONG;
+#else
+      flags |= FLAGS_LONG;
+#endif
+      break;
+    case 'O':
+#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG)
+      flags |= FLAGS_LONGLONG;
+#else
+      flags |= FLAGS_LONG;
+#endif
+      break;
+    case '0':
+      if(!(flags & FLAGS_LEFT))
+        flags |= FLAGS_PAD_NIL;
+      FALLTHROUGH();
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9': {
+      curl_off_t num;
+      flags |= FLAGS_WIDTH;
+      fmt--;
+      if(curlx_str_number(&fmt, &num, INT_MAX))
+        return PFMT_WIDTH;
+      width = (int)num;
+      break;
+    }
+    case '*':  /* read width from argument list */
+      flags |= FLAGS_WIDTHPARAM;
+      if(use_dollar == DOLLAR_USE) {
+        width = dollarstring(fmt, &fmt);
+        if(width < 0)
+          /* illegal combo */
+          return PFMT_DOLLARWIDTH;
+      }
+      else
+        /* pick from the next argument */
+        width = -1;
+      break;
+    default:
+      loopit = FALSE;
+      fmt--;
+      break;
+    } /* switch */
+  } while(loopit); /* do */
+  *flagsp = flags;
+  *precp = precision;
+  *widthp = width;
+  *fmtp = fmt;
+  return PFMT_OK;
+}
+
+static bool parse_conversion(const char f, unsigned int *flagp,
+                             FormatType *typep)
+{
+  unsigned int flags = *flagp;
+  FormatType type;
+  switch(f) {
+  case 'S':
+    flags |= FLAGS_ALT;
+    type = MTYPE_STRING;
+    break;
+  case 's':
+    type = MTYPE_STRING;
+    break;
+  case 'n':
+    type = MTYPE_INTPTR;
+    break;
+  case 'p':
+    type = MTYPE_PTR;
+    break;
+  case 'd':
+  case 'i':
+    if(flags & FLAGS_LONGLONG)
+      type = MTYPE_LONGLONG;
+    else if(flags & FLAGS_LONG)
+      type = MTYPE_LONG;
+    else
+      type = MTYPE_INT;
+    break;
+  case 'u':
+    if(flags & FLAGS_LONGLONG)
+      type = MTYPE_LONGLONGU;
+    else if(flags & FLAGS_LONG)
+      type = MTYPE_LONGU;
+    else
+      type = MTYPE_INTU;
+    flags |= FLAGS_UNSIGNED;
+    break;
+  case 'o':
+    if(flags & FLAGS_LONGLONG)
+      type = MTYPE_LONGLONGU;
+    else if(flags & FLAGS_LONG)
+      type = MTYPE_LONGU;
+    else
+      type = MTYPE_INTU;
+    flags |= FLAGS_OCTAL | FLAGS_UNSIGNED;
+    break;
+  case 'x':
+    if(flags & FLAGS_LONGLONG)
+      type = MTYPE_LONGLONGU;
+    else if(flags & FLAGS_LONG)
+      type = MTYPE_LONGU;
+    else
+      type = MTYPE_INTU;
+    flags |= FLAGS_HEX | FLAGS_UNSIGNED;
+    break;
+  case 'X':
+    if(flags & FLAGS_LONGLONG)
+      type = MTYPE_LONGLONGU;
+    else if(flags & FLAGS_LONG)
+      type = MTYPE_LONGU;
+    else
+      type = MTYPE_INTU;
+    flags |= FLAGS_HEX | FLAGS_UPPER | FLAGS_UNSIGNED;
+    break;
+  case 'c':
+    type = MTYPE_INT;
+    flags |= FLAGS_CHAR;
+    break;
+  case 'f':
+    type = MTYPE_DOUBLE;
+    break;
+  case 'e':
+    type = MTYPE_DOUBLE;
+    flags |= FLAGS_FLOATE;
+    break;
+  case 'E':
+    type = MTYPE_DOUBLE;
+    flags |= FLAGS_FLOATE | FLAGS_UPPER;
+    break;
+  case 'g':
+    type = MTYPE_DOUBLE;
+    flags |= FLAGS_FLOATG;
+    break;
+  case 'G':
+    type = MTYPE_DOUBLE;
+    flags |= FLAGS_FLOATG | FLAGS_UPPER;
+    break;
+  default:
+    /* invalid instruction, disregard and continue */
+    return TRUE;
+  } /* switch */
+
+  *flagp |= flags;
+  *typep = type;
+  return FALSE;
+}
+
+
 static int parsefmt(const char *format,
                     struct outsegment *out,
                     struct va_input *in,
@@ -192,12 +447,12 @@
   while(*fmt) {
     if(*fmt == '%') {
       struct va_input *iptr;
-      bool loopit = TRUE;
       FormatType type;
       unsigned int flags = 0;
       int width = 0;
       int precision = 0;
       int param = -1;
+      int rc;
       fmt++;
       outlen = (size_t)(fmt - start - 1);
       if(*fmt == '%') {
@@ -232,233 +487,12 @@
           use_dollar = DOLLAR_USE;
       }
 
-      /* Handle the flags */
-      while(loopit) {
-        switch(*fmt++) {
-        case ' ':
-          flags |= FLAGS_SPACE;
-          break;
-        case '+':
-          flags |= FLAGS_SHOWSIGN;
-          break;
-        case '-':
-          flags |= FLAGS_LEFT;
-          flags &= ~(unsigned int)FLAGS_PAD_NIL;
-          break;
-        case '#':
-          flags |= FLAGS_ALT;
-          break;
-        case '.':
-          if('*' == *fmt) {
-            /* The precision is picked from a specified parameter */
-            flags |= FLAGS_PRECPARAM;
-            fmt++;
+      rc = parse_flags(&fmt, &flags, use_dollar, &precision, &width);
+      if(rc)
+        return rc;
 
-            if(use_dollar == DOLLAR_USE) {
-              precision = dollarstring(fmt, &fmt);
-              if(precision < 0)
-                /* illegal combo */
-                return PFMT_DOLLARPREC;
-            }
-            else
-              /* get it from the next argument */
-              precision = -1;
-          }
-          else {
-            bool is_neg;
-            curl_off_t num;
-            flags |= FLAGS_PREC;
-            is_neg = ('-' == *fmt);
-            if(is_neg)
-              fmt++;
-            if(curlx_str_number(&fmt, &num, INT_MAX))
-              return PFMT_PREC;
-            precision = (int)num;
-            if(is_neg)
-              precision = -precision;
-          }
-          if((flags & (FLAGS_PREC | FLAGS_PRECPARAM)) ==
-             (FLAGS_PREC | FLAGS_PRECPARAM))
-            /* it is not permitted to use both kinds of precision for the same
-               argument */
-            return PFMT_PRECMIX;
-          break;
-        case 'h':
-          flags |= FLAGS_SHORT;
-          break;
-#ifdef _WIN32
-        case 'I':
-          /* Non-ANSI integer extensions I32 I64 */
-          if((fmt[0] == '3') && (fmt[1] == '2')) {
-            flags |= FLAGS_LONG;
-            fmt += 2;
-          }
-          else if((fmt[0] == '6') && (fmt[1] == '4')) {
-            flags |= FLAGS_LONGLONG;
-            fmt += 2;
-          }
-          else {
-#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG)
-            flags |= FLAGS_LONGLONG;
-#else
-            flags |= FLAGS_LONG;
-#endif
-          }
-          break;
-#endif /* _WIN32 */
-        case 'l':
-          if(flags & FLAGS_LONG)
-            flags |= FLAGS_LONGLONG;
-          else
-            flags |= FLAGS_LONG;
-          break;
-        case 'L':
-          flags |= FLAGS_LONGDOUBLE;
-          break;
-        case 'q':
-          flags |= FLAGS_LONGLONG;
-          break;
-        case 'z':
-          /* the code below generates a warning if -Wunreachable-code is
-             used */
-#if (SIZEOF_SIZE_T > SIZEOF_LONG)
-          flags |= FLAGS_LONGLONG;
-#else
-          flags |= FLAGS_LONG;
-#endif
-          break;
-        case 'O':
-#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG)
-          flags |= FLAGS_LONGLONG;
-#else
-          flags |= FLAGS_LONG;
-#endif
-          break;
-        case '0':
-          if(!(flags & FLAGS_LEFT))
-            flags |= FLAGS_PAD_NIL;
-          FALLTHROUGH();
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9': {
-          curl_off_t num;
-          flags |= FLAGS_WIDTH;
-          fmt--;
-          if(curlx_str_number(&fmt, &num, INT_MAX))
-            return PFMT_WIDTH;
-          width = (int)num;
-          break;
-        }
-        case '*':  /* read width from argument list */
-          flags |= FLAGS_WIDTHPARAM;
-          if(use_dollar == DOLLAR_USE) {
-            width = dollarstring(fmt, &fmt);
-            if(width < 0)
-              /* illegal combo */
-              return PFMT_DOLLARWIDTH;
-          }
-          else
-            /* pick from the next argument */
-            width = -1;
-          break;
-        default:
-          loopit = FALSE;
-          fmt--;
-          break;
-        } /* switch */
-      } /* while */
-
-      switch(*fmt) {
-      case 'S':
-        flags |= FLAGS_ALT;
-        FALLTHROUGH();
-      case 's':
-        type = MTYPE_STRING;
-        break;
-      case 'n':
-        type = MTYPE_INTPTR;
-        break;
-      case 'p':
-        type = MTYPE_PTR;
-        break;
-      case 'd':
-      case 'i':
-        if(flags & FLAGS_LONGLONG)
-          type = MTYPE_LONGLONG;
-        else if(flags & FLAGS_LONG)
-          type = MTYPE_LONG;
-        else
-          type = MTYPE_INT;
-        break;
-      case 'u':
-        if(flags & FLAGS_LONGLONG)
-          type = MTYPE_LONGLONGU;
-        else if(flags & FLAGS_LONG)
-          type = MTYPE_LONGU;
-        else
-          type = MTYPE_INTU;
-        flags |= FLAGS_UNSIGNED;
-        break;
-      case 'o':
-        if(flags & FLAGS_LONGLONG)
-          type = MTYPE_LONGLONGU;
-        else if(flags & FLAGS_LONG)
-          type = MTYPE_LONGU;
-        else
-          type = MTYPE_INTU;
-        flags |= FLAGS_OCTAL | FLAGS_UNSIGNED;
-        break;
-      case 'x':
-        if(flags & FLAGS_LONGLONG)
-          type = MTYPE_LONGLONGU;
-        else if(flags & FLAGS_LONG)
-          type = MTYPE_LONGU;
-        else
-          type = MTYPE_INTU;
-        flags |= FLAGS_HEX | FLAGS_UNSIGNED;
-        break;
-      case 'X':
-        if(flags & FLAGS_LONGLONG)
-          type = MTYPE_LONGLONGU;
-        else if(flags & FLAGS_LONG)
-          type = MTYPE_LONGU;
-        else
-          type = MTYPE_INTU;
-        flags |= FLAGS_HEX | FLAGS_UPPER | FLAGS_UNSIGNED;
-        break;
-      case 'c':
-        type = MTYPE_INT;
-        flags |= FLAGS_CHAR;
-        break;
-      case 'f':
-        type = MTYPE_DOUBLE;
-        break;
-      case 'e':
-        type = MTYPE_DOUBLE;
-        flags |= FLAGS_FLOATE;
-        break;
-      case 'E':
-        type = MTYPE_DOUBLE;
-        flags |= FLAGS_FLOATE | FLAGS_UPPER;
-        break;
-      case 'g':
-        type = MTYPE_DOUBLE;
-        flags |= FLAGS_FLOATG;
-        break;
-      case 'G':
-        type = MTYPE_DOUBLE;
-        flags |= FLAGS_FLOATG | FLAGS_UPPER;
-        break;
-      default:
-        /* invalid instruction, disregard and continue */
+      if(parse_conversion(*fmt, &flags, &type))
         continue;
-      } /* switch */
 
       if(flags & FLAGS_WIDTHPARAM) {
         if(width < 0)
@@ -672,22 +706,19 @@
 
   /* NOTE NOTE NOTE!! Not all sprintf implementations return number of
      output characters */
-#if defined(__GNUC__) || defined(__clang__)
+#ifdef CURL_HAVE_DIAG
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
 #endif
 #ifdef _WIN32
   curlx_win32_snprintf(work, BUFFSIZE, fmt, dnum);
-#elif defined(HAVE_SNPRINTF)
+#else
   /* !checksrc! disable BANNEDFUNC 1 */
   /* !checksrc! disable LONGLINE */
   /* NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) */
   snprintf(work, BUFFSIZE, fmt, dnum);
-#else
-  /* float and double outputs do not work without snprintf support */
-  work[0] = 0;
 #endif
-#if defined(__GNUC__) || defined(__clang__)
+#ifdef CURL_HAVE_DIAG
 #pragma GCC diagnostic pop
 #endif
   DEBUGASSERT(strlen(work) < BUFFSIZE);
@@ -1074,7 +1105,7 @@
 }
 
 int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format,
-                    va_list ap_save)
+                    va_list args)
 {
   int retcode;
   struct nsprintf info;
@@ -1083,7 +1114,7 @@
   info.length = 0;
   info.max = maxlength;
 
-  retcode = formatf(&info, addbyter, format, ap_save);
+  retcode = formatf(&info, addbyter, format, args);
   if(info.max) {
     /* we terminate this with a zero byte */
     if(info.max == info.length) {
@@ -1101,10 +1132,10 @@
 int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...)
 {
   int retcode;
-  va_list ap_save; /* argument pointer */
-  va_start(ap_save, format);
-  retcode = curl_mvsnprintf(buffer, maxlength, format, ap_save);
-  va_end(ap_save);
+  va_list args; /* argument pointer */
+  va_start(args, format);
+  retcode = curl_mvsnprintf(buffer, maxlength, format, args);
+  va_end(args);
   return retcode;
 }
 
@@ -1121,13 +1152,13 @@
 }
 
 /* appends the formatted string, returns MERR error code */
-int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save)
+int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list args)
 {
   struct asprintf info;
   info.b = dyn;
   info.merr = MERR_OK;
 
-  (void)formatf(&info, alloc_addbyter, format, ap_save);
+  (void)formatf(&info, alloc_addbyter, format, args);
   if(info.merr) {
     curlx_dyn_free(info.b);
     return info.merr;
@@ -1135,7 +1166,7 @@
   return 0;
 }
 
-char *curl_mvaprintf(const char *format, va_list ap_save)
+char *curl_mvaprintf(const char *format, va_list args)
 {
   struct asprintf info;
   struct dynbuf dyn;
@@ -1143,7 +1174,7 @@
   curlx_dyn_init(info.b, DYN_APRINTF);
   info.merr = MERR_OK;
 
-  (void)formatf(&info, alloc_addbyter, format, ap_save);
+  (void)formatf(&info, alloc_addbyter, format, args);
   if(info.merr) {
     curlx_dyn_free(info.b);
     return NULL;
@@ -1155,11 +1186,11 @@
 
 char *curl_maprintf(const char *format, ...)
 {
-  va_list ap_save;
+  va_list args;
   char *s;
-  va_start(ap_save, format);
-  s = curl_mvaprintf(format, ap_save);
-  va_end(ap_save);
+  va_start(args, format);
+  s = curl_mvaprintf(format, args);
+  va_end(args);
   return s;
 }
 
@@ -1173,11 +1204,11 @@
 
 int curl_msprintf(char *buffer, const char *format, ...)
 {
-  va_list ap_save; /* argument pointer */
+  va_list args; /* argument pointer */
   int retcode;
-  va_start(ap_save, format);
-  retcode = formatf(&buffer, storebuffer, format, ap_save);
-  va_end(ap_save);
+  va_start(args, format);
+  retcode = formatf(&buffer, storebuffer, format, args);
+  va_end(args);
   *buffer = 0; /* we terminate this with a zero byte */
   return retcode;
 }
@@ -1193,36 +1224,36 @@
 int curl_mprintf(const char *format, ...)
 {
   int retcode;
-  va_list ap_save; /* argument pointer */
-  va_start(ap_save, format);
-  retcode = formatf(stdout, fputc_wrapper, format, ap_save);
-  va_end(ap_save);
+  va_list args; /* argument pointer */
+  va_start(args, format);
+  retcode = formatf(stdout, fputc_wrapper, format, args);
+  va_end(args);
   return retcode;
 }
 
-int curl_mfprintf(FILE *whereto, const char *format, ...)
+int curl_mfprintf(FILE *fd, const char *format, ...)
 {
   int retcode;
-  va_list ap_save; /* argument pointer */
-  va_start(ap_save, format);
-  retcode = formatf(whereto, fputc_wrapper, format, ap_save);
-  va_end(ap_save);
+  va_list args; /* argument pointer */
+  va_start(args, format);
+  retcode = formatf(fd, fputc_wrapper, format, args);
+  va_end(args);
   return retcode;
 }
 
-int curl_mvsprintf(char *buffer, const char *format, va_list ap_save)
+int curl_mvsprintf(char *buffer, const char *format, va_list args)
 {
-  int retcode = formatf(&buffer, storebuffer, format, ap_save);
+  int retcode = formatf(&buffer, storebuffer, format, args);
   *buffer = 0; /* we terminate this with a zero byte */
   return retcode;
 }
 
-int curl_mvprintf(const char *format, va_list ap_save)
+int curl_mvprintf(const char *format, va_list args)
 {
-  return formatf(stdout, fputc_wrapper, format, ap_save);
+  return formatf(stdout, fputc_wrapper, format, args);
 }
 
-int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save)
+int curl_mvfprintf(FILE *fd, const char *format, va_list args)
 {
-  return formatf(whereto, fputc_wrapper, format, ap_save);
+  return formatf(fd, fputc_wrapper, format, args);
 }
diff --git a/Utilities/cmcurl/lib/mqtt.c b/Utilities/cmcurl/lib/mqtt.c
index e591cfc..84fd272 100644
--- a/Utilities/cmcurl/lib/mqtt.c
+++ b/Utilities/cmcurl/lib/mqtt.c
@@ -351,8 +351,8 @@
 end:
   if(packet)
     curlx_free(packet);
-  Curl_safefree(data->state.aptr.user);
-  Curl_safefree(data->state.aptr.passwd);
+  curlx_safefree(data->state.aptr.user);
+  curlx_safefree(data->state.aptr.passwd);
   return result;
 }
 
@@ -964,7 +964,7 @@
 /*
  * MQTTS protocol.
  */
-static const struct Curl_protocol Curl_protocol_mqtts = {
+const struct Curl_protocol Curl_protocol_mqtts = {
   mqtt_setup_conn,                    /* setup_connection */
   mqtt_do,                            /* do_it */
   mqtt_done,                          /* done */
@@ -979,7 +979,7 @@
   ZERO_NULL,                          /* disconnect */
   ZERO_NULL,                          /* write_resp */
   ZERO_NULL,                          /* write_resp_hd */
-  ZERO_NULL,                          /* connection_check */
+  ZERO_NULL,                          /* connection_is_dead */
   ZERO_NULL,                          /* attach connection */
   ZERO_NULL,                          /* follow */
 };
@@ -989,7 +989,7 @@
 /*
  * MQTT protocol.
  */
-static const struct Curl_protocol Curl_protocol_mqtt = {
+const struct Curl_protocol Curl_protocol_mqtt = {
   mqtt_setup_conn,                    /* setup_connection */
   mqtt_do,                            /* do_it */
   mqtt_done,                          /* done */
@@ -1004,38 +1004,9 @@
   ZERO_NULL,                          /* disconnect */
   ZERO_NULL,                          /* write_resp */
   ZERO_NULL,                          /* write_resp_hd */
-  ZERO_NULL,                          /* connection_check */
+  ZERO_NULL,                          /* connection_is_dead */
   ZERO_NULL,                          /* attach connection */
   ZERO_NULL,                          /* follow */
 };
 
 #endif /* CURL_DISABLE_MQTT */
-
-const struct Curl_scheme Curl_scheme_mqtts = {
-  "mqtts",                            /* scheme */
-#if defined(CURL_DISABLE_MQTT) || !defined(USE_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_mqtts,
-#endif
-  CURLPROTO_MQTTS,                    /* protocol */
-  CURLPROTO_MQTT,                     /* family */
-  PROTOPT_SSL,                        /* flags */
-  PORT_MQTTS,                         /* defport */
-};
-
-/*
- * MQTT protocol.
- */
-const struct Curl_scheme Curl_scheme_mqtt = {
-  "mqtt",                             /* scheme */
-#ifdef CURL_DISABLE_MQTT
-  ZERO_NULL,
-#else
-  &Curl_protocol_mqtt,
-#endif
-  CURLPROTO_MQTT,                     /* protocol */
-  CURLPROTO_MQTT,                     /* family */
-  PROTOPT_NONE,                       /* flags */
-  PORT_MQTT,                          /* defport */
-};
diff --git a/Utilities/cmcurl/lib/mqtt.h b/Utilities/cmcurl/lib/mqtt.h
index 67d1df4..3d95293 100644
--- a/Utilities/cmcurl/lib/mqtt.h
+++ b/Utilities/cmcurl/lib/mqtt.h
@@ -23,7 +23,11 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_mqtt;
-extern const struct Curl_scheme Curl_scheme_mqtts;
+#ifndef CURL_DISABLE_MQTT
+extern const struct Curl_protocol Curl_protocol_mqtt;
+#ifdef USE_SSL
+extern const struct Curl_protocol Curl_protocol_mqtts;
+#endif
+#endif
 
 #endif /* HEADER_CURL_MQTT_H */
diff --git a/Utilities/cmcurl/lib/multi.c b/Utilities/cmcurl/lib/multi.c
index 6133736..7520253 100644
--- a/Utilities/cmcurl/lib/multi.c
+++ b/Utilities/cmcurl/lib/multi.c
@@ -74,7 +74,6 @@
 
 #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
@@ -140,7 +139,6 @@
     NULL,              /* PENDING */
     NULL,              /* SETUP */
     Curl_init_CONNECT, /* CONNECT */
-    NULL,              /* RESOLVING */
     NULL,              /* CONNECTING */
     NULL,              /* PROTOCONNECT */
     NULL,              /* PROTOCONNECTING */
@@ -207,7 +205,7 @@
 static void ph_freeentry(void *p)
 {
   (void)p;
-  /* Will always be FALSE. Cannot use a 0 assert here since compilers
+  /* Always FALSE. Cannot use a 0 assert here since compilers
    * are not in agreement if they then want a NORETURN attribute or
    * not. *sigh* */
   DEBUGASSERT(p == NULL);
@@ -254,6 +252,10 @@
   multi->multiplexing = TRUE;
   multi->max_concurrent_streams = 100;
   multi->last_timeout_ms = -1;
+#ifdef ENABLE_WAKEUP
+  multi->wakeup_pair[0] = CURL_SOCKET_BAD;
+  multi->wakeup_pair[1] = CURL_SOCKET_BAD;
+#endif
 
   if(Curl_mntfy_resize(multi) ||
      Curl_uint32_bset_resize(&multi->process, xfer_table_size) ||
@@ -294,22 +296,35 @@
   multi->wsa_event = WSACreateEvent();
   if(multi->wsa_event == WSA_INVALID_EVENT)
     goto error;
-#elif defined(ENABLE_WAKEUP)
-  if(Curl_wakeup_init(multi->wakeup_pair, TRUE) < 0) {
-    multi->wakeup_pair[0] = CURL_SOCKET_BAD;
-    multi->wakeup_pair[1] = CURL_SOCKET_BAD;
-  }
+#endif
+#ifdef ENABLE_WAKEUP
+  /* When enabled, rely on this to work. We ignore this in previous
+   * versions, but that seems an unnecessary complication. */
+  if(Curl_wakeup_init(multi->wakeup_pair, TRUE) < 0)
+    goto error;
 #endif
 
-#ifdef USE_IPV6
   if(Curl_probeipv6(multi))
     goto error;
+
+#ifdef USE_RESOLV_THREADED
+  if(xfer_table_size < CURL_XFER_TABLE_SIZE) { /* easy multi */
+    if(Curl_async_thrdd_multi_init(multi, 0, 2, 10))
+      goto error;
+  }
+  else { /* real multi handle */
+    if(Curl_async_thrdd_multi_init(multi, 0, 20, 2000))
+      goto error;
+  }
 #endif
 
   return multi;
 
 error:
 
+#ifdef USE_RESOLV_THREADED
+  Curl_async_thrdd_multi_destroy(multi, TRUE);
+#endif
   Curl_multi_ev_cleanup(multi);
   Curl_hash_destroy(&multi->proto_hash);
   Curl_dnscache_destroy(&multi->dnscache);
@@ -319,6 +334,7 @@
   Curl_ssl_scache_destroy(multi->ssl_scache);
 #endif
   if(multi->admin) {
+    Curl_multi_ev_xfer_done(multi, multi->admin);
     multi->admin->multi = NULL;
     Curl_close(&multi->admin);
   }
@@ -329,6 +345,9 @@
   Curl_uint32_bset_destroy(&multi->pending);
   Curl_uint32_bset_destroy(&multi->msgsent);
   Curl_uint32_tbl_destroy(&multi->xfers);
+#ifdef ENABLE_WAKEUP
+  Curl_wakeup_destroy(multi->wakeup_pair);
+#endif
 
   curlx_free(multi);
   return NULL;
@@ -362,6 +381,17 @@
   return data->mstate < MSTATE_DO;
 }
 
+static CURLMcode multi_assess_wakeup(struct Curl_multi *multi)
+{
+#ifdef ENABLE_WAKEUP
+  if(multi->socket_cb)
+    return Curl_multi_ev_assess_xfer(multi, multi->admin);
+#else
+  (void)multi;
+#endif
+  return CURLM_OK;
+}
+
 static CURLMcode multi_xfers_add(struct Curl_multi *multi,
                                  struct Curl_easy *data)
 {
@@ -421,11 +451,11 @@
   return CURLM_OK;
 }
 
-CURLMcode curl_multi_add_handle(CURLM *m, CURL *d)
+CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl)
 {
   CURLMcode mresult;
   struct Curl_multi *multi = m;
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
 
   /* First, make some basic checks that the CURLM handle is a good handle */
   if(!GOOD_MULTI_HANDLE(multi))
@@ -528,6 +558,12 @@
     data->set.server_response_timeout;
   multi->admin->set.no_signal = data->set.no_signal;
 
+  mresult = multi_assess_wakeup(multi);
+  if(mresult) {
+    failf(data, "error enabling wakeup listening: %d", mresult);
+    return mresult;
+  }
+
   CURL_TRC_M(data, "added to multi, mid=%u, running=%u, total=%u",
              data->mid, Curl_multi_xfers_running(multi),
              Curl_uint32_tbl_count(&multi->xfers));
@@ -600,22 +636,6 @@
                               void *userdata)
 {
   struct multi_done_ctx *mdctx = userdata;
-#ifdef CURLVERBOSE
-  const char *host =
-#ifndef CURL_DISABLE_PROXY
-        conn->bits.socksproxy ?
-        conn->socks_proxy.host.dispname :
-        conn->bits.httpproxy ? conn->http_proxy.host.dispname :
-#endif
-        conn->bits.conn_to_host ? conn->conn_to_host.dispname :
-        conn->host.dispname;
-  int port =
-#ifndef CURL_DISABLE_PROXY
-        conn->bits.httpproxy ? conn->http_proxy.port :
-#endif
-        conn->bits.conn_to_port ? conn->conn_to_port :
-        conn->remote_port;
-#endif /* CURLVERBOSE */
 
   Curl_detach_connection(data);
 
@@ -630,22 +650,21 @@
   data->state.done = TRUE; /* called now! */
   data->state.recent_conn_id = conn->connection_id;
 
-  Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */
-  Curl_resolv_unlink(data, &data->state.dns[1]);
   Curl_dnscache_prune(data);
 
   if(multi_conn_should_close(conn, data, (bool)mdctx->premature)) {
-    CURL_TRC_M(data, "multi_done, terminating conn #%" FMT_OFF_T " to %s:%d, "
+    CURL_TRC_M(data, "multi_done, terminating conn #%" FMT_OFF_T " to %s, "
                "forbid=%d, close=%d, premature=%d, conn_multiplex=%d",
-               conn->connection_id, host, port, data->set.reuse_forbid,
-               conn->bits.close, mdctx->premature,
+               conn->connection_id, conn->destination,
+               data->set.reuse_forbid, conn->bits.close, mdctx->premature,
                Curl_conn_is_multiplex(conn, FIRSTSOCKET));
     connclose(conn, "disconnecting");
     Curl_conn_terminate(data, conn, (bool)mdctx->premature);
   }
   else if(!Curl_conn_get_max_concurrent(data, conn, FIRSTSOCKET)) {
-    CURL_TRC_M(data, "multi_done, conn #%" FMT_OFF_T " to %s:%d was shutdown"
-               " by server, not reusing", conn->connection_id, host, port);
+    CURL_TRC_M(data, "multi_done, conn #%" FMT_OFF_T " to %s was shutdown"
+               " by server, not reusing", conn->connection_id,
+               conn->destination);
     connclose(conn, "server shutdown");
     Curl_conn_terminate(data, conn, (bool)mdctx->premature);
   }
@@ -654,8 +673,8 @@
     if(Curl_cpool_conn_now_idle(data, conn)) {
       /* connection kept in the cpool */
       data->state.lastconnect_id = conn->connection_id;
-      infof(data, "Connection #%" FMT_OFF_T " to host %s:%d left intact",
-            conn->connection_id, host, port);
+      infof(data, "Connection #%" FMT_OFF_T " to host %s left intact",
+            conn->connection_id, conn->destination);
     }
     else {
       /* connection was removed from the cpool and destroyed. */
@@ -671,9 +690,6 @@
 {
   CURLcode result;
   struct connectdata *conn = data->conn;
-  struct multi_done_ctx mdctx;
-
-  memset(&mdctx, 0, sizeof(mdctx));
 
   CURL_TRC_M(data, "multi_done: status: %d prem: %d done: %d",
              (int)status, (int)premature, data->state.done);
@@ -683,11 +699,11 @@
     return CURLE_OK;
 
   /* Shut down any ongoing async resolver operation. */
-  Curl_async_shutdown(data);
+  Curl_resolv_shutdown_all(data);
 
   /* Cleanup possible redirect junk */
-  Curl_safefree(data->req.newurl);
-  Curl_safefree(data->req.location);
+  curlx_safefree(data->req.newurl);
+  curlx_safefree(data->req.location);
 
   switch(status) {
   case CURLE_ABORTED_BY_CALLBACK:
@@ -704,34 +720,43 @@
   }
 
   /* this calls the protocol-specific function pointer previously set */
-  if(conn->scheme->run->done && (data->mstate >= MSTATE_PROTOCONNECT))
+  if(conn && conn->scheme->run->done && (data->mstate >= MSTATE_PROTOCONNECT))
     result = conn->scheme->run->done(data, status, premature);
   else
     result = status;
 
-  if(result != CURLE_ABORTED_BY_CALLBACK) {
-    /* avoid this if we already aborted by callback to avoid this calling
-       another callback */
+  if(data->mstate > MSTATE_CONNECTING &&
+     (result != CURLE_ABORTED_BY_CALLBACK)) {
+    /* avoid this if
+     * - the transfer has not connected
+     * - we already aborted by callback to avoid this calling another callback
+     */
     int rc = Curl_pgrsDone(data);
     if(!result && rc)
       result = CURLE_ABORTED_BY_CALLBACK;
   }
 
   /* Make sure that transfer client writes are really done now. */
-  result = Curl_1st_err(result, Curl_xfer_write_done(data, premature));
+  result = Curl_1st_fatal(result, Curl_xfer_write_done(data, premature));
 
   /* Inform connection filters that this transfer is done */
-  Curl_conn_ev_data_done(data, premature);
+  if(conn)
+    Curl_conn_ev_data_done(data, premature);
 
   process_pending_handles(data->multi); /* connection / multiplex */
 
   if(!result)
     result = Curl_req_done(&data->req, data, premature);
 
-  /* Under the potential connection pool's share lock, decide what to
-   * do with the transfer's connection. */
-  mdctx.premature = premature;
-  Curl_cpool_do_locked(data, data->conn, multi_done_locked, &mdctx);
+  if(conn) {
+    /* Under the potential connection pool's share lock, decide what to
+     * do with the transfer's connection. */
+    struct multi_done_ctx mdctx;
+
+    memset(&mdctx, 0, sizeof(mdctx));
+    mdctx.premature = premature;
+    Curl_cpool_do_locked(data, data->conn, multi_done_locked, &mdctx);
+  }
 
   /* flush the netrc cache */
   Curl_netrc_cleanup(&data->state.netrc);
@@ -744,14 +769,14 @@
 {
   (void)userdata;
   (void)data;
-  if(conn->connect_only)
+  if(conn->bits.connect_only)
     connclose(conn, "Removing connect-only easy handle");
 }
 
-CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d)
+CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl)
 {
   struct Curl_multi *multi = m;
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   bool premature;
   struct Curl_llist_node *e;
   CURLMcode mresult;
@@ -886,6 +911,12 @@
   if(mresult)
     return mresult;
 
+  mresult = multi_assess_wakeup(multi);
+  if(mresult) {
+    failf(data, "error enabling wakeup listening: %d", mresult);
+    return mresult;
+  }
+
   CURL_TRC_M(data, "removed from multi, mid=%u, running=%u, total=%u",
              mid, Curl_multi_xfers_running(multi),
              Curl_uint32_tbl_count(&multi->xfers));
@@ -1080,7 +1111,7 @@
   if(conn->scheme->run->perform_pollset)
     result = conn->scheme->run->perform_pollset(data, ps);
   else {
-    /* Default is to obey the data->req.keepon flags for send/recv */
+    /* Default is to obey the request flags for send/recv */
     if(Curl_req_want_recv(data) && CONN_SOCK_IDX_VALID(conn->recv_idx)) {
       result = Curl_pollset_add_in(data, ps, conn->sock[conn->recv_idx]);
     }
@@ -1101,75 +1132,77 @@
 CURLMcode Curl_multi_pollset(struct Curl_easy *data,
                              struct easy_pollset *ps)
 {
-  CURLMcode mresult = CURLM_OK;
   CURLcode result = CURLE_OK;
 
+  Curl_pollset_reset(ps);
+#ifdef ENABLE_WAKEUP
+  /* The admin handle always listens on the wakeup socket when there
+   * are transfers alive. */
+  if(data->multi && (data == data->multi->admin) &&
+     data->multi->xfers_alive) {
+    result = Curl_pollset_add_in(data, ps, data->multi->wakeup_pair[0]);
+  }
+#endif
   /* If the transfer has no connection, this is fine. Happens when
      called via curl_multi_remove_handle() => Curl_multi_ev_assess() =>
      Curl_multi_pollset(). */
-  Curl_pollset_reset(ps);
-  if(!data->conn)
-    return CURLM_OK;
+  if(!result && data->conn) {
+    switch(data->mstate) {
+    case MSTATE_INIT:
+    case MSTATE_PENDING:
+    case MSTATE_SETUP:
+    case MSTATE_CONNECT:
+      /* nothing to poll for yet */
+      break;
 
-  switch(data->mstate) {
-  case MSTATE_INIT:
-  case MSTATE_PENDING:
-  case MSTATE_SETUP:
-  case MSTATE_CONNECT:
-    /* nothing to poll for yet */
-    break;
+    case MSTATE_CONNECTING:
+      if(data->conn && !data->conn->bits.dns_resolved)
+        result = Curl_resolv_pollset(data, ps);
+      if(!result)
+        result = mstate_connecting_pollset(data, ps);
+      break;
 
-  case MSTATE_RESOLVING:
-    result = Curl_resolv_pollset(data, ps);
-    break;
+    case MSTATE_PROTOCONNECT:
+    case MSTATE_PROTOCONNECTING:
+      result = mstate_protocol_pollset(data, ps);
+      break;
 
-  case MSTATE_CONNECTING:
-    result = mstate_connecting_pollset(data, ps);
-    break;
+    case MSTATE_DO:
+    case MSTATE_DOING:
+      result = mstate_do_pollset(data, ps);
+      break;
 
-  case MSTATE_PROTOCONNECT:
-  case MSTATE_PROTOCONNECTING:
-    result = mstate_protocol_pollset(data, ps);
-    break;
+    case MSTATE_DOING_MORE:
+      result = mstate_domore_pollset(data, ps);
+      break;
 
-  case MSTATE_DO:
-  case MSTATE_DOING:
-    result = mstate_do_pollset(data, ps);
-    break;
+    case MSTATE_DID: /* same as PERFORMING in regard to polling */
+    case MSTATE_PERFORMING:
+      result = mstate_perform_pollset(data, ps);
+      break;
 
-  case MSTATE_DOING_MORE:
-    result = mstate_domore_pollset(data, ps);
-    break;
+    case MSTATE_RATELIMITING:
+      /* we need to let time pass, ignore socket(s) */
+      break;
 
-  case MSTATE_DID: /* same as PERFORMING in regard to polling */
-  case MSTATE_PERFORMING:
-    result = mstate_perform_pollset(data, ps);
-    break;
+    case MSTATE_DONE:
+    case MSTATE_COMPLETED:
+    case MSTATE_MSGSENT:
+      /* nothing more to poll for */
+      break;
 
-  case MSTATE_RATELIMITING:
-    /* we need to let time pass, ignore socket(s) */
-    break;
-
-  case MSTATE_DONE:
-  case MSTATE_COMPLETED:
-  case MSTATE_MSGSENT:
-    /* nothing more to poll for */
-    break;
-
-  default:
-    failf(data, "multi_getsock: unexpected multi state %d", data->mstate);
-    DEBUGASSERT(0);
-    break;
+    default:
+      failf(data, "multi_getsock: unexpected multi state %d", data->mstate);
+      DEBUGASSERT(0);
+      break;
+    }
   }
 
   if(result) {
     if(result == CURLE_OUT_OF_MEMORY)
-      mresult = CURLM_OUT_OF_MEMORY;
-    else {
-      failf(data, "error determining pollset: %d", result);
-      mresult = CURLM_INTERNAL_ERROR;
-    }
-    goto out;
+      return CURLM_OUT_OF_MEMORY;
+    failf(data, "error determining pollset: %d", result);
+    return CURLM_INTERNAL_ERROR;
   }
 
 #ifdef CURLVERBOSE
@@ -1208,8 +1241,7 @@
   }
 #endif
 
-out:
-  return mresult;
+  return CURLM_OK;
 }
 
 CURLMcode curl_multi_fdset(CURLM *m,
@@ -1328,7 +1360,167 @@
   if(!getsockopt(s, SOL_SOCKET, SO_TYPE, (char *)&t, &l) && t == SOCK_STREAM)
     swrite(s, NULL, 0);
 }
-#endif
+
+static CURLMcode multi_winsock_select(struct Curl_multi *multi,
+                                      struct curl_pollfds *cpfds,
+                                      unsigned int curl_nfds,
+                                      struct curl_waitfd extra_fds[],
+                                      unsigned int extra_nfds,
+                                      int timeout_ms,
+                                      bool extrawait,
+                                      int *pnevents)
+{
+  CURLMcode mresult = CURLM_OK;
+  WSANETWORKEVENTS wsa_events;
+  int nevents = 0;
+  size_t i;
+
+  DEBUGASSERT(multi->wsa_event != WSA_INVALID_EVENT);
+
+  /* Set the WSA events based on the collected pollds */
+  for(i = 0; i < cpfds->n; i++) {
+    long mask = 0;
+    if(cpfds->pfds[i].events & POLLIN)
+      mask |= FD_READ | FD_ACCEPT | FD_CLOSE;
+    if(cpfds->pfds[i].events & POLLPRI)
+      mask |= FD_OOB;
+    if(cpfds->pfds[i].events & POLLOUT) {
+      mask |= FD_WRITE | FD_CONNECT | FD_CLOSE;
+      reset_socket_fdwrite(cpfds->pfds[i].fd);
+    }
+    if(mask) {
+      if(WSAEventSelect(cpfds->pfds[i].fd, multi->wsa_event, mask) != 0) {
+        mresult = CURLM_OUT_OF_MEMORY;
+        goto out;
+      }
+    }
+  }
+
+  if(cpfds->n || extrawait) {
+    int pollrc = 0;
+
+    if(cpfds->n) {         /* pre-check with Winsock */
+      pollrc = Curl_poll(cpfds->pfds, cpfds->n, 0);
+      if(pollrc < 0) {
+        mresult = CURLM_UNRECOVERABLE_POLL;
+        goto out;
+      }
+      nevents = pollrc;
+    }
+
+    if(!nevents) {
+      /* now wait... if not ready during the pre-check (pollrc == 0) */
+      WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, (DWORD)timeout_ms,
+                               FALSE);
+    }
+
+    /* With Winsock, we have to run the following section unconditionally
+       to call WSAEventSelect(fd, event, 0) on all the sockets */
+    /* copy revents results from the poll to the curl_multi_wait poll
+       struct, the bit values of the actual underlying poll() implementation
+       may not be the same as the ones in the public libcurl API! */
+    for(i = 0; i < extra_nfds; i++) {
+      unsigned short mask = 0;
+      curl_socket_t s = extra_fds[i].fd;
+
+      wsa_events.lNetworkEvents = 0;
+      if(WSAEnumNetworkEvents(s, NULL, &wsa_events) == 0) {
+        if(wsa_events.lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE))
+          mask |= CURL_WAIT_POLLIN;
+        if(wsa_events.lNetworkEvents & (FD_WRITE | FD_CONNECT | FD_CLOSE))
+          mask |= CURL_WAIT_POLLOUT;
+        if(wsa_events.lNetworkEvents & FD_OOB)
+          mask |= CURL_WAIT_POLLPRI;
+        if(!pollrc && wsa_events.lNetworkEvents)
+          nevents++;
+      }
+      WSAEventSelect(s, multi->wsa_event, 0);
+      if(!pollrc) {
+        extra_fds[i].revents = (short)mask;
+        continue;
+      }
+      else {
+        unsigned r = (unsigned)cpfds->pfds[curl_nfds + i].revents;
+        if(r & POLLIN)
+          mask |= CURL_WAIT_POLLIN;
+        if(r & POLLOUT)
+          mask |= CURL_WAIT_POLLOUT;
+        if(r & POLLPRI)
+          mask |= CURL_WAIT_POLLPRI;
+        extra_fds[i].revents = (short)mask;
+      }
+    }
+
+    /* Count up all our own sockets that had activity,
+       and remove them from the event. */
+    for(i = 0; i < curl_nfds; ++i) {
+      wsa_events.lNetworkEvents = 0;
+      if(WSAEnumNetworkEvents(cpfds->pfds[i].fd, NULL, &wsa_events) == 0) {
+        if(!pollrc && wsa_events.lNetworkEvents)
+          nevents++;
+      }
+      WSAEventSelect(cpfds->pfds[i].fd, multi->wsa_event, 0);
+    }
+    WSAResetEvent(multi->wsa_event);
+  }
+
+out:
+  *pnevents = nevents;
+  return mresult;
+}
+
+#else /* USE_WINSOCK */
+
+static CURLMcode multi_posix_poll(struct Curl_multi *multi,
+                                  struct curl_pollfds *cpfds,
+                                  unsigned int curl_nfds,
+                                  struct curl_waitfd extra_fds[],
+                                  unsigned int extra_nfds,
+                                  int timeout_ms,
+                                  bool extrawait,
+                                  int *pnevents)
+{
+  CURLMcode mresult = CURLM_OK;
+  int nevents = 0;
+  size_t i;
+
+  (void)multi;
+  if(cpfds->n) {
+    int pollrc = Curl_poll(cpfds->pfds, cpfds->n, timeout_ms); /* wait... */
+    if(pollrc < 0) {
+      mresult = CURLM_UNRECOVERABLE_POLL;
+      goto out;
+    }
+    nevents = pollrc;
+
+    /* copy revents results from the poll to the curl_multi_wait poll
+       struct, the bit values of the actual underlying poll() implementation
+       may not be the same as the ones in the public libcurl API! */
+    for(i = 0; i < extra_nfds; i++) {
+      unsigned r = (unsigned)cpfds->pfds[curl_nfds + i].revents;
+      unsigned short mask = 0;
+      if(r & POLLIN)
+        mask |= CURL_WAIT_POLLIN;
+      if(r & POLLOUT)
+        mask |= CURL_WAIT_POLLOUT;
+      if(r & POLLPRI)
+        mask |= CURL_WAIT_POLLPRI;
+      extra_fds[i].revents = (short)mask;
+    }
+  }
+  else if(extrawait) {
+    /* No fds to poll, but asked to obey timeout_ms anyway. We cannot
+     * use Curl_poll() as it, on some platforms, returns immediately
+     * without fds. */
+    curlx_wait_ms(timeout_ms);
+  }
+
+out:
+  *pnevents = nevents;
+  return mresult;
+}
+
+#endif /* !USE_WINSOCK */
 
 #define NUM_POLLS_ON_STACK 10
 
@@ -1337,13 +1529,12 @@
                             unsigned int extra_nfds,
                             int timeout_ms,
                             int *ret,
-                            bool extrawait, /* when no socket, wait */
-                            bool use_wakeup)
+                            bool extrawait)  /* when no socket, wait */
 {
   size_t i;
   struct curltime expire_time;
   long timeout_internal;
-  int retcode = 0;
+  int nevents = 0;
   struct easy_pollset ps;
   struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
   struct curl_pollfds cpfds;
@@ -1351,13 +1542,8 @@
   struct Curl_easy *data = NULL;
   CURLMcode mresult = CURLM_OK;
   uint32_t mid;
-
-#ifdef USE_WINSOCK
-  WSANETWORKEVENTS wsa_events;
-  DEBUGASSERT(multi->wsa_event != WSA_INVALID_EVENT);
-#endif
-#ifndef ENABLE_WAKEUP
-  (void)use_wakeup;
+#ifdef ENABLE_WAKEUP
+  int wakeup_idx = -1;
 #endif
 
   if(!GOOD_MULTI_HANDLE(multi))
@@ -1395,6 +1581,20 @@
     goto out;
   }
 
+#ifdef ENABLE_WAKEUP
+  /* If `extrawait` is TRUE *or* we have `extra_fds`to poll *or* we
+   * have transfer sockets to poll, we obey `timeout_ms`.
+   * Then we need to also monitor the multi's wakeup
+   * socket to catch calls to `curl_multi_wakeup()` during the wait. */
+  if(extrawait || cpfds.n || extra_nfds) {
+    wakeup_idx = cpfds.n;
+    if(Curl_pollfds_add_sock(&cpfds, multi->wakeup_pair[0], POLLIN)) {
+      mresult = CURLM_OUT_OF_MEMORY;
+      goto out;
+    }
+  }
+#endif
+
   curl_nfds = cpfds.n; /* what curl internally uses in cpfds */
   /* Add external file descriptions from poll-like struct curl_waitfd */
   for(i = 0; i < extra_nfds; i++) {
@@ -1411,185 +1611,64 @@
     }
   }
 
-#ifdef USE_WINSOCK
-  /* Set the WSA events based on the collected pollds */
-  for(i = 0; i < cpfds.n; i++) {
-    long mask = 0;
-    if(cpfds.pfds[i].events & POLLIN)
-      mask |= FD_READ | FD_ACCEPT | FD_CLOSE;
-    if(cpfds.pfds[i].events & POLLPRI)
-      mask |= FD_OOB;
-    if(cpfds.pfds[i].events & POLLOUT) {
-      mask |= FD_WRITE | FD_CONNECT | FD_CLOSE;
-      reset_socket_fdwrite(cpfds.pfds[i].fd);
-    }
-    if(mask) {
-      if(WSAEventSelect(cpfds.pfds[i].fd, multi->wsa_event, mask) != 0) {
-        mresult = CURLM_OUT_OF_MEMORY;
-        goto out;
-      }
-    }
-  }
-#endif
-
-#ifdef ENABLE_WAKEUP
-#ifndef USE_WINSOCK
-  if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
-    if(Curl_pollfds_add_sock(&cpfds, multi->wakeup_pair[0], POLLIN)) {
-      mresult = CURLM_OUT_OF_MEMORY;
-      goto out;
-    }
-  }
-#endif
-#endif
-
   /* We check the internal timeout *AFTER* we collected all sockets to
    * poll. Collecting the sockets may install new timers by protocols
    * and connection filters.
-   * Use the shorter one of the internal and the caller requested timeout. */
+   * Use the shorter one of the internal and the caller requested timeout.
+   * If we are called with `!extrawait` and multi_timeout() reports no
+   * timeouts exist, do not wait. */
   multi_timeout(multi, &expire_time, &timeout_internal);
   if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms))
     timeout_ms = (int)timeout_internal;
 
   if(data)
-    CURL_TRC_M(data, "multi_wait(fds=%d, timeout=%d) tinternal=%ld",
+    CURL_TRC_M(data, "multi_wait(fds=%u, timeout=%d) tinternal=%ld",
                cpfds.n, timeout_ms, timeout_internal);
-#if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK)
-  if(cpfds.n || use_wakeup) {
-#else
-  if(cpfds.n) {
-#endif
-    int pollrc;
-#ifdef USE_WINSOCK
-    if(cpfds.n)         /* pre-check with Winsock */
-      pollrc = Curl_poll(cpfds.pfds, cpfds.n, 0);
-    else
-      pollrc = 0;
-#else
-    pollrc = Curl_poll(cpfds.pfds, cpfds.n, timeout_ms); /* wait... */
-#endif
-    if(pollrc < 0) {
-      mresult = CURLM_UNRECOVERABLE_POLL;
-      goto out;
-    }
-
-    if(pollrc > 0) {
-      retcode = pollrc;
-#ifdef USE_WINSOCK
-    }
-    else { /* now wait... if not ready during the pre-check (pollrc == 0) */
-      WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, (DWORD)timeout_ms,
-                               FALSE);
-    }
-    /* With Winsock, we have to run the following section unconditionally
-       to call WSAEventSelect(fd, event, 0) on all the sockets */
-    {
-#endif
-      /* copy revents results from the poll to the curl_multi_wait poll
-         struct, the bit values of the actual underlying poll() implementation
-         may not be the same as the ones in the public libcurl API! */
-      for(i = 0; i < extra_nfds; i++) {
-        unsigned r = (unsigned)cpfds.pfds[curl_nfds + i].revents;
-        unsigned short mask = 0;
-#ifdef USE_WINSOCK
-        curl_socket_t s = extra_fds[i].fd;
-        wsa_events.lNetworkEvents = 0;
-        if(WSAEnumNetworkEvents(s, NULL, &wsa_events) == 0) {
-          if(wsa_events.lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE))
-            mask |= CURL_WAIT_POLLIN;
-          if(wsa_events.lNetworkEvents & (FD_WRITE | FD_CONNECT | FD_CLOSE))
-            mask |= CURL_WAIT_POLLOUT;
-          if(wsa_events.lNetworkEvents & FD_OOB)
-            mask |= CURL_WAIT_POLLPRI;
-          if(ret && !pollrc && wsa_events.lNetworkEvents)
-            retcode++;
-        }
-        WSAEventSelect(s, multi->wsa_event, 0);
-        if(!pollrc) {
-          extra_fds[i].revents = (short)mask;
-          continue;
-        }
-#endif
-        if(r & POLLIN)
-          mask |= CURL_WAIT_POLLIN;
-        if(r & POLLOUT)
-          mask |= CURL_WAIT_POLLOUT;
-        if(r & POLLPRI)
-          mask |= CURL_WAIT_POLLPRI;
-        extra_fds[i].revents = (short)mask;
-      }
 
 #ifdef USE_WINSOCK
-      /* Count up all our own sockets that had activity,
-         and remove them from the event. */
-      for(i = 0; i < curl_nfds; ++i) {
-        wsa_events.lNetworkEvents = 0;
-        if(WSAEnumNetworkEvents(cpfds.pfds[i].fd, NULL, &wsa_events) == 0) {
-          if(ret && !pollrc && wsa_events.lNetworkEvents)
-            retcode++;
-        }
-        WSAEventSelect(cpfds.pfds[i].fd, multi->wsa_event, 0);
-      }
-      WSAResetEvent(multi->wsa_event);
+  mresult = multi_winsock_select(multi, &cpfds, curl_nfds,
+                                 extra_fds, extra_nfds,
+                                 timeout_ms, extrawait, &nevents);
 #else
+  mresult = multi_posix_poll(multi, &cpfds, curl_nfds,
+                             extra_fds, extra_nfds,
+                             timeout_ms, extrawait, &nevents);
+#endif
+
 #ifdef ENABLE_WAKEUP
-      if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
-        if(cpfds.pfds[curl_nfds + extra_nfds].revents & POLLIN) {
-          (void)Curl_wakeup_consume(multi->wakeup_pair, TRUE);
-          /* do not count the wakeup socket into the returned value */
-          retcode--;
-        }
+    if(nevents && (wakeup_idx >= 0)) {
+      if(cpfds.pfds[wakeup_idx].revents & POLLIN) {
+        (void)Curl_wakeup_consume(multi->wakeup_pair, TRUE);
+        /* do not count the wakeup socket into the returned value */
+        nevents--;
       }
-#endif
-#endif
     }
-  }
-
-  if(ret)
-    *ret = retcode;
-#if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK)
-  if(extrawait && !cpfds.n && !use_wakeup) {
-#else
-  if(extrawait && !cpfds.n) {
 #endif
-    long sleep_ms = 0;
-
-    /* Avoid busy-looping when there is nothing particular to wait for */
-    multi_timeout(multi, &expire_time, &sleep_ms);
-    if(sleep_ms) {
-      if(sleep_ms > timeout_ms)
-        sleep_ms = timeout_ms;
-      /* when there are no easy handles in the multi, this holds a -1
-         timeout */
-      else if(sleep_ms < 0)
-        sleep_ms = timeout_ms;
-      curlx_wait_ms(sleep_ms);
-    }
-  }
 
 out:
   Curl_pollset_cleanup(&ps);
   Curl_pollfds_cleanup(&cpfds);
+  if(ret)
+    *ret = nevents;
   return mresult;
 }
 
-CURLMcode curl_multi_wait(CURLM *multi,
+CURLMcode curl_multi_wait(CURLM *m,
                           struct curl_waitfd extra_fds[],
                           unsigned int extra_nfds,
                           int timeout_ms,
                           int *ret)
 {
-  return multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE,
-                    FALSE);
+  return multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, FALSE);
 }
 
-CURLMcode curl_multi_poll(CURLM *multi,
+CURLMcode curl_multi_poll(CURLM *m,
                           struct curl_waitfd extra_fds[],
                           unsigned int extra_nfds,
                           int timeout_ms,
                           int *ret)
 {
-  return multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE, TRUE);
+  return multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, TRUE);
 }
 
 CURLMcode curl_multi_wakeup(CURLM *m)
@@ -1598,27 +1677,24 @@
      it has to be careful only to access parts of the
      Curl_multi struct that are constant */
   struct Curl_multi *multi = m;
+  CURLMcode mresult = CURLM_WAKEUP_FAILURE;
 
   /* GOOD_MULTI_HANDLE can be safely called */
   if(!GOOD_MULTI_HANDLE(multi))
     return CURLM_BAD_HANDLE;
 
 #ifdef ENABLE_WAKEUP
-#ifdef USE_WINSOCK
-  if(WSASetEvent(multi->wsa_event))
-    return CURLM_OK;
-#else
   /* the wakeup_pair variable is only written during init and cleanup,
      making it safe to access from another thread after the init part
      and before cleanup */
-  if(multi->wakeup_pair[1] != CURL_SOCKET_BAD) {
-    if(Curl_wakeup_signal(multi->wakeup_pair))
-      return CURLM_WAKEUP_FAILURE;
-    return CURLM_OK;
-  }
+  if(!Curl_wakeup_signal(multi->wakeup_pair))
+    mresult = CURLM_OK;
 #endif
+#ifdef USE_WINSOCK
+  if(WSASetEvent(multi->wsa_event))
+    mresult = CURLM_OK;
 #endif
-  return CURLM_WAKEUP_FAILURE;
+  return mresult;
 }
 
 /*
@@ -1660,7 +1736,6 @@
 
   mresult = curl_multi_add_handle(multi, data);
   if(!mresult) {
-    struct SingleRequest *k = &data->req;
     CURLcode result;
 
     /* pass in NULL for 'conn' here since we do not want to init the
@@ -1674,7 +1749,7 @@
     /* take this handle to the perform state right away */
     multistate(data, MSTATE_PERFORMING);
     Curl_attach_connection(data, conn);
-    k->keepon |= KEEP_RECV; /* setup to receive! */
+    CURL_REQ_SET_RECV(data);
   }
   return mresult;
 }
@@ -1732,13 +1807,9 @@
       since = data->progress.t_startsingle;
     else
       since = data->progress.t_startop;
-    if(data->mstate == MSTATE_RESOLVING)
-      failf(data, "Resolving timed out after %" FMT_TIMEDIFF_T
-            " milliseconds",
-            curlx_ptimediff_ms(Curl_pgrs_now(data), &since));
-    else if(data->mstate == MSTATE_CONNECTING)
-      failf(data, "Connection timed out after %" FMT_TIMEDIFF_T
-            " milliseconds",
+    if(data->mstate == MSTATE_CONNECTING)
+      failf(data, "%s timed out after %" FMT_TIMEDIFF_T " milliseconds",
+            data->conn->bits.dns_resolved ? "Connection" : "Resolving",
             curlx_ptimediff_ms(Curl_pgrs_now(data), &since));
     else {
       struct SingleRequest *k = &data->req;
@@ -1867,7 +1938,7 @@
  * multi_follow() handles the URL redirect magic. Pass in the 'newurl' string
  * as given by the remote server and set up the new URL to request.
  *
- * This function DOES NOT FREE the given url.
+ * This function DOES NOT FREE the given URL.
  */
 static CURLcode multi_follow(struct Curl_easy *data,
                              const struct Curl_scheme *handler,
@@ -1876,6 +1947,12 @@
 {
   if(handler && handler->run->follow)
     return handler->run->follow(data, newurl, type);
+
+  if(type == FOLLOW_RETRY)
+    /* Retries are generic and do not require protocol-specific redirect
+       handling. */
+    return CURLE_OK;
+
   return CURLE_TOO_MANY_REDIRECTS;
 }
 
@@ -2224,56 +2301,6 @@
   return mresult;
 }
 
-static CURLMcode state_resolving(struct Curl_multi *multi,
-                                 struct Curl_easy *data,
-                                 bool *stream_errorp,
-                                 CURLcode *resultp)
-{
-  struct Curl_dns_entry *dns = NULL;
-  CURLcode result;
-  CURLMcode mresult = CURLM_OK;
-
-  result = Curl_resolv_check(data, &dns);
-  CURL_TRC_DNS(data, "Curl_resolv_check() -> %d, %s",
-               result, dns ? "found" : "missing");
-  /* Update sockets here, because the socket(s) may have been closed and the
-     application thus needs to be told, even if it is likely that the same
-     socket(s) will again be used further down. If the name has not yet been
-     resolved, it is likely that new sockets have been opened in an attempt to
-     contact another resolver. */
-  mresult = Curl_multi_ev_assess_xfer(multi, data);
-  if(mresult)
-    return mresult;
-
-  if(dns) {
-    bool connected;
-    /* Perform the next step in the connection phase, and then move on to the
-       WAITCONNECT state */
-    result = Curl_once_resolved(data, dns, &connected);
-
-    if(result)
-      /* if Curl_once_resolved() returns failure, the connection struct is
-         already freed and gone */
-      data->conn = NULL; /* no more connection */
-    else {
-      /* call again please so that we get the next socket setup */
-      mresult = CURLM_CALL_MULTI_PERFORM;
-      if(connected)
-        multistate(data, MSTATE_PROTOCONNECT);
-      else {
-        multistate(data, MSTATE_CONNECTING);
-      }
-    }
-  }
-
-  if(result)
-    /* failure detected */
-    *stream_errorp = TRUE;
-
-  *resultp = result;
-  return mresult;
-}
-
 static CURLMcode state_connect(struct Curl_multi *multi,
                                struct Curl_easy *data,
                                CURLcode *resultp)
@@ -2281,9 +2308,8 @@
   /* Connect. We want to get a connection identifier filled in. This state can
      be entered from SETUP and from PENDING. */
   bool connected;
-  bool async;
   CURLMcode mresult = CURLM_OK;
-  CURLcode result = Curl_connect(data, &async, &connected);
+  CURLcode result = Curl_connect(data, &connected);
   if(result == CURLE_NO_CONNECTION_AVAILABLE) {
     /* There was no connection available. We will go to the pending state and
        wait for an available connection. */
@@ -2299,26 +2325,21 @@
     process_pending_handles(data->multi);
 
   if(!result) {
-    if(async)
-      /* We are now waiting for an asynchronous name lookup */
-      multistate(data, MSTATE_RESOLVING);
-    else {
-      /* after the connect has been sent off, go WAITCONNECT unless the
-         protocol connect is already done and we can go directly to WAITDO or
-         DO! */
-      mresult = CURLM_CALL_MULTI_PERFORM;
+    /* after the connect has been sent off, go WAITCONNECT unless the
+       protocol connect is already done and we can go directly to WAITDO or
+       DO! */
+    mresult = CURLM_CALL_MULTI_PERFORM;
 
-      if(connected) {
-        if(!data->conn->bits.reuse &&
-           Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) {
-          /* new connection, can multiplex, wake pending handles */
-          process_pending_handles(data->multi);
-        }
-        multistate(data, MSTATE_PROTOCONNECT);
+    if(connected) {
+      if(!data->conn->bits.reuse &&
+         Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) {
+        /* new connection, can multiplex, wake pending handles */
+        process_pending_handles(data->multi);
       }
-      else {
-        multistate(data, MSTATE_CONNECTING);
-      }
+      multistate(data, MSTATE_PROTOCONNECT);
+    }
+    else {
+      multistate(data, MSTATE_CONNECTING);
     }
   }
   *resultp = result;
@@ -2367,7 +2388,7 @@
       return result;
     }
     /* if there is still a connection to use, call the progress function */
-    else if(data->conn) {
+    else if(data->conn && Curl_conn_is_connected(data->conn, FIRSTSOCKET)) {
       result = Curl_pgrsUpdate(data);
       if(result) {
         /* aborted due to progress callback return code must close the
@@ -2424,6 +2445,8 @@
   Curl_uint32_bset_remove(&multi->pending, data->mid);
   Curl_uint32_bset_add(&multi->msgsent, data->mid);
   --multi->xfers_alive;
+  if(!multi->xfers_alive)
+    multi_assess_wakeup(multi);
 }
 
 static CURLMcode multi_runsingle(struct Curl_multi *multi,
@@ -2456,6 +2479,9 @@
   Curl_uint32_bset_remove(&multi->dirty, data->mid);
 
   if(data == multi->admin) {
+#ifdef USE_RESOLV_THREADED
+    Curl_async_thrdd_multi_process(multi);
+#endif
     Curl_cshutdn_perform(&multi->cshutdn, multi->admin, sigpipe_ctx);
     return CURLM_OK;
   }
@@ -2519,15 +2545,14 @@
       mresult = state_connect(multi, data, &result);
       break;
 
-    case MSTATE_RESOLVING:
-      /* awaiting an asynch name resolve to complete */
-      mresult = state_resolving(multi, data, &stream_error, &result);
-      break;
-
     case MSTATE_CONNECTING:
       /* awaiting a completion of an asynch TCP connect */
-      DEBUGASSERT(data->conn);
-      if(!Curl_xfer_recv_is_paused(data)) {
+      if(!data->conn) {
+        DEBUGASSERT(0);
+        result = CURLE_FAILED_INIT;
+        break;
+      }
+      else if(!Curl_xfer_recv_is_paused(data)) {
         result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &connected);
         if(connected && !result) {
           if(!data->conn->bits.reuse &&
@@ -2540,6 +2565,7 @@
         }
         else if(result) {
           /* failure detected */
+          CURL_TRC_M(data, "connect failed -> %d", result);
           multi_posttransfer(data);
           multi_done(data, result, TRUE);
           stream_error = TRUE;
@@ -2885,6 +2911,9 @@
       } while(Curl_uint32_tbl_next(&multi->xfers, mid, &mid, &entry));
     }
 
+#ifdef USE_RESOLV_THREADED
+    Curl_async_thrdd_multi_destroy(multi, !multi->quick_exit);
+#endif
     Curl_cpool_destroy(&multi->cpool);
     Curl_cshutdn_destroy(&multi->cshutdn, multi->admin);
     if(multi->admin) {
@@ -2906,11 +2935,10 @@
 
 #ifdef USE_WINSOCK
     WSACloseEvent(multi->wsa_event);
-#else
+#endif
 #ifdef ENABLE_WAKEUP
   Curl_wakeup_destroy(multi->wakeup_pair);
 #endif
-#endif
 
     multi_xfer_bufs_free(multi);
     Curl_mntfy_cleanup(multi);
@@ -3148,6 +3176,14 @@
        handles the case when the application asks libcurl to run the timeout
        prematurely. */
     memset(&multi->last_expire_ts, 0, sizeof(multi->last_expire_ts));
+
+    /* Applications may set `socket_cb` *after* having added transfers
+     * first. *Then* kick off processing with a
+     * curl_multi_socket_action(TIMEOUT) afterwards. Make sure our
+     * admin handle registers its pollset with the callbacks present. */
+    mresult = multi_assess_wakeup(multi);
+    if(mresult)
+      goto out;
   }
 
   multi_mark_expired_as_dirty(multi, multi_now(multi));
@@ -3255,6 +3291,11 @@
   }
   case CURLMOPT_NETWORK_CHANGED: {
     long val = va_arg(param, long);
+    if(val & CURLMNWC_CLEAR_ALL)
+      /* In the beginning, all values available to set were 1 by mistake. We
+         converted this to mean "all", thus setting all the bits
+         automatically */
+      val = CURLMNWC_CLEAR_DNS | CURLMNWC_CLEAR_CONNS;
     if(val & CURLMNWC_CLEAR_DNS) {
       Curl_dnscache_clear(multi->admin);
     }
@@ -3269,6 +3310,34 @@
   case CURLMOPT_NOTIFYDATA:
     multi->ntfy.ntfy_cb_data = va_arg(param, void *);
     break;
+  case CURLMOPT_RESOLVE_THREADS_MAX:
+#ifdef USE_RESOLV_THREADED
+    uarg = va_arg(param, long);
+    if((uarg <= 0) || (uarg > UINT32_MAX))
+      mresult = CURLM_BAD_FUNCTION_ARGUMENT;
+    else {
+      CURLcode result = Curl_async_thrdd_multi_set_props(
+        multi, 0, (uint32_t)uarg, 2000);
+      switch(result) {
+      case CURLE_OK:
+        mresult = CURLM_OK;
+        break;
+      case CURLE_BAD_FUNCTION_ARGUMENT:
+        mresult = CURLM_BAD_FUNCTION_ARGUMENT;
+        break;
+      case CURLE_OUT_OF_MEMORY:
+        mresult = CURLM_OUT_OF_MEMORY;
+        break;
+      default:
+        mresult = CURLM_INTERNAL_ERROR;
+        break;
+      }
+    }
+#endif
+    break;
+  case CURLMOPT_QUICK_EXIT:
+    multi->quick_exit = va_arg(param, long) ? 1 : 0;
+    break;
   default:
     mresult = CURLM_UNKNOWN_OPTION;
     break;
@@ -3652,14 +3721,14 @@
   }
 }
 
-CURLMcode curl_multi_assign(CURLM *m, curl_socket_t s,
-                            void *hashp)
+CURLMcode curl_multi_assign(CURLM *m, curl_socket_t sockfd,
+                            void *sockp)
 {
   struct Curl_multi *multi = m;
   if(!GOOD_MULTI_HANDLE(multi))
     return CURLM_BAD_HANDLE;
 
-  return Curl_multi_ev_assign(multi, s, hashp);
+  return Curl_multi_ev_assign(multi, sockfd, sockp);
 }
 
 static void move_pending_to_connect(struct Curl_multi *multi,
@@ -3691,13 +3760,32 @@
 */
 static void process_pending_handles(struct Curl_multi *multi)
 {
-  uint32_t mid;
+  uint32_t mid = multi->last_pending_mid;
+
+  if(mid) {
+    while(Curl_uint32_bset_next(&multi->pending, mid, &mid)) {
+      struct Curl_easy *data = Curl_multi_get_easy(multi, mid);
+      if(data) {
+        move_pending_to_connect(multi, data);
+        multi->last_pending_mid = mid;
+        return;
+      }
+      /* transfer no longer known, should not happen */
+      Curl_uint32_bset_remove(&multi->pending, mid);
+      DEBUGASSERT(0);
+    }
+    /* found no pending transfers with `mid` larger than `last_pending_mid`.
+     * Start at the beginning of the pending set again. */
+    multi->last_pending_mid = 0;
+  }
+
   if(Curl_uint32_bset_first(&multi->pending, &mid)) {
     do {
       struct Curl_easy *data = Curl_multi_get_easy(multi, mid);
       if(data) {
         move_pending_to_connect(multi, data);
-        break;
+        multi->last_pending_mid = mid;
+        return;
       }
       /* transfer no longer known, should not happen */
       Curl_uint32_bset_remove(&multi->pending, mid);
@@ -3941,13 +4029,13 @@
 static void multi_xfer_bufs_free(struct Curl_multi *multi)
 {
   DEBUGASSERT(multi);
-  Curl_safefree(multi->xfer_buf);
+  curlx_safefree(multi->xfer_buf);
   multi->xfer_buf_len = 0;
   multi->xfer_buf_borrowed = FALSE;
-  Curl_safefree(multi->xfer_ulbuf);
+  curlx_safefree(multi->xfer_ulbuf);
   multi->xfer_ulbuf_len = 0;
   multi->xfer_ulbuf_borrowed = FALSE;
-  Curl_safefree(multi->xfer_sockbuf);
+  curlx_safefree(multi->xfer_sockbuf);
   multi->xfer_sockbuf_len = 0;
   multi->xfer_sockbuf_borrowed = FALSE;
 }
diff --git a/Utilities/cmcurl/lib/multi_ev.c b/Utilities/cmcurl/lib/multi_ev.c
index 696a012..937e7ce 100644
--- a/Utilities/cmcurl/lib/multi_ev.c
+++ b/Utilities/cmcurl/lib/multi_ev.c
@@ -253,7 +253,7 @@
   DEBUGASSERT(entry->writers + entry->readers);
 
   CURL_TRC_M(data, "ev update fd=%" FMT_SOCKET_T ", action '%s%s' -> '%s%s'"
-             " (%d/%d r/w)", s,
+             " (%u/%u r/w)", s,
              (last_action & CURL_POLL_IN) ? "IN" : "",
              (last_action & CURL_POLL_OUT) ? "OUT" : "",
              (cur_action & CURL_POLL_IN) ? "IN" : "",
@@ -603,10 +603,8 @@
                              struct Curl_easy *data)
 {
   DEBUGASSERT(!data->conn); /* transfer should have been detached */
-  if(data != multi->admin) {
-    (void)mev_assess(multi, data, NULL);
-    Curl_meta_remove(data, CURL_META_MEV_POLLSET);
-  }
+  (void)mev_assess(multi, data, NULL);
+  Curl_meta_remove(data, CURL_META_MEV_POLLSET);
 }
 
 void Curl_multi_ev_conn_done(struct Curl_multi *multi,
diff --git a/Utilities/cmcurl/lib/multihandle.h b/Utilities/cmcurl/lib/multihandle.h
index 7a56112..c5cdfbe 100644
--- a/Utilities/cmcurl/lib/multihandle.h
+++ b/Utilities/cmcurl/lib/multihandle.h
@@ -27,7 +27,7 @@
 #include "hash.h"
 #include "conncache.h"
 #include "cshutdn.h"
-#include "hostip.h"
+#include "dnscache.h"
 #include "multi_ev.h"
 #include "multi_ntfy.h"
 #include "psl.h"
@@ -53,7 +53,6 @@
   MSTATE_PENDING,      /* no connections, waiting for one */
   MSTATE_SETUP,        /* start a new transfer */
   MSTATE_CONNECT,      /* resolve/connect has been sent off */
-  MSTATE_RESOLVING,    /* awaiting the resolve to finalize */
   MSTATE_CONNECTING,   /* awaiting the TCP connect to finalize */
   MSTATE_PROTOCONNECT, /* initiate protocol connect procedure */
   MSTATE_PROTOCONNECTING, /* completing the protocol-specific connect phase */
@@ -71,7 +70,7 @@
 
 #define CURLPIPE_ANY (CURLPIPE_MULTIPLEX)
 
-#ifndef CURL_DISABLE_SOCKETPAIR
+#if !defined(CURL_DISABLE_SOCKETPAIR) && !defined(USE_WINSOCK)
 #define ENABLE_WAKEUP
 #endif
 
@@ -109,6 +108,9 @@
 
   struct Curl_dnscache dnscache; /* DNS cache */
   struct Curl_ssl_scache *ssl_scache; /* TLS session pool */
+#ifdef USE_RESOLV_THREADED
+  struct curl_thrdq *resolv_thrdq;
+#endif
 
 #ifdef USE_LIBPSL
   /* PSL cache. */
@@ -160,13 +162,12 @@
 
 #ifdef USE_WINSOCK
   WSAEVENT wsa_event; /* Winsock event used for waits */
-#else
+#endif
 #ifdef ENABLE_WAKEUP
   curl_socket_t wakeup_pair[2]; /* eventfd()/pipe()/socketpair() used for
                                    wakeup 0 is used for read, 1 is used
                                    for write */
 #endif
-#endif
   unsigned int max_concurrent_streams;
   unsigned int maxconnects; /* if >0, a fixed limit of the maximum number of
                                entries we are allowed to grow the connection
@@ -174,6 +175,8 @@
 #ifdef DEBUGBUILD
   unsigned int now_access_count;
 #endif
+  uint32_t last_pending_mid; /* mid of last pending transfer rescheduled */
+  uint32_t last_resolv_id; /* id of the last DNS resolve operation */
   BIT(ipv6_works);
   BIT(multiplexing);           /* multiplexing wanted */
   BIT(recheckstate);           /* see Curl_multi_connchanged */
@@ -187,6 +190,7 @@
   BIT(xfer_buf_borrowed);      /* xfer_buf is currently being borrowed */
   BIT(xfer_ulbuf_borrowed);    /* xfer_ulbuf is currently being borrowed */
   BIT(xfer_sockbuf_borrowed);  /* xfer_sockbuf is currently being borrowed */
+  BIT(quick_exit);             /* do not join threads on cleanup */
 #ifdef DEBUGBUILD
   BIT(warned);                 /* true after user warned of DEBUGBUILD */
 #endif
diff --git a/Utilities/cmcurl/lib/netrc.c b/Utilities/cmcurl/lib/netrc.c
index 1073cc5..72d8fee 100644
--- a/Utilities/cmcurl/lib/netrc.c
+++ b/Utilities/cmcurl/lib/netrc.c
@@ -72,35 +72,329 @@
 {
   NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */
   FILE *file = curlx_fopen(filename, FOPEN_READTEXT);
-  struct dynbuf linebuf;
-  curlx_dyn_init(&linebuf, MAX_NETRC_LINE);
 
   if(file) {
-    CURLcode result = CURLE_OK;
-    bool eof;
-    ret = NETRC_OK;
-    do {
-      const char *line;
-      result = Curl_get_line(&linebuf, file, &eof);
-      if(!result) {
-        line = curlx_dyn_ptr(&linebuf);
-        /* skip comments on load */
-        curlx_str_passblanks(&line);
-        if(*line == '#')
-          continue;
-        result = curlx_dyn_add(filebuf, line);
-      }
-      if(result) {
-        curlx_dyn_free(filebuf);
-        ret = curl2netrc(result);
+    curlx_struct_stat stat;
+    if((curlx_fstat(fileno(file), &stat) == -1) || !S_ISDIR(stat.st_mode)) {
+      CURLcode result = CURLE_OK;
+      bool eof;
+      struct dynbuf linebuf;
+      curlx_dyn_init(&linebuf, MAX_NETRC_LINE);
+      ret = NETRC_OK;
+      do {
+        const char *line;
+        /* Curl_get_line always returns lines ending with a newline */
+        result = Curl_get_line(&linebuf, file, &eof);
+        if(!result) {
+          line = curlx_dyn_ptr(&linebuf);
+          /* skip comments on load */
+          curlx_str_passblanks(&line);
+          if(*line == '#')
+            continue;
+          result = curlx_dyn_add(filebuf, line);
+        }
+        if(result) {
+          curlx_dyn_free(filebuf);
+          ret = curl2netrc(result);
+          break;
+        }
+      } while(!eof);
+      curlx_dyn_free(&linebuf);
+    }
+    curlx_fclose(file);
+  }
+  return ret;
+}
+
+/* bundled parser state to keep function signatures compact */
+struct netrc_state {
+  char *login;
+  char *password;
+  enum host_lookup_state state;
+  enum found_state keyword;
+  NETRCcode retcode;
+  unsigned char found; /* FOUND_LOGIN | FOUND_PASSWORD bits */
+  bool our_login;
+  bool done;
+  bool specific_login;
+};
+
+/*
+ * Parse a quoted token starting after the opening '"'. Handles \n, \r, \t
+ * escape sequences. Advances *tok_endp past the closing '"'.
+ *
+ * Returns NETRC_OK or error.
+ */
+static NETRCcode netrc_quoted_token(const char **tok_endp,
+                                    struct dynbuf *token)
+{
+  bool escape = FALSE;
+  NETRCcode rc = NETRC_SYNTAX_ERROR;
+  const char *tok_end = *tok_endp;
+  tok_end++; /* pass the leading quote */
+  while(*tok_end) {
+    CURLcode result;
+    char s = *tok_end;
+    if(escape) {
+      escape = FALSE;
+      switch(s) {
+      case 'n':
+        s = '\n';
+        break;
+      case 'r':
+        s = '\r';
+        break;
+      case 't':
+        s = '\t';
         break;
       }
-    } while(!eof);
+    }
+    else if(s == '\\') {
+      escape = TRUE;
+      tok_end++;
+      continue;
+    }
+    else if(s == '\"') {
+      tok_end++; /* pass the ending quote */
+      rc = NETRC_OK;
+      break;
+    }
+    result = curlx_dyn_addn(token, &s, 1);
+    if(result) {
+      *tok_endp = tok_end;
+      return curl2netrc(result);
+    }
+    tok_end++;
   }
-  curlx_dyn_free(&linebuf);
-  if(file)
-    curlx_fclose(file);
-  return ret;
+  *tok_endp = tok_end;
+  return rc;
+}
+
+/*
+ * Gets the next token from the netrc buffer at *tokp. Writes the token into
+ * the 'token' dynbuf. Advances *tok_endp past the consumed token in the input
+ * buffer. Updates *statep for MACDEF newline handling. Sets *lineend = TRUE
+ * when the line is exhausted.
+ *
+ * Returns NETRC_OK or an error code.
+ */
+static NETRCcode netrc_get_token(const char **tokp,
+                                 const char **tok_endp,
+                                 struct dynbuf *token,
+                                 enum host_lookup_state *statep,
+                                 bool *lineend)
+{
+  const char *tok = *tokp;
+  const char *tok_end;
+
+  *lineend = FALSE;
+  curlx_dyn_reset(token);
+  curlx_str_passblanks(&tok);
+
+  /* tok is first non-space letter */
+  if(*statep == MACDEF) {
+    if((*tok == '\n') || (*tok == '\r'))
+      *statep = NOTHING; /* end of macro definition */
+    *lineend = TRUE;
+    *tokp = tok;
+    return NETRC_OK;
+  }
+
+  if(!*tok || (*tok == '\n')) {
+    /* end of line */
+    *lineend = TRUE;
+    *tokp = tok;
+    return NETRC_OK;
+  }
+
+  tok_end = tok;
+  if(*tok == '\"') {
+    /* quoted string */
+    NETRCcode ret = netrc_quoted_token(&tok_end, token);
+    if(ret)
+      return ret;
+  }
+  else {
+    /* unquoted token */
+    size_t len = 0;
+    CURLcode result;
+    while(*tok_end > ' ') {
+      tok_end++;
+      len++;
+    }
+    if(!len)
+      return NETRC_SYNTAX_ERROR;
+    result = curlx_dyn_addn(token, tok, len);
+    if(result)
+      return curl2netrc(result);
+  }
+
+  *tok_endp = tok_end;
+
+  if(curlx_dyn_len(token))
+    *tokp = curlx_dyn_ptr(token);
+  else
+    /* set it to blank to avoid NULL */
+    *tokp = "";
+
+  return NETRC_OK;
+}
+
+/*
+ * Reset parser for a new machine entry. Frees password and optionally login
+ * if it was not user-specified.
+ */
+static void netrc_new_machine(struct netrc_state *ns)
+{
+  ns->keyword = NONE;
+  ns->found = 0;
+  ns->our_login = FALSE;
+  curlx_safefree(ns->password);
+  if(!ns->specific_login)
+    curlx_safefree(ns->login);
+}
+
+/*
+ * Process a parsed token through the HOSTVALID state machine branch. This
+ * handles login/password values and keyword transitions for the matched host.
+ *
+ * Returns NETRC_OK or an error code.
+ */
+static NETRCcode netrc_hostvalid(struct netrc_state *ns, const char *tok)
+{
+  if(ns->keyword == LOGIN) {
+    if(ns->specific_login)
+      ns->our_login = !Curl_timestrcmp(ns->login, tok);
+    else {
+      ns->our_login = TRUE;
+      curlx_free(ns->login);
+      ns->login = curlx_strdup(tok);
+      if(!ns->login)
+        return NETRC_OUT_OF_MEMORY;
+    }
+    ns->found |= FOUND_LOGIN;
+    ns->keyword = NONE;
+  }
+  else if(ns->keyword == PASSWORD) {
+    curlx_free(ns->password);
+    ns->password = curlx_strdup(tok);
+    if(!ns->password)
+      return NETRC_OUT_OF_MEMORY;
+    ns->found |= FOUND_PASSWORD;
+    ns->keyword = NONE;
+  }
+  else if(curl_strequal("login", tok))
+    ns->keyword = LOGIN;
+  else if(curl_strequal("password", tok))
+    ns->keyword = PASSWORD;
+  else if(curl_strequal("machine", tok)) {
+    /* a new machine here */
+
+    if(ns->found & FOUND_PASSWORD &&
+      /* a password was provided for this host */
+
+       ((!ns->specific_login || ns->our_login) ||
+        /* either there was no specific login to search for, or this
+           is the specific one we wanted */
+        (ns->specific_login && !(ns->found & FOUND_LOGIN)))) {
+      /* or we look for a specific login, but that was not specified */
+
+      ns->done = TRUE;
+      return NETRC_OK;
+    }
+
+    ns->state = HOSTFOUND;
+    netrc_new_machine(ns);
+  }
+  else if(curl_strequal("default", tok)) {
+    ns->state = HOSTVALID;
+    ns->retcode = NETRC_OK;
+    netrc_new_machine(ns);
+  }
+  if((ns->found == (FOUND_PASSWORD | FOUND_LOGIN)) && ns->our_login)
+    ns->done = TRUE;
+  return NETRC_OK;
+}
+
+/*
+ * Process one parsed token through the netrc state
+ * machine. Updates the parser state in *ns.
+ * Returns NETRC_OK or an error code.
+ */
+static NETRCcode netrc_handle_token(struct netrc_state *ns,
+                                    const char *tok,
+                                    const char *host)
+{
+  switch(ns->state) {
+  case NOTHING:
+    if(curl_strequal("macdef", tok))
+      ns->state = MACDEF;
+    else if(curl_strequal("machine", tok)) {
+      ns->state = HOSTFOUND;
+      netrc_new_machine(ns);
+    }
+    else if(curl_strequal("default", tok)) {
+      ns->state = HOSTVALID;
+      ns->retcode = NETRC_OK;
+    }
+    break;
+  case MACDEF:
+    if(!*tok)
+      ns->state = NOTHING;
+    break;
+  case HOSTFOUND:
+    if(curl_strequal(host, tok)) {
+      ns->state = HOSTVALID;
+      ns->retcode = NETRC_OK;
+    }
+    else
+      ns->state = NOTHING;
+    break;
+  case HOSTVALID:
+    return netrc_hostvalid(ns, tok);
+  }
+  return NETRC_OK;
+}
+
+/*
+ * Finalize the parse result: fill in defaults and free
+ * resources on error.
+ */
+static NETRCcode netrc_finalize(struct netrc_state *ns,
+                                char **loginp,
+                                char **passwordp,
+                                struct store_netrc *store)
+{
+  NETRCcode retcode = ns->retcode;
+  if(!retcode) {
+    if(!ns->password && ns->our_login) {
+      /* success without a password, set a blank one */
+      ns->password = curlx_strdup("");
+      if(!ns->password)
+        retcode = NETRC_OUT_OF_MEMORY;
+    }
+    else if(!ns->login && !ns->password)
+      /* a default with no credentials */
+      retcode = NETRC_NO_MATCH;
+  }
+  if(!retcode) {
+    /* success */
+    if(!ns->specific_login)
+      *loginp = ns->login;
+
+    /* netrc_finalize() can return a password even when specific_login is set
+       but our_login is false (e.g., host matched but the requested login
+       never matched). See test 685. */
+    *passwordp = ns->password;
+  }
+  else {
+    curlx_dyn_free(&store->filebuf);
+    store->loaded = FALSE;
+    if(!ns->specific_login)
+      curlx_free(ns->login);
+    curlx_free(ns->password);
+  }
+  return retcode;
 }
 
 /*
@@ -108,23 +402,20 @@
  */
 static NETRCcode parsenetrc(struct store_netrc *store,
                             const char *host,
-                            char **loginp, /* might point to a username */
+                            char **loginp,
                             char **passwordp,
                             const char *netrcfile)
 {
-  NETRCcode retcode = NETRC_NO_MATCH;
-  char *login = *loginp;
-  char *password = NULL;
-  bool specific_login = !!login; /* points to something */
-  enum host_lookup_state state = NOTHING;
-  enum found_state keyword = NONE;
-  unsigned char found = 0; /* login + password found bits, as they can come in
-                              any order */
-  bool our_login = FALSE;  /* found our login name */
-  bool done = FALSE;
   const char *netrcbuffer;
   struct dynbuf token;
   struct dynbuf *filebuf = &store->filebuf;
+  struct netrc_state ns;
+
+  memset(&ns, 0, sizeof(ns));
+  ns.retcode = NETRC_NO_MATCH;
+  ns.login = *loginp;
+  ns.specific_login = !!ns.login;
+
   DEBUGASSERT(!*passwordp);
   curlx_dyn_init(&token, MAX_NETRC_TOKEN);
 
@@ -137,195 +428,32 @@
 
   netrcbuffer = curlx_dyn_ptr(filebuf);
 
-  while(!done) {
+  while(!ns.done) {
     const char *tok = netrcbuffer;
-    while(tok && !done) {
+    while(tok && !ns.done) {
       const char *tok_end;
-      bool quoted;
-      curlx_dyn_reset(&token);
-      curlx_str_passblanks(&tok);
-      /* tok is first non-space letter */
-      if(state == MACDEF) {
-        if((*tok == '\n') || (*tok == '\r'))
-          state = NOTHING; /* end of macro definition */
+      bool lineend;
+      NETRCcode ret;
+
+      ret = netrc_get_token(&tok, &tok_end, &token, &ns.state, &lineend);
+      if(ret) {
+        ns.retcode = ret;
+        goto out;
       }
-
-      if(!*tok || (*tok == '\n'))
-        /* end of line */
+      if(lineend)
         break;
 
-      /* leading double-quote means quoted string */
-      quoted = (*tok == '\"');
-
-      tok_end = tok;
-      if(!quoted) {
-        size_t len = 0;
-        CURLcode result;
-        while(*tok_end > ' ') {
-          tok_end++;
-          len++;
-        }
-        if(!len) {
-          retcode = NETRC_SYNTAX_ERROR;
-          goto out;
-        }
-        result = curlx_dyn_addn(&token, tok, len);
-        if(result) {
-          retcode = curl2netrc(result);
-          goto out;
-        }
+      ret = netrc_handle_token(&ns, tok, host);
+      if(ret) {
+        ns.retcode = ret;
+        goto out;
       }
-      else {
-        bool escape = FALSE;
-        bool endquote = FALSE;
-        tok_end++; /* pass the leading quote */
-        while(*tok_end) {
-          CURLcode result;
-          char s = *tok_end;
-          if(escape) {
-            escape = FALSE;
-            switch(s) {
-            case 'n':
-              s = '\n';
-              break;
-            case 'r':
-              s = '\r';
-              break;
-            case 't':
-              s = '\t';
-              break;
-            }
-          }
-          else if(s == '\\') {
-            escape = TRUE;
-            tok_end++;
-            continue;
-          }
-          else if(s == '\"') {
-            tok_end++; /* pass the ending quote */
-            endquote = TRUE;
-            break;
-          }
-          result = curlx_dyn_addn(&token, &s, 1);
-          if(result) {
-            retcode = curl2netrc(result);
-            goto out;
-          }
-          tok_end++;
-        }
-        if(escape || !endquote) {
-          /* bad syntax, get out */
-          retcode = NETRC_SYNTAX_ERROR;
-          goto out;
-        }
-      }
-
-      if(curlx_dyn_len(&token))
-        tok = curlx_dyn_ptr(&token);
-      else
-        /* since tok might actually be NULL for no content, set it to blank
-           to avoid having to deal with it being NULL */
-        tok = "";
-
-      switch(state) {
-      case NOTHING:
-        if(curl_strequal("macdef", tok))
-          /* Define a macro. A macro is defined with the specified name; its
-             contents begin with the next .netrc line and continue until a
-             null line (consecutive new-line characters) is encountered. */
-          state = MACDEF;
-        else if(curl_strequal("machine", tok)) {
-          /* the next tok is the machine name, this is in itself the delimiter
-             that starts the stuff entered for this machine, after this we
-             need to search for 'login' and 'password'. */
-          state = HOSTFOUND;
-          keyword = NONE;
-          found = 0;
-          our_login = FALSE;
-          Curl_safefree(password);
-          if(!specific_login)
-            Curl_safefree(login);
-        }
-        else if(curl_strequal("default", tok)) {
-          state = HOSTVALID;
-          retcode = NETRC_OK; /* we did find our host */
-        }
-        break;
-      case MACDEF:
-        if(!*tok)
-          state = NOTHING;
-        break;
-      case HOSTFOUND:
-        if(curl_strequal(host, tok)) {
-          /* and yes, this is our host! */
-          state = HOSTVALID;
-          retcode = NETRC_OK; /* we did find our host */
-        }
-        else
-          /* not our host */
-          state = NOTHING;
-        break;
-      case HOSTVALID:
-        /* we are now parsing sub-keywords concerning "our" host */
-        if(keyword == LOGIN) {
-          if(specific_login)
-            our_login = !Curl_timestrcmp(login, tok);
-          else {
-            our_login = TRUE;
-            curlx_free(login);
-            login = curlx_strdup(tok);
-            if(!login) {
-              retcode = NETRC_OUT_OF_MEMORY; /* allocation failed */
-              goto out;
-            }
-          }
-          found |= FOUND_LOGIN;
-          keyword = NONE;
-        }
-        else if(keyword == PASSWORD) {
-          curlx_free(password);
-          password = curlx_strdup(tok);
-          if(!password) {
-            retcode = NETRC_OUT_OF_MEMORY; /* allocation failed */
-            goto out;
-          }
-          if(!specific_login || our_login)
-            found |= FOUND_PASSWORD;
-          keyword = NONE;
-        }
-        else if(curl_strequal("login", tok))
-          keyword = LOGIN;
-        else if(curl_strequal("password", tok))
-          keyword = PASSWORD;
-        else if(curl_strequal("machine", tok)) {
-          /* a new machine here */
-          if(found & FOUND_PASSWORD) {
-            done = TRUE;
-            break;
-          }
-          state = HOSTFOUND;
-          keyword = NONE;
-          found = 0;
-          Curl_safefree(password);
-          if(!specific_login)
-            Curl_safefree(login);
-        }
-        else if(curl_strequal("default", tok)) {
-          state = HOSTVALID;
-          retcode = NETRC_OK; /* we did find our host */
-          Curl_safefree(password);
-          if(!specific_login)
-            Curl_safefree(login);
-        }
-        if((found == (FOUND_PASSWORD | FOUND_LOGIN)) && our_login) {
-          done = TRUE;
-          break;
-        }
-        break;
-      } /* switch (state) */
+      /* tok_end cannot point to a null byte here since lines are always
+         newline terminated */
+      DEBUGASSERT(*tok_end);
       tok = ++tok_end;
     }
-    if(!done) {
+    if(!ns.done) {
       const char *nl = NULL;
       if(tok)
         nl = strchr(tok, '\n');
@@ -338,32 +466,7 @@
 
 out:
   curlx_dyn_free(&token);
-  if(!retcode) {
-    if(!password && our_login) {
-      /* success without a password, set a blank one */
-      password = curlx_strdup("");
-      if(!password)
-        retcode = NETRC_OUT_OF_MEMORY; /* out of memory */
-    }
-    else if(!login && !password)
-      /* a default with no credentials */
-      retcode = NETRC_NO_MATCH;
-  }
-  if(!retcode) {
-    /* success */
-    if(!specific_login)
-      *loginp = login;
-    *passwordp = password;
-  }
-  else {
-    curlx_dyn_free(filebuf);
-    store->loaded = FALSE;
-    if(!specific_login)
-      curlx_free(login);
-    curlx_free(password);
-  }
-
-  return retcode;
+  return netrc_finalize(&ns, loginp, passwordp, store);
 }
 
 const char *Curl_netrc_strerror(NETRCcode ret)
diff --git a/Utilities/cmcurl/lib/netrc.h b/Utilities/cmcurl/lib/netrc.h
index 061e1e4..90318c2 100644
--- a/Utilities/cmcurl/lib/netrc.h
+++ b/Utilities/cmcurl/lib/netrc.h
@@ -58,7 +58,6 @@
  */
 #else
 /* disabled */
-#define Curl_parsenetrc(a, b, c, d, e, f) 1
 #define Curl_netrc_init(x)
 #define Curl_netrc_cleanup(x)
 #endif
diff --git a/Utilities/cmcurl/lib/noproxy.c b/Utilities/cmcurl/lib/noproxy.c
index ee03fd3..05c59a0 100644
--- a/Utilities/cmcurl/lib/noproxy.c
+++ b/Utilities/cmcurl/lib/noproxy.c
@@ -38,12 +38,17 @@
 #endif
 
 /*
- * Curl_cidr4_match() returns TRUE if the given IPv4 address is within the
+ * cidr4_match() returns TRUE if the given IPv4 address is within the
  * specified CIDR address range.
+ *
+ * @unittest 1614
  */
-UNITTEST bool Curl_cidr4_match(const char *ipv4,    /* 1.2.3.4 address */
-                               const char *network, /* 1.2.3.4 address */
-                               unsigned int bits)
+UNITTEST bool cidr4_match(const char *ipv4,    /* 1.2.3.4 address */
+                          const char *network, /* 1.2.3.4 address */
+                          unsigned int bits);
+UNITTEST bool cidr4_match(const char *ipv4,    /* 1.2.3.4 address */
+                          const char *network, /* 1.2.3.4 address */
+                          unsigned int bits)
 {
   unsigned int address = 0;
   unsigned int check = 0;
@@ -74,9 +79,11 @@
   return address == check;
 }
 
-UNITTEST bool Curl_cidr6_match(const char *ipv6,
-                               const char *network,
-                               unsigned int bits)
+/* @unittest 1614 */
+UNITTEST bool cidr6_match(const char *ipv6, const char *network,
+                          unsigned int bits);
+UNITTEST bool cidr6_match(const char *ipv6, const char *network,
+                          unsigned int bits)
 {
 #ifdef USE_IPV6
   unsigned int bytes;
@@ -170,9 +177,9 @@
     *slash = 0; /* null-terminate there */
   }
   if(type == TYPE_IPV6)
-    return Curl_cidr6_match(name, checkip, bits);
+    return cidr6_match(name, checkip, bits);
   else
-    return Curl_cidr4_match(name, checkip, bits);
+    return cidr4_match(name, checkip, bits);
 }
 
 /****************************************************************
diff --git a/Utilities/cmcurl/lib/noproxy.h b/Utilities/cmcurl/lib/noproxy.h
index 71ae7ea..e16c139 100644
--- a/Utilities/cmcurl/lib/noproxy.h
+++ b/Utilities/cmcurl/lib/noproxy.h
@@ -26,17 +26,6 @@
 #include "curl_setup.h"
 
 #ifndef CURL_DISABLE_PROXY
-
-#ifdef UNITTESTS
-
-UNITTEST bool Curl_cidr4_match(const char *ipv4,    /* 1.2.3.4 address */
-                               const char *network, /* 1.2.3.4 address */
-                               unsigned int bits);
-UNITTEST bool Curl_cidr6_match(const char *ipv6,
-                               const char *network,
-                               unsigned int bits);
-#endif
-
 bool Curl_check_noproxy(const char *name, const char *no_proxy);
 #endif
 
diff --git a/Utilities/cmcurl/lib/openldap.c b/Utilities/cmcurl/lib/openldap.c
index 95f7681..48bf5b7 100644
--- a/Utilities/cmcurl/lib/openldap.c
+++ b/Utilities/cmcurl/lib/openldap.c
@@ -444,18 +444,18 @@
     struct connectdata *conn = data->conn;
     if(conn) {
       struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN);
-      CURLcode err = CURLE_RECV_ERROR;
+      CURLcode result = CURLE_RECV_ERROR;
       size_t nread;
 
       if(!li) {
         SET_SOCKERRNO(SOCKEINVAL);
         return -1;
       }
-      err = (li->recv)(data, FIRSTSOCKET, buf, len, &nread);
-      if(err == CURLE_AGAIN) {
+      result = (li->recv)(data, FIRSTSOCKET, buf, len, &nread);
+      if(result == CURLE_AGAIN) {
         SET_SOCKERRNO(SOCKEWOULDBLOCK);
       }
-      ret = err ? -1 : (ber_slen_t)nread;
+      ret = result ? -1 : (ber_slen_t)nread;
     }
   }
   return ret;
@@ -470,18 +470,18 @@
     struct connectdata *conn = data->conn;
     if(conn) {
       struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN);
-      CURLcode err = CURLE_SEND_ERROR;
+      CURLcode result = CURLE_SEND_ERROR;
       size_t nwritten;
 
       if(!li) {
         SET_SOCKERRNO(SOCKEINVAL);
         return -1;
       }
-      err = (li->send)(data, FIRSTSOCKET, buf, len, FALSE, &nwritten);
-      if(err == CURLE_AGAIN) {
+      result = (li->send)(data, FIRSTSOCKET, buf, len, FALSE, &nwritten);
+      if(result == CURLE_AGAIN) {
         SET_SOCKERRNO(SOCKEWOULDBLOCK);
       }
-      ret = err ? -1 : (ber_slen_t)nwritten;
+      ret = result ? -1 : (ber_slen_t)nwritten;
     }
   }
   return ret;
@@ -614,11 +614,10 @@
   if(result)
     goto out;
 
-  hosturl = curl_maprintf("%s://%s%s%s:%d",
+  hosturl = curl_maprintf("%s://%s:%d",
                           conn->scheme->name,
-                          conn->bits.ipv6_ip ? "[" : "",
-                          conn->host.name,
-                          conn->bits.ipv6_ip ? "]" : "",
+                          (data->state.up.hostname[0] == '[') ?
+                          data->state.up.hostname : conn->host.name,
                           conn->remote_port);
   if(!hosturl) {
     result = CURLE_OUT_OF_MEMORY;
@@ -694,13 +693,19 @@
 static CURLcode oldap_state_mechs_resp(struct Curl_easy *data,
                                        LDAPMessage *msg, int code)
 {
-  struct connectdata *conn = data->conn;
-  struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN);
+  struct connectdata *conn;
+  struct ldapconninfo *li;
   int rc;
   BerElement *ber = NULL;
   CURLcode result = CURLE_OK;
   struct berval bv, *bvals;
 
+  if(!data)
+    return CURLE_FAILED_INIT;
+
+  conn = data->conn;
+  li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN);
+
   if(!li)
     return CURLE_FAILED_INIT;
   switch(ldap_msgtype(msg)) {
@@ -1276,7 +1281,7 @@
   oldap_disconnect,                     /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
diff --git a/Utilities/cmcurl/lib/parsedate.c b/Utilities/cmcurl/lib/parsedate.c
index 7efedec..ce358bd 100644
--- a/Utilities/cmcurl/lib/parsedate.c
+++ b/Utilities/cmcurl/lib/parsedate.c
@@ -25,6 +25,7 @@
 
 #include "parsedate.h"
 #include "curlx/strparse.h"
+#include "curlx/strcopy.h"
 
 /*
   A brief summary of the date string formats this parser groks:
@@ -98,9 +99,7 @@
 #ifndef CURL_DISABLE_PARSEDATE
 
 #define PARSEDATE_LATER  1
-#if defined(HAVE_TIME_T_UNSIGNED) || (SIZEOF_TIME_T < 5)
 #define PARSEDATE_SOONER 2
-#endif
 
 static const char * const weekday[] = {
   "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
@@ -108,89 +107,83 @@
 
 struct tzinfo {
   char name[5];
-  int offset; /* +/- in minutes */
+  int16_t offset; /* +/- in minutes */
 };
 
-/* Here's a bunch of frequently used time zone names. These were supported
-   by the old getdate parser. */
 #define tDAYZONE (-60)         /* offset for daylight savings time */
+
+/* alpha-sorted list of time zones */
 static const struct tzinfo tz[] = {
-  { "GMT",     0 },            /* Greenwich Mean */
-  { "UT",      0 },            /* Universal Time */
-  { "UTC",     0 },            /* Universal (Coordinated) */
-  { "WET",     0 },            /* Western European */
-  { "BST",     0 + tDAYZONE }, /* British Summer */
-  { "WAT",    60 },            /* West Africa */
-  { "AST",   240 },            /* Atlantic Standard */
+  { "A", -1 * 60 },            /* Alpha */
   { "ADT",   240 + tDAYZONE }, /* Atlantic Daylight */
-  { "EST",   300 },            /* Eastern Standard */
-  { "EDT",   300 + tDAYZONE }, /* Eastern Daylight */
-  { "CST",   360 },            /* Central Standard */
-  { "CDT",   360 + tDAYZONE }, /* Central Daylight */
-  { "MST",   420 },            /* Mountain Standard */
-  { "MDT",   420 + tDAYZONE }, /* Mountain Daylight */
-  { "PST",   480 },            /* Pacific Standard */
-  { "PDT",   480 + tDAYZONE }, /* Pacific Daylight */
-  { "YST",   540 },            /* Yukon Standard */
-  { "YDT",   540 + tDAYZONE }, /* Yukon Daylight */
-  { "HST",   600 },            /* Hawaii Standard */
-  { "HDT",   600 + tDAYZONE }, /* Hawaii Daylight */
-  { "CAT",   600 },            /* Central Alaska */
   { "AHST",  600 },            /* Alaska-Hawaii Standard */
-  { "NT",    660 },            /* Nome */ /* spellchecker:disable-line */
-  { "IDLW",  720 },            /* International Date Line West */
+  { "AST",   240 },            /* Atlantic Standard */
+  { "B", -2 * 60 },            /* Bravo */
+  { "BST",     0 + tDAYZONE }, /* British Summer */
+  { "C", -3 * 60 },            /* Charlie */
+  { "CAT",   600 },            /* Central Alaska */
+  { "CCT",  -480 },            /* China Coast, USSR Zone 7 */
+  { "CDT",   360 + tDAYZONE }, /* Central Daylight */
+  { "CEST",  -60 + tDAYZONE }, /* Central European Summer */
   { "CET",   -60 },            /* Central European */
+  { "CST",   360 },            /* Central Standard */
+  { "D", -4 * 60 },            /* Delta */
+  { "E", -5 * 60 },            /* Echo */
+  { "EADT", -600 + tDAYZONE }, /* Eastern Australian Daylight */
+  { "EAST", -600 },            /* Eastern Australian Standard */
+  { "EDT",   300 + tDAYZONE }, /* Eastern Daylight */
+  { "EET",  -120 },            /* Eastern Europe, USSR Zone 1 */
+  { "EST",   300 },            /* Eastern Standard */
+  { "F", -6 * 60 },            /* Foxtrot */
+  { "FST",   -60 + tDAYZONE }, /* French Summer */
+  { "FWT",   -60 },            /* French Winter */
+  { "G", -7 * 60 },            /* Golf */
+  { "GMT",     0 },            /* Greenwich Mean */
+  { "GST",  -600 },            /* Guam Standard, USSR Zone 9 */
+  { "H", -8 * 60 },            /* Hotel */
+  { "HDT",   600 + tDAYZONE }, /* Hawaii Daylight */
+  { "HST",   600 },            /* Hawaii Standard */
+  { "I", -9 * 60 },            /* India */
+  { "IDLE", -720 },            /* International Date Line East */
+  { "IDLW",  720 },            /* International Date Line West */
+  { "JST",  -540 },            /* Japan Standard, USSR Zone 8 */
+  { "K", -10 * 60 },           /* Kilo */
+  { "L", -11 * 60 },           /* Lima */
+  { "M", -12 * 60 },           /* Mike */
+  { "MDT",   420 + tDAYZONE }, /* Mountain Daylight */
+  { "MEST",  -60 + tDAYZONE }, /* Middle European Summer */
+  { "MESZ",  -60 + tDAYZONE }, /* Middle European Summer */
   { "MET",   -60 },            /* Middle European */
   { "MEWT",  -60 },            /* Middle European Winter */
-  { "MEST",  -60 + tDAYZONE }, /* Middle European Summer */
-  { "CEST",  -60 + tDAYZONE }, /* Central European Summer */
-  { "MESZ",  -60 + tDAYZONE }, /* Middle European Summer */
-  { "FWT",   -60 },            /* French Winter */
-  { "FST",   -60 + tDAYZONE }, /* French Summer */
-  { "EET",  -120 },            /* Eastern Europe, USSR Zone 1 */
+  { "MST",   420 },            /* Mountain Standard */
+  { "N",      60 },            /* November */
+  { "NT",    660 },            /* Nome */ /* spellchecker:disable-line */
+  { "NZDT", -720 + tDAYZONE }, /* New Zealand Daylight */
+  { "NZST", -720 },            /* New Zealand Standard */
+  { "NZT",  -720 },            /* New Zealand */
+  { "O",  2 * 60 },            /* Oscar */
+  { "P",  3 * 60 },            /* Papa */
+  { "PDT",   480 + tDAYZONE }, /* Pacific Daylight */
+  { "PST",   480 },            /* Pacific Standard */
+  { "Q",  4 * 60 },            /* Quebec */
+  { "R",  5 * 60 },            /* Romeo */
+  { "S",  6 * 60 },            /* Sierra */
+  { "T",  7 * 60 },            /* Tango */
+  { "U",  8 * 60 },            /* Uniform */
+  { "UT",      0 },            /* Universal Time */
+  { "UTC",     0 },            /* Universal (Coordinated) */
+  { "V",  9 * 60 },            /* Victor */
+  { "W", 10 * 60 },            /* Whiskey */
+  { "WADT", -420 + tDAYZONE }, /* West Australian Daylight */
   { "WAST", -420 }, /* spellchecker:disable-line */
                                /* West Australian Standard */
-  { "WADT", -420 + tDAYZONE }, /* West Australian Daylight */
-  { "CCT",  -480 },            /* China Coast, USSR Zone 7 */
-  { "JST",  -540 },            /* Japan Standard, USSR Zone 8 */
-  { "EAST", -600 },            /* Eastern Australian Standard */
-  { "EADT", -600 + tDAYZONE }, /* Eastern Australian Daylight */
-  { "GST",  -600 },            /* Guam Standard, USSR Zone 9 */
-  { "NZT",  -720 },            /* New Zealand */
-  { "NZST", -720 },            /* New Zealand Standard */
-  { "NZDT", -720 + tDAYZONE }, /* New Zealand Daylight */
-  { "IDLE", -720 },            /* International Date Line East */
-  /* Next up: Military timezone names. RFC822 allowed these, but (as noted in
-     RFC 1123) had their signs wrong. Here we use the correct signs to match
-     actual military usage.
-   */
-  { "A",   1 * 60 },           /* Alpha */
-  { "B",   2 * 60 },           /* Bravo */
-  { "C",   3 * 60 },           /* Charlie */
-  { "D",   4 * 60 },           /* Delta */
-  { "E",   5 * 60 },           /* Echo */
-  { "F",   6 * 60 },           /* Foxtrot */
-  { "G",   7 * 60 },           /* Golf */
-  { "H",   8 * 60 },           /* Hotel */
-  { "I",   9 * 60 },           /* India */
-  /* "J", Juliet is not used as a timezone, to indicate the observer's local
-     time */
-  { "K",  10 * 60 },           /* Kilo */
-  { "L",  11 * 60 },           /* Lima */
-  { "M",  12 * 60 },           /* Mike */
-  { "N",  -1 * 60 },           /* November */
-  { "O",  -2 * 60 },           /* Oscar */
-  { "P",  -3 * 60 },           /* Papa */
-  { "Q",  -4 * 60 },           /* Quebec */
-  { "R",  -5 * 60 },           /* Romeo */
-  { "S",  -6 * 60 },           /* Sierra */
-  { "T",  -7 * 60 },           /* Tango */
-  { "U",  -8 * 60 },           /* Uniform */
-  { "V",  -9 * 60 },           /* Victor */
-  { "W", -10 * 60 },           /* Whiskey */
-  { "X", -11 * 60 },           /* X-ray */
-  { "Y", -12 * 60 },           /* Yankee */
-  { "Z", 0 },                  /* Zulu, zero meridian, a.k.a. UTC */
+  { "WAT",    60 },            /* West Africa */
+  { "WET",     0 },            /* Western European */
+  { "X", 11 * 60 },            /* X-ray */
+  { "Y", 12 * 60 },            /* Yankee */
+  { "YDT",   540 + tDAYZONE }, /* Yukon Daylight */
+  { "YST",   540 },            /* Yukon Standard */
+  { "Z",       0 },            /* Zulu, zero meridian, a.k.a. UTC */
 };
 
 /* returns:
@@ -233,22 +226,24 @@
   return -1; /* return the offset or -1, no real offset is -1 */
 }
 
-/* return the time zone offset between GMT and the input one, in number
-   of seconds or -1 if the timezone was not found/legal */
+static int tzcompare(const void *m1, const void *m2)
+{
+  const struct tzinfo *tz1 = m1;
+  const struct tzinfo *tz2 = m2;
+  return strcmp(tz1->name, tz2->name);
+}
 
+/* return the time zone offset between GMT and the input one, in number of
+   seconds or -1 if the timezone was not found/legal */
 static int checktz(const char *check, size_t len)
 {
-  unsigned int i;
-  const struct tzinfo *what = tz;
-  if(len > 4) /* longer than any valid timezone */
-    return -1;
-
-  for(i = 0; i < CURL_ARRAYSIZE(tz); i++) {
-    size_t ilen = strlen(what->name);
-    if((ilen == len) &&
-       curl_strnequal(check, what->name, len))
+  if(len <= 4) {
+    const struct tzinfo *what;
+    struct tzinfo find;
+    curlx_strcopy(find.name, sizeof(find.name), check, len);
+    what = bsearch(&find, tz, CURL_ARRAYSIZE(tz), sizeof(tz[0]), tzcompare);
+    if(what)
       return what->offset * 60;
-    what++;
   }
   return -1;
 }
@@ -260,29 +255,42 @@
     (*date)++;
 }
 
+/* each field is exactly -1 when unknown */
+struct when {
+  int wday;  /* day of the week, 0-6 (mon-sun) */
+  int mon;   /* month of the year, 0-11 */
+  int mday;  /* day of month, 1 - 31 */
+  int hour;  /* hour of day, 0 - 23 */
+  int min;   /* minute of hour, 0 - 59 */
+  int sec;   /* second of minute, 0 - 60 (leap second) */
+  int year;  /* year, >= 1583 */
+  int tzoff; /* time zone offset in seconds */
+};
+
 enum assume {
   DATE_MDAY,
   DATE_YEAR,
   DATE_TIME
 };
 
+/* (1969 / 4) - (1969 / 100) + (1969 / 400) = 492 - 19 + 4 = 477 */
+#define LEAP_DAYS_BEFORE_1969 477
+
 /*
  * time2epoch: time stamp to seconds since epoch in GMT time zone. Similar to
  * mktime but for GMT only.
  */
-static time_t time2epoch(int sec, int min, int hour,
-                         int mday, int mon, int year)
+static curl_off_t time2epoch(struct when *w)
 {
-  static const int month_days_cumulative[12] = {
+  static const int cumulative_days[12] = {
     0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
   };
-  int leap_days = year - (mon <= 1);
-  leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400)
-               - (1969 / 4) + (1969 / 100) - (1969 / 400));
-  return ((((((((time_t)(year - 1970) * 365)
-    + leap_days + month_days_cumulative[mon] + mday - 1) * 24)
-    + hour) * 60)
-    + min) * 60) + sec;
+  int y = w->year - (w->mon <= 1);
+  int leap_days = (y / 4) - (y / 100) + (y / 400) - LEAP_DAYS_BEFORE_1969;
+  curl_off_t days = (curl_off_t)(w->year - 1970) * 365 + leap_days +
+    cumulative_days[w->mon] + w->mday - 1;
+
+  return (((days * 24 + w->hour) * 60 + w->min) * 60) + w->sec;
 }
 
 /* Returns the value of a single-digit or two-digit decimal number, return
@@ -300,8 +308,7 @@
 }
 
 /* HH:MM:SS or HH:MM and accept single-digits too */
-static bool match_time(const char *date,
-                       int *h, int *m, int *s, char **endp)
+static bool match_time(const char *date, struct when *w, char **endp)
 {
   const char *p;
   int hh, mm, ss = 0;
@@ -324,9 +331,9 @@
   }
   return FALSE; /* not a time string */
 match:
-  *h = hh;
-  *m = mm;
-  *s = ss;
+  w->hour = hh;
+  w->min = mm;
+  w->sec = ss;
   *endp = (char *)CURL_UNCONST(p);
   return TRUE;
 }
@@ -345,209 +352,221 @@
 /* Wednesday is the longest name this parser knows about */
 #define NAME_LEN 12
 
+static void initwhen(struct when *w)
+{
+  w->wday = w->mon = w->mday = w->hour = w->min = w->sec = w->year = w->tzoff =
+    -1;
+}
+
+static int datestring(const char **datep, struct when *w)
+{
+  /* a name coming up */
+  size_t len = 0;
+  const char *p = *datep;
+  bool found = FALSE;
+  while(ISALPHA(*p) && (len < NAME_LEN)) {
+    p++;
+    len++;
+  }
+
+  if(len != NAME_LEN) {
+    if(w->wday == -1) {
+      w->wday = checkday(*datep, len);
+      if(w->wday != -1)
+        found = TRUE;
+    }
+    if(!found && (w->mon == -1)) {
+      w->mon = checkmonth(*datep, len);
+      if(w->mon != -1)
+        found = TRUE;
+    }
+
+    if(!found && (w->tzoff == -1)) {
+      /* this must be a time zone string */
+      w->tzoff = checktz(*datep, len);
+      if(w->tzoff != -1)
+        found = TRUE;
+    }
+  }
+  if(!found)
+    return PARSEDATE_FAIL; /* bad string */
+
+  *datep += len;
+  return PARSEDATE_OK;
+}
+
+static int datenum(const char *indate, const char **datep, struct when *w,
+                   enum assume *dignextp)
+{
+  /* a digit */
+  unsigned int val;
+  char *end;
+  const char *date = *datep;
+  enum assume dignext = *dignextp;
+
+  if((w->sec == -1) && match_time(date, w, &end)) {
+    /* time stamp */
+    date = end;
+  }
+  else {
+    bool found = FALSE;
+    curl_off_t lval;
+    int num_digits = 0;
+    const char *p = *datep;
+    if(curlx_str_number(&p, &lval, 99999999))
+      return PARSEDATE_FAIL;
+
+    /* we know num_digits cannot be larger than 8 */
+    num_digits = (int)(p - *datep);
+    val = (unsigned int)lval;
+
+    if((w->tzoff == -1) &&
+       (num_digits == 4) &&
+       (val <= 1400) &&
+       (indate < date) &&
+       (date[-1] == '+' || date[-1] == '-')) {
+      /* four digits and a value less than or equal to 1400 (to take into
+         account all sorts of funny time zone diffs) and it is preceded
+         with a plus or minus. This is a time zone indication. 1400 is
+         picked since +1300 is frequently used and +1400 is mentioned as
+         an edge number in the document "ISO C 200X Proposal: Timezone
+         Functions" at http://david.tribble.com/text/c0xtimezone.html If
+         anyone has a more authoritative source for the exact maximum time
+         zone offsets, please speak up! */
+      found = TRUE;
+      w->tzoff = ((val / 100 * 60) + (val % 100)) * 60;
+
+      /* the + and - prefix indicates the local time compared to GMT,
+         this we need their reversed math to get what we want */
+      w->tzoff = date[-1] == '+' ? -w->tzoff : w->tzoff;
+    }
+
+    else if((num_digits == 8) && (w->year == -1) &&
+            (w->mon == -1) && (w->mday == -1)) {
+      /* 8 digits, no year, month or day yet. This is YYYYMMDD */
+      found = TRUE;
+      w->year = val / 10000;
+      w->mon = ((val % 10000) / 100) - 1; /* month is 0 - 11 */
+      w->mday = val % 100;
+    }
+
+    if(!found && (dignext == DATE_MDAY) && (w->mday == -1)) {
+      if((val > 0) && (val < 32)) {
+        w->mday = val;
+        found = TRUE;
+      }
+      dignext = DATE_YEAR;
+    }
+
+    if(!found && (dignext == DATE_YEAR) && (w->year == -1)) {
+      w->year = val;
+      found = TRUE;
+      if(w->year < 100) {
+        if(w->year > 70)
+          w->year += 1900;
+        else
+          w->year += 2000;
+      }
+      if(w->mday == -1)
+        dignext = DATE_MDAY;
+    }
+
+    if(!found)
+      return PARSEDATE_FAIL;
+
+    date = p;
+  }
+  *datep = date;
+  *dignextp = dignext;
+  return PARSEDATE_OK;
+}
+
+static int datecheck(struct when *w)
+{
+  if(w->sec == -1)
+    w->sec = w->min = w->hour = 0; /* no time, make it zero */
+
+  if((w->mday == -1) || (w->mon == -1) || (w->year == -1))
+    /* lacks vital info, fail */
+    return PARSEDATE_FAIL;
+
+  /* The Gregorian calendar was introduced 1582 */
+  else if(w->year < 1583)
+    return PARSEDATE_FAIL;
+
+  else if((w->mday > 31) || (w->mon > 11) || (w->hour > 23) ||
+          (w->min > 59) || (w->sec > 60))
+    return PARSEDATE_FAIL; /* clearly an illegal date */
+
+  return PARSEDATE_OK;
+}
+
+static void tzadjust(curl_off_t *tp, struct when *w)
+{
+  if(w->tzoff == -1) /* unknown tz means no offset */
+    w->tzoff = 0;
+
+  /* Add the time zone diff between local time zone and GMT. */
+  if((w->tzoff > 0) && (*tp > (curl_off_t)(CURL_OFF_T_MAX - w->tzoff)))
+    *tp = CURL_OFF_T_MAX;
+  else
+    *tp += w->tzoff;
+  /* this needs no minimum check since we require a year > 1582 */
+}
+
+static int mktimet(curl_off_t seconds, time_t *output)
+{
+#if SIZEOF_TIME_T < 5
+  if(seconds > TIME_T_MAX) {
+    *output = TIME_T_MAX;
+    return PARSEDATE_LATER;
+  }
+  else if(seconds < TIME_T_MIN) {
+    *output = TIME_T_MIN;
+    return PARSEDATE_SOONER;
+  }
+#elif defined(HAVE_TIME_T_UNSIGNED)
+  if(seconds < 0) {
+    *output = 0;
+    return PARSEDATE_SOONER;
+  }
+#endif
+  *output = (time_t)seconds;
+  return PARSEDATE_OK;
+}
+
 static int parsedate(const char *date, time_t *output)
 {
-  time_t t = 0;
-  int wdaynum = -1;  /* day of the week number, 0-6 (mon-sun) */
-  int monnum = -1;   /* month of the year number, 0-11 */
-  int mdaynum = -1;  /* day of month, 1 - 31 */
-  int hournum = -1;
-  int minnum = -1;
-  int secnum = -1;
-  int yearnum = -1;
-  int tzoff = -1;
+  curl_off_t seconds = 0;
   enum assume dignext = DATE_MDAY;
   const char *indate = date; /* save the original pointer */
   int part = 0; /* max 6 parts */
+  int rc = 0;
+  struct when w;
+  initwhen(&w);
 
   while(*date && (part < 6)) {
-    bool found = FALSE;
-
     skip(&date);
 
-    if(ISALPHA(*date)) {
-      /* a name coming up */
-      size_t len = 0;
-      const char *p = date;
-      while(ISALPHA(*p) && (len < NAME_LEN)) {
-        p++;
-        len++;
-      }
-
-      if(len != NAME_LEN) {
-        if(wdaynum == -1) {
-          wdaynum = checkday(date, len);
-          if(wdaynum != -1)
-            found = TRUE;
-        }
-        if(!found && (monnum == -1)) {
-          monnum = checkmonth(date, len);
-          if(monnum != -1)
-            found = TRUE;
-        }
-
-        if(!found && (tzoff == -1)) {
-          /* this must be a time zone string */
-          tzoff = checktz(date, len);
-          if(tzoff != -1)
-            found = TRUE;
-        }
-      }
-      if(!found)
-        return PARSEDATE_FAIL; /* bad string */
-
-      date += len;
-    }
-    else if(ISDIGIT(*date)) {
-      /* a digit */
-      unsigned int val;
-      char *end;
-      if((secnum == -1) &&
-         match_time(date, &hournum, &minnum, &secnum, &end)) {
-        /* time stamp */
-        date = end;
-      }
-      else {
-        curl_off_t lval;
-        int num_digits = 0;
-        const char *p = date;
-        if(curlx_str_number(&p, &lval, 99999999))
-          return PARSEDATE_FAIL;
-
-        /* we know num_digits cannot be larger than 8 */
-        num_digits = (int)(p - date);
-        val = (unsigned int)lval;
-
-        if((tzoff == -1) &&
-           (num_digits == 4) &&
-           (val <= 1400) &&
-           (indate < date) &&
-           (date[-1] == '+' || date[-1] == '-')) {
-          /* four digits and a value less than or equal to 1400 (to take into
-             account all sorts of funny time zone diffs) and it is preceded
-             with a plus or minus. This is a time zone indication. 1400 is
-             picked since +1300 is frequently used and +1400 is mentioned as
-             an edge number in the document "ISO C 200X Proposal: Timezone
-             Functions" at http://david.tribble.com/text/c0xtimezone.html If
-             anyone has a more authoritative source for the exact maximum time
-             zone offsets, please speak up! */
-          found = TRUE;
-          tzoff = ((val / 100 * 60) + (val % 100)) * 60;
-
-          /* the + and - prefix indicates the local time compared to GMT,
-             this we need their reversed math to get what we want */
-          tzoff = date[-1] == '+' ? -tzoff : tzoff;
-        }
-
-        else if((num_digits == 8) &&
-                (yearnum == -1) &&
-                (monnum == -1) &&
-                (mdaynum == -1)) {
-          /* 8 digits, no year, month or day yet. This is YYYYMMDD */
-          found = TRUE;
-          yearnum = val / 10000;
-          monnum = ((val % 10000) / 100) - 1; /* month is 0 - 11 */
-          mdaynum = val % 100;
-        }
-
-        if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) {
-          if((val > 0) && (val < 32)) {
-            mdaynum = val;
-            found = TRUE;
-          }
-          dignext = DATE_YEAR;
-        }
-
-        if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) {
-          yearnum = val;
-          found = TRUE;
-          if(yearnum < 100) {
-            if(yearnum > 70)
-              yearnum += 1900;
-            else
-              yearnum += 2000;
-          }
-          if(mdaynum == -1)
-            dignext = DATE_MDAY;
-        }
-
-        if(!found)
-          return PARSEDATE_FAIL;
-
-        date = p;
-      }
-    }
+    if(ISALPHA(*date))
+      rc = datestring(&date, &w);
+    else if(ISDIGIT(*date))
+      rc = datenum(indate, &date, &w, &dignext);
+    if(rc)
+      return rc;
 
     part++;
   }
 
-  if(secnum == -1)
-    secnum = minnum = hournum = 0; /* no time, make it zero */
+  rc = datecheck(&w);
+  if(rc)
+    return rc;
 
-  if((mdaynum == -1) ||
-     (monnum == -1) ||
-     (yearnum == -1))
-    /* lacks vital info, fail */
-    return PARSEDATE_FAIL;
+  seconds = time2epoch(&w); /* get number of seconds */
+  tzadjust(&seconds, &w); /* handle the time zone offset */
+  rc = mktimet(seconds, output); /* squeeze seconds into a time_t */
 
-#ifdef HAVE_TIME_T_UNSIGNED
-  if(yearnum < 1970) {
-    /* only positive numbers cannot return earlier */
-    *output = TIME_T_MIN;
-    return PARSEDATE_SOONER;
-  }
-#endif
-
-#if (SIZEOF_TIME_T < 5)
-
-#ifdef HAVE_TIME_T_UNSIGNED
-  /* an unsigned 32-bit time_t can only hold dates to 2106 */
-  if(yearnum > 2105) {
-    *output = TIME_T_MAX;
-    return PARSEDATE_LATER;
-  }
-#else
-  /* a signed 32-bit time_t can only hold dates to the beginning of 2038 */
-  if(yearnum > 2037) {
-    *output = TIME_T_MAX;
-    return PARSEDATE_LATER;
-  }
-  if(yearnum < 1903) {
-    *output = TIME_T_MIN;
-    return PARSEDATE_SOONER;
-  }
-#endif
-
-#else
-  /* The Gregorian calendar was introduced 1582 */
-  if(yearnum < 1583)
-    return PARSEDATE_FAIL;
-#endif
-
-  if((mdaynum > 31) || (monnum > 11) ||
-     (hournum > 23) || (minnum > 59) || (secnum > 60))
-    return PARSEDATE_FAIL; /* clearly an illegal date */
-
-  /* time2epoch() returns a time_t. time_t is often 32 bits, sometimes even on
-     architectures that feature a 64 bits 'long' but ultimately time_t is the
-     correct data type to use.
-  */
-  t = time2epoch(secnum, minnum, hournum, mdaynum, monnum, yearnum);
-
-  /* Add the time zone diff between local time zone and GMT. */
-  if(tzoff == -1)
-    tzoff = 0;
-
-  if((tzoff > 0) && (t > (time_t)(TIME_T_MAX - tzoff))) {
-    *output = TIME_T_MAX;
-    return PARSEDATE_LATER; /* time_t overflow */
-  }
-
-  t += tzoff;
-
-  *output = t;
-
-  return PARSEDATE_OK;
+  return rc;
 }
 #else
 /* disabled */
diff --git a/Utilities/cmcurl/lib/pingpong.c b/Utilities/cmcurl/lib/pingpong.c
index 4ede0a5..44e4244 100644
--- a/Utilities/cmcurl/lib/pingpong.c
+++ b/Utilities/cmcurl/lib/pingpong.c
@@ -39,29 +39,21 @@
 #include "select.h"
 #include "progress.h"
 
-/* Returns timeout in ms. 0 or negative number means the timeout has already
-   triggered */
-timediff_t Curl_pp_state_timeout(struct Curl_easy *data,
-                                 struct pingpong *pp)
+timediff_t Curl_pp_state_timeleft_ms(struct Curl_easy *data,
+                                     struct pingpong *pp)
 {
-  timediff_t timeout_ms, xfer_timeout_ms;
-  timediff_t response_time = data->set.server_response_timeout ?
-    data->set.server_response_timeout : RESP_TIMEOUT;
+  timediff_t xfer_remain_ms;
+  timediff_t remain_ms = data->set.server_response_timeout ?
+    data->set.server_response_timeout : PINGPONG_TIMEOUT_MS;
 
-  /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine
-     remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is
-     supposed to govern the response for any given server response, not for
-     the time from connect to the given server response. */
-
-  /* Without a requested timeout, we only wait 'response_time' seconds for the
-     full response to arrive before we bail out */
-  timeout_ms = response_time -
-               curlx_ptimediff_ms(Curl_pgrs_now(data), &pp->response);
-  /* transfer timeout can be 0, which means no timeout applies */
-  xfer_timeout_ms = Curl_timeleft_ms(data);
-  if(xfer_timeout_ms && (xfer_timeout_ms < timeout_ms))
-    return xfer_timeout_ms;
-  return timeout_ms;
+  /* If the overall transfer has less time remaining than pingpong
+   * has otherwise for the state, return that. */
+  remain_ms -= curlx_ptimediff_ms(Curl_pgrs_now(data), &pp->response);
+  /* transfer remaining time is 0, when it has no timeout. */
+  xfer_remain_ms = Curl_timeleft_ms(data);
+  if(xfer_remain_ms)
+    return CURLMIN(remain_ms, xfer_remain_ms);
+  return remain_ms;
 }
 
 /*
@@ -75,7 +67,7 @@
   curl_socket_t sock = conn->sock[FIRSTSOCKET];
   int rc;
   timediff_t interval_ms;
-  timediff_t timeout_ms = Curl_pp_state_timeout(data, pp);
+  timediff_t timeout_ms = Curl_pp_state_timeleft_ms(data, pp);
   CURLcode result = CURLE_OK;
 
   if(timeout_ms <= 0) {
diff --git a/Utilities/cmcurl/lib/pingpong.h b/Utilities/cmcurl/lib/pingpong.h
index 33d2a92..864f2c6 100644
--- a/Utilities/cmcurl/lib/pingpong.h
+++ b/Utilities/cmcurl/lib/pingpong.h
@@ -70,6 +70,10 @@
                          read */
 };
 
+/* Default pingpong response timeout in milliseconds, unless a transfer
+ * has CURLOPT_SERVER_RESPONSE_TIMEOUT(_MS) set. */
+#define PINGPONG_TIMEOUT_MS      (60 * 1000)
+
 #define PINGPONG_SETUP(pp, s, e) \
   do {                           \
     (pp)->statemachine = s;      \
@@ -88,10 +92,10 @@
 /* initialize stuff to prepare for reading a fresh new response */
 void Curl_pp_init(struct pingpong *pp, const struct curltime *pnow);
 
-/* Returns timeout in ms. 0 or negative number means the timeout has already
-   triggered */
-timediff_t Curl_pp_state_timeout(struct Curl_easy *data,
-                                 struct pingpong *pp);
+/* Returns time remaining in ms. 0 or negative number means the
+  timeout has already triggered */
+timediff_t Curl_pp_state_timeleft_ms(struct Curl_easy *data,
+                                     struct pingpong *pp);
 
 /***********************************************************************
  *
diff --git a/Utilities/cmcurl/lib/pop3.c b/Utilities/cmcurl/lib/pop3.c
index de704d1..317c04b 100644
--- a/Utilities/cmcurl/lib/pop3.c
+++ b/Utilities/cmcurl/lib/pop3.c
@@ -1075,7 +1075,6 @@
 {
   /* This code could be made into a special function in the handler struct */
   CURLcode result = CURLE_OK;
-  struct SingleRequest *k = &data->req;
   struct connectdata *conn = data->conn;
   struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN);
   bool strip_dot = FALSE;
@@ -1185,7 +1184,7 @@
     message as per RFC-1939, sect. 3 */
     result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, 2);
 
-    k->keepon &= ~KEEP_RECV;
+    CURL_REQ_CLEAR_RECV(data);
     pop3c->eob = 0;
 
     return result;
@@ -1230,7 +1229,7 @@
      when there is no body to return. */
   pop3c->eob = 2;
 
-  /* But since this initial CR LF pair is not part of the actual body, we set
+  /* Since this initial CR LF pair is not part of the actual body, we set
      the strip counter here so that these bytes will not be delivered. */
   pop3c->strip = 2;
 
@@ -1480,8 +1479,8 @@
   }
 
   /* Cleanup our per-request based variables */
-  Curl_safefree(pop3->id);
-  Curl_safefree(pop3->custom);
+  curlx_safefree(pop3->id);
+  curlx_safefree(pop3->custom);
 
   /* Clear the transfer mode for the next request */
   pop3->transfer = PPTRANSFER_BODY;
@@ -1628,7 +1627,7 @@
   Curl_pp_disconnect(&pop3c->pp);
 
   /* Cleanup our connection based variables */
-  Curl_safefree(pop3c->apoptimestamp);
+  curlx_safefree(pop3c->apoptimestamp);
 
   return CURLE_OK;
 }
@@ -1656,8 +1655,8 @@
   (void)klen;
   DEBUGASSERT(pop3);
   /* Cleanup our per-request based variables */
-  Curl_safefree(pop3->id);
-  Curl_safefree(pop3->custom);
+  curlx_safefree(pop3->id);
+  curlx_safefree(pop3->custom);
   curlx_free(pop3);
 }
 
@@ -1668,7 +1667,7 @@
   (void)klen;
   DEBUGASSERT(pop3c);
   Curl_pp_disconnect(&pop3c->pp);
-  Curl_safefree(pop3c->apoptimestamp);
+  curlx_safefree(pop3c->apoptimestamp);
   curlx_free(pop3c);
 }
 
@@ -1692,7 +1691,7 @@
 /*
  * POP3 protocol.
  */
-static const struct Curl_protocol Curl_protocol_pop3 = {
+const struct Curl_protocol Curl_protocol_pop3 = {
   pop3_setup_connection,            /* setup_connection */
   pop3_do,                          /* do_it */
   pop3_done,                        /* done */
@@ -1707,43 +1706,9 @@
   pop3_disconnect,                  /* disconnect */
   pop3_write,                       /* write_resp */
   ZERO_NULL,                        /* write_resp_hd */
-  ZERO_NULL,                        /* connection_check */
+  ZERO_NULL,                        /* connection_is_dead */
   ZERO_NULL,                        /* attach connection */
   ZERO_NULL,                        /* follow */
 };
 
 #endif /* CURL_DISABLE_POP3 */
-
-/*
- * POP3 protocol handler.
- */
-const struct Curl_scheme Curl_scheme_pop3 = {
-  "pop3",                           /* scheme */
-#ifdef CURL_DISABLE_POP3
-  ZERO_NULL,
-#else
-  &Curl_protocol_pop3,
-#endif
-  CURLPROTO_POP3,                   /* protocol */
-  CURLPROTO_POP3,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */
-  PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE,
-  PORT_POP3,                        /* defport */
-};
-
-/*
- * POP3S protocol handler.
- */
-const struct Curl_scheme Curl_scheme_pop3s = {
-  "pop3s",                          /* scheme */
-#if defined(CURL_DISABLE_POP3) || !defined(USE_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_pop3,
-#endif
-  CURLPROTO_POP3S,                  /* protocol */
-  CURLPROTO_POP3,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
-  PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE,
-  PORT_POP3S,                       /* defport */
-};
diff --git a/Utilities/cmcurl/lib/pop3.h b/Utilities/cmcurl/lib/pop3.h
index 77e30f8..75c3a67 100644
--- a/Utilities/cmcurl/lib/pop3.h
+++ b/Utilities/cmcurl/lib/pop3.h
@@ -23,7 +23,8 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_pop3;
-extern const struct Curl_scheme Curl_scheme_pop3s;
+#ifndef CURL_DISABLE_POP3
+extern const struct Curl_protocol Curl_protocol_pop3;
+#endif
 
 #endif /* HEADER_CURL_POP3_H */
diff --git a/Utilities/cmcurl/lib/progress.c b/Utilities/cmcurl/lib/progress.c
index 6cde567..919c151 100644
--- a/Utilities/cmcurl/lib/progress.c
+++ b/Utilities/cmcurl/lib/progress.c
@@ -33,7 +33,7 @@
 #ifndef CURL_DISABLE_PROGRESS_METER
 /* Provide a string that is 7 letters long (plus the zero byte).
 
-   Unit test 1636.
+   @unittest 1636
 */
 UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds);
 UNITTEST void time2str(char *r, size_t rsize, curl_off_t seconds)
@@ -83,7 +83,7 @@
    but never longer than 6 columns (+ one zero byte).
    Add suffix k, M, G when suitable...
 
-   Unit test 1636
+   @unittest 1636
 */
 UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen);
 UNITTEST char *max6out(curl_off_t bytes, char *max6, size_t mlen)
@@ -127,9 +127,11 @@
 }
 
 /*
- * @unittest: 1606
+ * @unittest 1606
  */
 UNITTEST CURLcode pgrs_speedcheck(struct Curl_easy *data,
+                                  const struct curltime *pnow);
+UNITTEST CURLcode pgrs_speedcheck(struct Curl_easy *data,
                                   const struct curltime *pnow)
 {
   if(!data->set.low_speed_time || !data->set.low_speed_limit ||
@@ -211,6 +213,7 @@
   Curl_pgrsSetUploadSize(data, -1);
   Curl_pgrsSetDownloadSize(data, -1);
   data->progress.speeder_c = 0; /* reset speed records */
+  data->progress.deliver = 0;
   pgrs_speedinit(data);
 }
 
@@ -339,6 +342,25 @@
   p->ul_size_known = FALSE;
 }
 
+/* check that the 'delta' amount of bytes are okay to deliver to the
+   application, or return error if not. */
+CURLcode Curl_pgrs_deliver_check(struct Curl_easy *data, size_t delta)
+{
+  if(data->set.max_filesize &&
+     ((curl_off_t)delta > data->set.max_filesize - data->progress.deliver)) {
+    failf(data, "Would have exceeded max file size");
+    return CURLE_FILESIZE_EXCEEDED;
+  }
+  return CURLE_OK;
+}
+
+/* this counts how much data is delivered to the application, which
+   in compressed cases may differ from downloaded amount */
+void Curl_pgrs_deliver_inc(struct Curl_easy *data, size_t delta)
+{
+  data->progress.deliver += delta;
+}
+
 void Curl_pgrs_download_inc(struct Curl_easy *data, size_t delta)
 {
   if(delta) {
@@ -609,18 +631,18 @@
 static CURLcode pgrsupdate(struct Curl_easy *data, bool showprogress)
 {
   if(!data->progress.hide) {
+    int rc;
     if(data->set.fxferinfo) {
-      int result;
       /* There is a callback set, call that */
       Curl_set_in_callback(data, TRUE);
-      result = data->set.fxferinfo(data->set.progress_client,
-                                   data->progress.dl.total_size,
-                                   data->progress.dl.cur_size,
-                                   data->progress.ul.total_size,
-                                   data->progress.ul.cur_size);
+      rc = data->set.fxferinfo(data->set.progress_client,
+                               data->progress.dl.total_size,
+                               data->progress.dl.cur_size,
+                               data->progress.ul.total_size,
+                               data->progress.ul.cur_size);
       Curl_set_in_callback(data, FALSE);
-      if(result != CURL_PROGRESSFUNC_CONTINUE) {
-        if(result) {
+      if(rc != CURL_PROGRESSFUNC_CONTINUE) {
+        if(rc) {
           failf(data, "Callback aborted");
           return CURLE_ABORTED_BY_CALLBACK;
         }
@@ -628,17 +650,16 @@
       }
     }
     else if(data->set.fprogress) {
-      int result;
       /* The older deprecated callback is set, call that */
       Curl_set_in_callback(data, TRUE);
-      result = data->set.fprogress(data->set.progress_client,
-                                   (double)data->progress.dl.total_size,
-                                   (double)data->progress.dl.cur_size,
-                                   (double)data->progress.ul.total_size,
-                                   (double)data->progress.ul.cur_size);
+      rc = data->set.fprogress(data->set.progress_client,
+                               (double)data->progress.dl.total_size,
+                               (double)data->progress.dl.cur_size,
+                               (double)data->progress.ul.total_size,
+                               (double)data->progress.ul.cur_size);
       Curl_set_in_callback(data, FALSE);
-      if(result != CURL_PROGRESSFUNC_CONTINUE) {
-        if(result) {
+      if(rc != CURL_PROGRESSFUNC_CONTINUE) {
+        if(rc) {
           failf(data, "Callback aborted");
           return CURLE_ABORTED_BY_CALLBACK;
         }
diff --git a/Utilities/cmcurl/lib/progress.h b/Utilities/cmcurl/lib/progress.h
index 9cb0924..7d419ec 100644
--- a/Utilities/cmcurl/lib/progress.h
+++ b/Utilities/cmcurl/lib/progress.h
@@ -50,7 +50,8 @@
 void Curl_pgrsStartNow(struct Curl_easy *data);
 void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size);
 void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size);
-
+CURLcode Curl_pgrs_deliver_check(struct Curl_easy *data, size_t delta);
+void Curl_pgrs_deliver_inc(struct Curl_easy *data, size_t delta);
 void Curl_pgrs_download_inc(struct Curl_easy *data, size_t delta);
 void Curl_pgrs_upload_inc(struct Curl_easy *data, size_t delta);
 void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size);
@@ -82,9 +83,4 @@
 
 void Curl_pgrsEarlyData(struct Curl_easy *data, curl_off_t sent);
 
-#ifdef UNITTESTS
-UNITTEST CURLcode pgrs_speedcheck(struct Curl_easy *data,
-                                  const struct curltime *pnow);
-#endif
-
 #endif /* HEADER_CURL_PROGRESS_H */
diff --git a/Utilities/cmcurl/lib/protocol.c b/Utilities/cmcurl/lib/protocol.c
new file mode 100644
index 0000000..c8d4325
--- /dev/null
+++ b/Utilities/cmcurl/lib/protocol.c
@@ -0,0 +1,485 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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 "protocol.h"
+#include "strcase.h"
+
+#include "dict.h"
+#include "file.h"
+#include "ftp.h"
+#include "gopher.h"
+#include "http.h"
+#include "imap.h"
+#include "curl_ldap.h"
+#include "mqtt.h"
+#include "pop3.h"
+#include "rtsp.h"
+#include "smb.h"
+#include "smtp.h"
+#include "telnet.h"
+#include "tftp.h"
+#include "ws.h"
+#include "vssh/ssh.h"
+
+
+/* All URI schemes known to libcurl, but not necessarily implemented
+ * by protocol handlers. */
+const struct Curl_scheme Curl_scheme_dict = {
+  "dict",                               /* scheme */
+#ifdef CURL_DISABLE_DICT
+  ZERO_NULL,
+#else
+  &Curl_protocol_dict,
+#endif
+  CURLPROTO_DICT,                       /* protocol */
+  CURLPROTO_DICT,                       /* family */
+  PROTOPT_NONE | PROTOPT_NOURLQUERY,    /* flags */
+  PORT_DICT,                            /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_file = {
+  "file",                               /* scheme */
+#ifdef CURL_DISABLE_FILE
+  ZERO_NULL,
+#else
+  &Curl_protocol_file,
+#endif
+  CURLPROTO_FILE,                       /* protocol */
+  CURLPROTO_FILE,                       /* family */
+  PROTOPT_NONETWORK | PROTOPT_NOURLQUERY, /* flags */
+  0                                     /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_ftp = {
+  "ftp",                           /* scheme */
+#ifdef CURL_DISABLE_FTP
+  ZERO_NULL,
+#else
+  &Curl_protocol_ftp,
+#endif
+  CURLPROTO_FTP,                   /* protocol */
+  CURLPROTO_FTP,                   /* family */
+  PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD |
+  PROTOPT_NOURLQUERY | PROTOPT_PROXY_AS_HTTP |
+  PROTOPT_WILDCARD | PROTOPT_SSL_REUSE |
+  PROTOPT_CONN_REUSE, /* flags */
+  PORT_FTP,                        /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_ftps = {
+  "ftps",                          /* scheme */
+#if defined(CURL_DISABLE_FTP) || !defined(USE_SSL)
+  ZERO_NULL,
+#else
+  &Curl_protocol_ftp,
+#endif
+  CURLPROTO_FTPS,                  /* protocol */
+  CURLPROTO_FTP,                   /* family */
+  PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION |
+  PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY | PROTOPT_WILDCARD |
+  PROTOPT_CONN_REUSE, /* flags */
+  PORT_FTPS,                       /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_gopher = {
+  "gopher",                             /* scheme */
+#ifdef CURL_DISABLE_GOPHER
+  ZERO_NULL,
+#else
+  &Curl_protocol_gopher,
+#endif
+  CURLPROTO_GOPHER,                     /* protocol */
+  CURLPROTO_GOPHER,                     /* family */
+  PROTOPT_NONE,                         /* flags */
+  PORT_GOPHER,                          /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_gophers = {
+  "gophers",                            /* scheme */
+#if defined(CURL_DISABLE_GOPHER) || !defined(USE_SSL)
+  ZERO_NULL,
+#else
+  &Curl_protocol_gophers,
+#endif
+  CURLPROTO_GOPHERS,                    /* protocol */
+  CURLPROTO_GOPHER,                     /* family */
+  PROTOPT_SSL,                          /* flags */
+  PORT_GOPHER,                          /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_http = {
+  "http",                               /* scheme */
+#ifdef CURL_DISABLE_HTTP
+  ZERO_NULL,
+#else
+  &Curl_protocol_http,
+#endif
+  CURLPROTO_HTTP,                       /* protocol */
+  CURLPROTO_HTTP,                       /* family */
+  PROTOPT_CREDSPERREQUEST |             /* flags */
+  PROTOPT_USERPWDCTRL | PROTOPT_CONN_REUSE,
+  PORT_HTTP,                            /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_https = {
+  "https",                              /* scheme */
+#if defined(CURL_DISABLE_HTTP) || !defined(USE_SSL)
+  ZERO_NULL,
+#else
+  &Curl_protocol_http,
+#endif
+  CURLPROTO_HTTPS,                      /* protocol */
+  CURLPROTO_HTTP,                       /* family */
+  PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN | /* flags */
+  PROTOPT_USERPWDCTRL | PROTOPT_CONN_REUSE,
+  PORT_HTTPS,                           /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_imap = {
+  "imap",                           /* scheme */
+#ifdef CURL_DISABLE_IMAP
+  ZERO_NULL,
+#else
+  &Curl_protocol_imap,
+#endif
+  CURLPROTO_IMAP,                   /* protocol */
+  CURLPROTO_IMAP,                   /* family */
+  PROTOPT_CLOSEACTION |             /* flags */
+  PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE |
+  PROTOPT_CONN_REUSE,
+  PORT_IMAP,                        /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_imaps = {
+  "imaps",                          /* scheme */
+#if defined(CURL_DISABLE_IMAP) || !defined(USE_SSL)
+  ZERO_NULL,
+#else
+  &Curl_protocol_imap,
+#endif
+  CURLPROTO_IMAPS,                  /* protocol */
+  CURLPROTO_IMAP,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
+  PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE,
+  PORT_IMAPS,                       /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_ldap = {
+  "ldap",                               /* scheme */
+#ifdef CURL_DISABLE_LDAP
+  ZERO_NULL,
+#else
+  &Curl_protocol_ldap,
+#endif
+  CURLPROTO_LDAP,                       /* protocol */
+  CURLPROTO_LDAP,                       /* family */
+  PROTOPT_SSL_REUSE,                    /* flags */
+  PORT_LDAP,                            /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_ldaps = {
+  "ldaps",                              /* scheme */
+#if defined(CURL_DISABLE_LDAP) || !defined(HAVE_LDAP_SSL)
+  ZERO_NULL,
+#else
+  &Curl_protocol_ldap,
+#endif
+  CURLPROTO_LDAPS,                      /* protocol */
+  CURLPROTO_LDAP,                       /* family */
+  PROTOPT_SSL,                          /* flags */
+  PORT_LDAPS,                           /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_mqtt = {
+  "mqtt",                             /* scheme */
+#ifdef CURL_DISABLE_MQTT
+  ZERO_NULL,
+#else
+  &Curl_protocol_mqtt,
+#endif
+  CURLPROTO_MQTT,                     /* protocol */
+  CURLPROTO_MQTT,                     /* family */
+  PROTOPT_NONE,                       /* flags */
+  PORT_MQTT,                          /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_mqtts = {
+  "mqtts",                            /* scheme */
+#if defined(CURL_DISABLE_MQTT) || !defined(USE_SSL)
+  ZERO_NULL,
+#else
+  &Curl_protocol_mqtts,
+#endif
+  CURLPROTO_MQTTS,                    /* protocol */
+  CURLPROTO_MQTT,                     /* family */
+  PROTOPT_SSL,                        /* flags */
+  PORT_MQTTS,                         /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_pop3 = {
+  "pop3",                           /* scheme */
+#ifdef CURL_DISABLE_POP3
+  ZERO_NULL,
+#else
+  &Curl_protocol_pop3,
+#endif
+  CURLPROTO_POP3,                   /* protocol */
+  CURLPROTO_POP3,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */
+  PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE,
+  PORT_POP3,                        /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_pop3s = {
+  "pop3s",                          /* scheme */
+#if defined(CURL_DISABLE_POP3) || !defined(USE_SSL)
+  ZERO_NULL,
+#else
+  &Curl_protocol_pop3,
+#endif
+  CURLPROTO_POP3S,                  /* protocol */
+  CURLPROTO_POP3,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
+  PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE,
+  PORT_POP3S,                       /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_rtsp = {
+  "rtsp",                               /* scheme */
+#ifdef CURL_DISABLE_RTSP
+  ZERO_NULL,
+#else
+  &Curl_protocol_rtsp,
+#endif
+  CURLPROTO_RTSP,                       /* protocol */
+  CURLPROTO_RTSP,                       /* family */
+  PROTOPT_CONN_REUSE,                   /* flags */
+  PORT_RTSP,                            /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_sftp = {
+  "sftp",                               /* scheme */
+#ifndef USE_SSH
+  NULL,
+#else
+  &Curl_protocol_sftp,
+#endif
+  CURLPROTO_SFTP,                       /* protocol */
+  CURLPROTO_SFTP,                       /* family */
+  PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION | /* flags */
+  PROTOPT_NOURLQUERY | PROTOPT_CONN_REUSE,
+  PORT_SSH                              /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_scp = {
+  "scp",                                /* scheme */
+#ifndef USE_SSH
+  NULL,
+#else
+  &Curl_protocol_scp,
+#endif
+  CURLPROTO_SCP,                        /* protocol */
+  CURLPROTO_SCP,                        /* family */
+  PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION | /* flags */
+  PROTOPT_NOURLQUERY | PROTOPT_CONN_REUSE,
+  PORT_SSH,                             /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_smb = {
+  "smb",                                /* scheme */
+#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE)
+  &Curl_protocol_smb,
+#else
+  ZERO_NULL,
+#endif
+  CURLPROTO_SMB,                        /* protocol */
+  CURLPROTO_SMB,                        /* family */
+  PROTOPT_NONE,                         /* flags */
+  PORT_SMB,                             /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_smbs = {
+  "smbs",                               /* scheme */
+#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE) && defined(USE_SSL)
+  &Curl_protocol_smb,
+#else
+  ZERO_NULL,
+#endif
+  CURLPROTO_SMBS,                       /* protocol */
+  CURLPROTO_SMB,                        /* family */
+  PROTOPT_SSL,                          /* flags */
+  PORT_SMBS,                            /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_smtp = {
+  "smtp",                           /* scheme */
+#ifdef CURL_DISABLE_SMTP
+  ZERO_NULL,
+#else
+  &Curl_protocol_smtp,
+#endif
+  CURLPROTO_SMTP,                   /* protocol */
+  CURLPROTO_SMTP,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */
+  PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE,
+  PORT_SMTP,                        /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_smtps = {
+  "smtps",                          /* scheme */
+#if defined(CURL_DISABLE_SMTP) || !defined(USE_SSL)
+  ZERO_NULL,
+#else
+  &Curl_protocol_smtp,
+#endif
+  CURLPROTO_SMTPS,                  /* protocol */
+  CURLPROTO_SMTP,                   /* family */
+  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
+  PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE,
+  PORT_SMTPS,                       /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_telnet = {
+  "telnet",                             /* scheme */
+#ifdef CURL_DISABLE_TELNET
+  ZERO_NULL,
+#else
+  &Curl_protocol_telnet,
+#endif
+  CURLPROTO_TELNET,                     /* protocol */
+  CURLPROTO_TELNET,                     /* family */
+  PROTOPT_NONE | PROTOPT_NOURLQUERY,    /* flags */
+  PORT_TELNET,                          /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_tftp = {
+  "tftp",                               /* scheme */
+#ifdef CURL_DISABLE_TFTP
+  ZERO_NULL,
+#else
+  &Curl_protocol_tftp,
+#endif
+  CURLPROTO_TFTP,                       /* protocol */
+  CURLPROTO_TFTP,                       /* family */
+  PROTOPT_NOTCPPROXY | PROTOPT_NOURLQUERY, /* flags */
+  PORT_TFTP,                            /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_ws = {
+  "ws",                                 /* scheme */
+#if defined(CURL_DISABLE_WEBSOCKETS) || defined(CURL_DISABLE_HTTP)
+  ZERO_NULL,
+#else
+  &Curl_protocol_ws,
+#endif
+  CURLPROTO_WS,                         /* protocol */
+  CURLPROTO_HTTP,                       /* family */
+  PROTOPT_CREDSPERREQUEST |             /* flags */
+  PROTOPT_USERPWDCTRL,
+  PORT_HTTP                             /* defport */
+};
+
+const struct Curl_scheme Curl_scheme_wss = {
+  "wss",                                /* scheme */
+#if defined(CURL_DISABLE_WEBSOCKETS) || defined(CURL_DISABLE_HTTP) || \
+  !defined(USE_SSL)
+  ZERO_NULL,
+#else
+  &Curl_protocol_ws,
+#endif
+  CURLPROTO_WSS,                        /* protocol */
+  CURLPROTO_HTTP,                       /* family */
+  PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */
+  PROTOPT_USERPWDCTRL,
+  PORT_HTTPS                            /* defport */
+};
+
+/* Returns a struct scheme pointer if the name is a known scheme. Check the
+   ->run struct field for non-NULL to figure out if an implementation is
+   present. */
+const struct Curl_scheme *Curl_getn_scheme(const char *scheme, size_t len)
+{
+  /* table generated by schemetable.c:
+     1. gcc schemetable.c && ./a.out
+     2. check how small the table gets
+     3. tweak the hash algorithm, then rerun from 1
+     4. when the table is good enough
+     5. copy the table into this source code
+     6. make sure this function uses the same hash function that worked for
+     schemetable.c
+     */
+  static const struct Curl_scheme * const all_schemes[47] = {
+    &Curl_scheme_mqtt,
+    &Curl_scheme_smtp,
+    &Curl_scheme_tftp,
+    &Curl_scheme_imap, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+    &Curl_scheme_ldaps,
+    &Curl_scheme_dict, NULL,
+    &Curl_scheme_file, NULL,
+    &Curl_scheme_pop3s,
+    &Curl_scheme_ftp,
+    &Curl_scheme_scp,
+    &Curl_scheme_mqtts,
+    &Curl_scheme_imaps,
+    &Curl_scheme_ldap,
+    &Curl_scheme_http,
+    &Curl_scheme_smb, NULL, NULL,
+    &Curl_scheme_telnet,
+    &Curl_scheme_https,
+    &Curl_scheme_gopher,
+    &Curl_scheme_rtsp, NULL, NULL,
+    &Curl_scheme_wss, NULL,
+    &Curl_scheme_gophers,
+    &Curl_scheme_smtps,
+    &Curl_scheme_pop3,
+    &Curl_scheme_ws, NULL, NULL,
+    &Curl_scheme_sftp,
+    &Curl_scheme_ftps, NULL,
+    &Curl_scheme_smbs, NULL,
+  };
+
+  if(len && (len <= 7)) {
+    const char *s = scheme;
+    size_t l = len;
+    const struct Curl_scheme *h;
+    unsigned int c = 792;
+    while(l) {
+      c <<= 4;
+      c += (unsigned int)Curl_raw_tolower(*s);
+      s++;
+      l--;
+    }
+
+    h = all_schemes[c % 47];
+    if(h && curl_strnequal(scheme, h->name, len) && !h->name[len])
+      return h;
+  }
+  return NULL;
+}
+
+const struct Curl_scheme *Curl_get_scheme(const char *scheme)
+{
+  return Curl_getn_scheme(scheme, strlen(scheme));
+}
diff --git a/Utilities/cmcurl/lib/protocol.h b/Utilities/cmcurl/lib/protocol.h
new file mode 100644
index 0000000..f825409
--- /dev/null
+++ b/Utilities/cmcurl/lib/protocol.h
@@ -0,0 +1,276 @@
+#ifndef HEADER_CURL_PROTOCOL_H
+#define HEADER_CURL_PROTOCOL_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
+ *
+ ***************************************************************************/
+/* This file is for lib internal stuff */
+#include "curl_setup.h"
+
+struct Curl_easy;
+struct connectdata;
+struct easy_pollset;
+
+/* Known protocol default port numbers */
+#define PORT_FTP    21
+#define PORT_FTPS   990
+#define PORT_TELNET 23
+#define PORT_HTTP   80
+#define PORT_HTTPS  443
+#define PORT_DICT   2628
+#define PORT_LDAP   389
+#define PORT_LDAPS  636
+#define PORT_TFTP   69
+#define PORT_SSH    22
+#define PORT_IMAP   143
+#define PORT_IMAPS  993
+#define PORT_POP3   110
+#define PORT_POP3S  995
+#define PORT_SMB    445
+#define PORT_SMBS   445
+#define PORT_SMTP   25
+#define PORT_SMTPS  465 /* sometimes called SSMTP */
+#define PORT_RTSP   554
+#define PORT_GOPHER 70
+#define PORT_MQTT   1883
+#define PORT_MQTTS  8883
+
+/* CURLPROTO_GOPHERS (29) is the highest publicly used protocol bit number,
+ * the rest are internal information. If we use higher bits we only do this on
+ * platforms that have a >= 64-bit type and then we use such a type for the
+ * protocol fields in the scheme definition.
+ */
+#define CURLPROTO_WS     (1L << 30)
+#define CURLPROTO_WSS    ((curl_prot_t)1 << 31)
+#define CURLPROTO_MQTTS  (1LL << 32)
+
+#define CURLPROTO_64ALL ((uint64_t)0xffffffffffffffff)
+
+/* the default protocols accepting a redirect to */
+#define CURLPROTO_REDIR (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | \
+                         CURLPROTO_FTPS)
+
+typedef curl_off_t curl_prot_t;
+
+/* This mask is for all the old protocols that are provided and defined in the
+   public header and shall exclude protocols added since which are not exposed
+   in the API */
+#define CURLPROTO_MASK   0x3fffffff
+
+/* Convenience defines for checking protocols or their SSL based version. Each
+   protocol scheme should only ever have a single CURLPROTO_ in its protocol
+   field. */
+#define PROTO_FAMILY_HTTP (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_WS | \
+                           CURLPROTO_WSS)
+#define PROTO_FAMILY_FTP  (CURLPROTO_FTP | CURLPROTO_FTPS)
+#define PROTO_FAMILY_POP3 (CURLPROTO_POP3 | CURLPROTO_POP3S)
+#define PROTO_FAMILY_SMB  (CURLPROTO_SMB | CURLPROTO_SMBS)
+#define PROTO_FAMILY_SMTP (CURLPROTO_SMTP | CURLPROTO_SMTPS)
+#define PROTO_FAMILY_SSH  (CURLPROTO_SCP | CURLPROTO_SFTP)
+
+#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) || \
+  !defined(CURL_DISABLE_POP3)
+/* these protocols support CURLOPT_DIRLISTONLY */
+#define CURL_LIST_ONLY_PROTOCOL 1
+#endif
+
+/* When redirecting transfers. */
+typedef enum {
+  FOLLOW_NONE,  /* not used within the function, a placeholder to allow
+                   initing to this */
+  FOLLOW_FAKE,  /* only records stuff, not actually following */
+  FOLLOW_RETRY, /* set if this is a request retry as opposed to a real
+                   redirect following */
+  FOLLOW_REDIR /* a full true redirect */
+} followtype;
+
+/*
+ * Specific protocol handler, an implementation of one or more URI schemes.
+ */
+struct Curl_protocol {
+  /* Complement to setup_connection_internals(). This is done before the
+     transfer "owns" the connection. */
+  CURLcode (*setup_connection)(struct Curl_easy *data,
+                               struct connectdata *conn);
+
+  /* These two functions MUST be set to be protocol dependent */
+  CURLcode (*do_it)(struct Curl_easy *data, bool *done);
+  CURLcode (*done)(struct Curl_easy *, CURLcode, bool);
+
+  /* If the curl_do() function is better made in two halves, this
+   * curl_do_more() function will be called afterwards, if set. For example
+   * for doing the FTP stuff after the PASV/PORT command.
+   */
+  CURLcode (*do_more)(struct Curl_easy *, int *);
+
+  /* This function *MAY* be set to a protocol-dependent function that is run
+   * after the connect() and everything is done, as a step in the connection.
+   * The 'done' pointer points to a bool that should be set to TRUE if the
+   * function completes before return. If it does not complete, the caller
+   * should call the ->connecting() function until it is.
+   */
+  CURLcode (*connect_it)(struct Curl_easy *data, bool *done);
+
+  /* See above. */
+  CURLcode (*connecting)(struct Curl_easy *data, bool *done);
+  CURLcode (*doing)(struct Curl_easy *data, bool *done);
+
+  /* Called from the multi interface during the PROTOCONNECT phase, and it
+     should then return a proper fd set */
+  CURLcode (*proto_pollset)(struct Curl_easy *data,
+                            struct easy_pollset *ps);
+
+  /* Called from the multi interface during the DOING phase, and it should
+     then return a proper fd set */
+  CURLcode (*doing_pollset)(struct Curl_easy *data,
+                            struct easy_pollset *ps);
+
+  /* Called from the multi interface during the DO_MORE phase, and it should
+     then return a proper fd set */
+  CURLcode (*domore_pollset)(struct Curl_easy *data,
+                            struct easy_pollset *ps);
+
+  /* Called from the multi interface during the DO_DONE, PERFORM and
+     WAITPERFORM phases, and it should then return a proper fd set. Not setting
+     this will make libcurl use the generic default one. */
+  CURLcode (*perform_pollset)(struct Curl_easy *data,
+                              struct easy_pollset *ps);
+
+  /* This function *MAY* be set to a protocol-dependent function that is run
+   * by the curl_disconnect(), as a step in the disconnection. If the handler
+   * is called because the connection has been considered dead,
+   * dead_connection is set to TRUE. The connection is (again) associated with
+   * the transfer here.
+   */
+  CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *,
+                         bool dead_connection);
+
+  /* If used, this function gets called from transfer.c to
+     allow the protocol to do extra handling in writing response to
+     the client. */
+  CURLcode (*write_resp)(struct Curl_easy *data, const char *buf, size_t blen,
+                         bool is_eos);
+
+  /* If used, this function gets called from transfer.c to
+     allow the protocol to do extra handling in writing a single response
+     header line to the client. */
+  CURLcode (*write_resp_hd)(struct Curl_easy *data,
+                            const char *hd, size_t hdlen, bool is_eos);
+
+  /* If used, this function checks for a connection managed by this
+    protocol and currently not in use, if it should be considered dead. */
+  bool (*connection_is_dead)(struct Curl_easy *data,
+                             struct connectdata *conn);
+
+  /* attach() attaches this transfer to this connection */
+  void (*attach)(struct Curl_easy *data, struct connectdata *conn);
+
+  /* return CURLE_OK if a redirect to `newurl` should be followed,
+     CURLE_TOO_MANY_REDIRECTS otherwise. May alter `data` to change
+     the way the follow request is performed. */
+  CURLcode (*follow)(struct Curl_easy *data, const char *newurl,
+                     followtype type);
+};
+
+#define PROTOPT_NONE 0             /* nothing extra */
+#define PROTOPT_SSL (1 << 0)       /* uses SSL */
+#define PROTOPT_DUAL (1 << 1)      /* this protocol uses two connections */
+#define PROTOPT_CLOSEACTION (1 << 2) /* need action before socket close */
+/* some protocols will have to call the underlying functions without regard to
+   what exact state the socket signals. IE even if the socket says "readable",
+   the send function might need to be called while uploading, or vice versa.
+*/
+#define PROTOPT_DIRLOCK (1 << 3)
+#define PROTOPT_NONETWORK (1 << 4) /* protocol does not use the network! */
+#define PROTOPT_NEEDSPWD (1 << 5)  /* needs a password, and if none is set it
+                                      gets a default */
+#define PROTOPT_NOURLQUERY (1 << 6)  /* protocol cannot handle
+                                        URL query strings (?foo=bar) ! */
+#define PROTOPT_CREDSPERREQUEST (1 << 7) /* requires login credentials per
+                                            request instead of per
+                                            connection */
+#define PROTOPT_ALPN (1 << 8) /* set ALPN for this */
+/* (1 << 9) was PROTOPT_STREAM, now free */
+#define PROTOPT_URLOPTIONS (1 << 10) /* allow options part in the userinfo
+                                        field of the URL */
+#define PROTOPT_PROXY_AS_HTTP (1 << 11) /* allow this non-HTTP scheme over a
+                                           HTTP proxy as HTTP proxies may know
+                                           this protocol and act as
+                                           a gateway */
+#define PROTOPT_WILDCARD (1 << 12)  /* protocol supports wildcard matching */
+#define PROTOPT_USERPWDCTRL (1 << 13) /* Allow "control bytes" (< 32 ASCII) in
+                                         username and password */
+#define PROTOPT_NOTCPPROXY (1 << 14)  /* this protocol cannot proxy over TCP */
+#define PROTOPT_SSL_REUSE (1 << 15)   /* this protocol may reuse an existing
+                                         SSL connection in the same family
+                                         without having PROTOPT_SSL. */
+#define PROTOPT_CONN_REUSE (1 << 16)  /* this protocol can reuse connections */
+
+/* Everything about a URI scheme. */
+struct Curl_scheme {
+  const char *name;       /* URL scheme name in lowercase */
+  const struct Curl_protocol *run; /* implementation, optional */
+  curl_prot_t protocol;   /* See CURLPROTO_* - this needs to be the single
+                             specific protocol bit */
+  curl_prot_t family;     /* single bit for protocol family; the non-TLS name
+                             of the protocol this is */
+  uint32_t flags;         /* Extra particular characteristics, see PROTOPT_* */
+  uint16_t defport;       /* Default port. */
+};
+
+/* Get scheme definition for a URI scheme name
+ * @param scheme URI scheme name, case-insensitive
+ * @return NULL if scheme is not known
+ */
+const struct Curl_scheme *Curl_get_scheme(const char *scheme);
+const struct Curl_scheme *Curl_getn_scheme(const char *scheme, size_t len);
+
+/* For direct access to a URI scheme */
+extern const struct Curl_scheme Curl_scheme_dict;
+extern const struct Curl_scheme Curl_scheme_file;
+extern const struct Curl_scheme Curl_scheme_ftp;
+extern const struct Curl_scheme Curl_scheme_ftps;
+extern const struct Curl_scheme Curl_scheme_gopher;
+extern const struct Curl_scheme Curl_scheme_gophers;
+extern const struct Curl_scheme Curl_scheme_http;
+extern const struct Curl_scheme Curl_scheme_https;
+extern const struct Curl_scheme Curl_scheme_imap;
+extern const struct Curl_scheme Curl_scheme_imaps;
+extern const struct Curl_scheme Curl_scheme_ldap;
+extern const struct Curl_scheme Curl_scheme_ldaps;
+extern const struct Curl_scheme Curl_scheme_mqtt;
+extern const struct Curl_scheme Curl_scheme_mqtts;
+extern const struct Curl_scheme Curl_scheme_pop3;
+extern const struct Curl_scheme Curl_scheme_pop3s;
+extern const struct Curl_scheme Curl_scheme_rtsp;
+extern const struct Curl_scheme Curl_scheme_scp;
+extern const struct Curl_scheme Curl_scheme_sftp;
+extern const struct Curl_scheme Curl_scheme_smb;
+extern const struct Curl_scheme Curl_scheme_smbs;
+extern const struct Curl_scheme Curl_scheme_smtp;
+extern const struct Curl_scheme Curl_scheme_smtps;
+extern const struct Curl_scheme Curl_scheme_telnet;
+extern const struct Curl_scheme Curl_scheme_tftp;
+extern const struct Curl_scheme Curl_scheme_ws;
+extern const struct Curl_scheme Curl_scheme_wss;
+
+#endif /* HEADER_CURL_PROTOCOL_H */
diff --git a/Utilities/cmcurl/lib/rand.c b/Utilities/cmcurl/lib/rand.c
index 4232e81..dd82750 100644
--- a/Utilities/cmcurl/lib/rand.c
+++ b/Utilities/cmcurl/lib/rand.c
@@ -34,30 +34,20 @@
 #include "escape.h"
 
 #ifdef _WIN32
-
-#ifndef CURL_WINDOWS_UWP
-#  include <bcrypt.h>
-#  ifdef _MSC_VER
-#    pragma comment(lib, "bcrypt.lib")
-#  endif
-#  ifndef STATUS_SUCCESS
-#  define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
-#  endif
+#include <bcrypt.h>
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
 #endif
 
 CURLcode Curl_win32_random(unsigned char *entropy, size_t length)
 {
   memset(entropy, 0, length);
 
-#ifndef CURL_WINDOWS_UWP
   if(BCryptGenRandom(NULL, entropy, (ULONG)length,
                      BCRYPT_USE_SYSTEM_PREFERRED_RNG) != STATUS_SUCCESS)
     return CURLE_FAILED_INIT;
 
   return CURLE_OK;
-#else
-  return CURLE_NOT_BUILT_IN;
-#endif
 }
 #endif
 
diff --git a/Utilities/cmcurl/lib/rand.h b/Utilities/cmcurl/lib/rand.h
index 8676580..afccd0a 100644
--- a/Utilities/cmcurl/lib/rand.h
+++ b/Utilities/cmcurl/lib/rand.h
@@ -25,7 +25,7 @@
  ***************************************************************************/
 CURLcode Curl_rand_bytes(struct Curl_easy *data,
 #ifdef DEBUGBUILD
-                         bool allow_env_override,
+                         bool env_override,
 #endif
                          unsigned char *rnd, size_t num);
 
diff --git a/Utilities/cmcurl/lib/ratelimit.c b/Utilities/cmcurl/lib/ratelimit.c
index 2d88cdd..dc01375 100644
--- a/Utilities/cmcurl/lib/ratelimit.c
+++ b/Utilities/cmcurl/lib/ratelimit.c
@@ -157,8 +157,8 @@
   DEBUGASSERT(rate_per_sec >= 0);
   DEBUGASSERT(burst_per_sec >= rate_per_sec || !burst_per_sec);
   DEBUGASSERT(pts);
-  r->rate_per_step = rate_per_sec;
-  r->burst_per_step = burst_per_sec;
+  r->rate_per_step = r->rate_per_sec = rate_per_sec;
+  r->burst_per_step = r->burst_per_sec = burst_per_sec;
   r->step_us = CURL_US_PER_SEC;
   r->spare_us = 0;
   r->tokens = r->rate_per_step;
@@ -169,8 +169,13 @@
 void Curl_rlimit_start(struct Curl_rlimit *r, const struct curltime *pts,
                        int64_t total_tokens)
 {
-  r->tokens = r->rate_per_step;
+  /* A start always resets the values to initial defaults, then
+   * fine tunes the intervals for the total_tokens expected. */
+  r->rate_per_step = r->rate_per_sec;
+  r->burst_per_step = r->burst_per_sec;
+  r->step_us = CURL_US_PER_SEC;
   r->spare_us = 0;
+  r->tokens = r->rate_per_step;
   r->ts = *pts;
   rlimit_tune_steps(r, total_tokens);
 }
diff --git a/Utilities/cmcurl/lib/ratelimit.h b/Utilities/cmcurl/lib/ratelimit.h
index 7563734..3c3e38b 100644
--- a/Utilities/cmcurl/lib/ratelimit.h
+++ b/Utilities/cmcurl/lib/ratelimit.h
@@ -55,6 +55,8 @@
  */
 
 struct Curl_rlimit {
+  int64_t rate_per_sec; /* rate tokens generated per second */
+  int64_t burst_per_sec; /* burst rate of tokens per second */
   int64_t rate_per_step; /* rate tokens generated per step us */
   int64_t burst_per_step; /* burst rate of tokens per step us */
   timediff_t step_us;     /* microseconds between token increases */
diff --git a/Utilities/cmcurl/lib/request.c b/Utilities/cmcurl/lib/request.c
index 6607753..c414383 100644
--- a/Utilities/cmcurl/lib/request.c
+++ b/Utilities/cmcurl/lib/request.c
@@ -65,6 +65,11 @@
   req->httpversion = 0;
   req->sendbuf_hds_len = 0;
 
+  curlx_safefree(req->userpwd);
+#ifndef CURL_DISABLE_PROXY
+  curlx_safefree(req->proxyuserpwd);
+#endif
+
   result = Curl_client_start(data);
   if(result)
     return result;
@@ -102,9 +107,6 @@
   if(!aborted)
     (void)req_flush(data);
   Curl_client_reset(data);
-#ifndef CURL_DISABLE_DOH
-  Curl_doh_close(data);
-#endif
   return CURLE_OK;
 }
 
@@ -112,14 +114,20 @@
 {
   struct curltime t0 = { 0, 0 };
 
-  Curl_safefree(req->newurl);
+  curlx_safefree(req->newurl);
+  curlx_safefree(req->userpwd);
+#ifndef CURL_DISABLE_PROXY
+  curlx_safefree(req->proxyuserpwd);
+#endif
+#ifndef CURL_DISABLE_COOKIES
+  curlx_safefree(req->cookiehost);
+#endif
   Curl_client_reset(data);
   if(req->sendbuf_init)
     Curl_bufq_reset(&req->sendbuf);
 
-#ifndef CURL_DISABLE_DOH
-  Curl_doh_close(data);
-#endif
+  /* clear any resolve data */
+  Curl_resolv_destroy_all(data);
   /* Can no longer memset() this struct as we need to keep some state */
   req->size = -1;
   req->maxdownload = -1;
@@ -132,7 +140,7 @@
   req->headerline = 0;
   req->offset = 0;
   req->httpcode = 0;
-  req->keepon = 0;
+  req->io_flags = 0;
   req->upgr101 = UPGR101_NONE;
   req->sendbuf_hds_len = 0;
   req->timeofdoc = 0;
@@ -153,6 +161,7 @@
   req->ignorebody = FALSE;
   req->http_bodyless = FALSE;
   req->chunk = FALSE;
+  req->resp_trailer = FALSE;
   req->ignore_cl = FALSE;
   req->upload_chunky = FALSE;
   req->no_body = data->set.opt_no_body;
@@ -165,7 +174,11 @@
 
 void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data)
 {
-  Curl_safefree(req->newurl);
+  curlx_safefree(req->newurl);
+  curlx_safefree(req->userpwd);
+#ifndef CURL_DISABLE_PROXY
+  curlx_safefree(req->proxyuserpwd);
+#endif
   if(req->sendbuf_init)
     Curl_bufq_free(&req->sendbuf);
   Curl_client_cleanup(data);
@@ -256,7 +269,7 @@
 {
   DEBUGASSERT(!data->req.upload_done);
   data->req.upload_done = TRUE;
-  data->req.keepon &= ~KEEP_SEND; /* we are done sending */
+  CURL_REQ_CLEAR_SEND(data);
 
   Curl_pgrsTime(data, TIMER_POSTRANSFER);
   Curl_creader_done(data, data->req.upload_aborted);
@@ -419,22 +432,22 @@
 bool Curl_req_want_send(struct Curl_easy *data)
 {
   /* Not done and upload not blocked and either one of
-   * - KEEP_SEND
+   * - REQ_IO_SEND
    * - request has buffered data to send
    * - connection has pending data to send */
   return !data->req.done &&
          !Curl_rlimit_is_blocked(&data->progress.ul.rlimit) &&
-         ((data->req.keepon & KEEP_SEND) ||
+         (CURL_REQ_WANT_SEND(data) ||
           !Curl_req_sendbuf_empty(data) ||
           Curl_xfer_needs_flush(data));
 }
 
 bool Curl_req_want_recv(struct Curl_easy *data)
 {
-  /* Not done and download not blocked and KEEP_RECV */
+  /* Not done and download not blocked and want RECV */
   return !data->req.done &&
          !Curl_rlimit_is_blocked(&data->progress.dl.rlimit) &&
-         (data->req.keepon & KEEP_RECV);
+         CURL_REQ_WANT_RECV(data);
 }
 
 bool Curl_req_done_sending(struct Curl_easy *data)
@@ -470,7 +483,7 @@
   if(!data->req.upload_done) {
     Curl_bufq_reset(&data->req.sendbuf);
     data->req.upload_aborted = TRUE;
-    data->req.keepon &= ~KEEP_SEND;
+    CURL_REQ_CLEAR_SEND(data);
     return req_set_upload_done(data);
   }
   return CURLE_OK;
@@ -482,8 +495,8 @@
    * We might still be paused on receive client writes though, so
    * keep those bits around. */
   CURLcode result = CURLE_OK;
-  if(data->req.keepon & KEEP_SEND)
+  if(CURL_REQ_WANT_SEND(data))
     result = Curl_req_abort_sending(data);
-  data->req.keepon &= ~(KEEP_RECV | KEEP_SEND);
+  CURL_REQ_CLEAR_IO(data);
   return result;
 }
diff --git a/Utilities/cmcurl/lib/request.h b/Utilities/cmcurl/lib/request.h
index 5332d48..6948d79 100644
--- a/Utilities/cmcurl/lib/request.h
+++ b/Utilities/cmcurl/lib/request.h
@@ -31,6 +31,25 @@
 /* forward declarations */
 struct UserDefined;
 
+/* Bits on the io_flags member of SingleRequest */
+#define REQ_IO_RECV       (1 << 0) /* there is or may be data to read */
+#define REQ_IO_SEND       (1 << 1) /* there is or may be data to write */
+
+/* Low level request receive/send io_flags checks. */
+#define CURL_REQ_WANT_SEND(d)  ((d)->req.io_flags & REQ_IO_SEND)
+#define CURL_REQ_WANT_RECV(d)  ((d)->req.io_flags & REQ_IO_RECV)
+#define CURL_REQ_WANT_IO(d)    \
+  ((d)->req.io_flags & (REQ_IO_RECV | REQ_IO_SEND))
+/* Low level request receive/send io_flags manipulations. */
+#define CURL_REQ_SET_SEND(d)   ((d)->req.io_flags |= REQ_IO_SEND)
+#define CURL_REQ_SET_RECV(d)   ((d)->req.io_flags |= REQ_IO_RECV)
+#define CURL_REQ_CLEAR_SEND(d) \
+  ((d)->req.io_flags &= (uint8_t)~REQ_IO_SEND)
+#define CURL_REQ_CLEAR_RECV(d) \
+  ((d)->req.io_flags &= (uint8_t)~REQ_IO_RECV)
+#define CURL_REQ_CLEAR_IO(d)  \
+  ((d)->req.io_flags &= (uint8_t)~(REQ_IO_RECV | REQ_IO_SEND))
+
 enum expect100 {
   EXP100_SEND_DATA,           /* enough waiting, send the body now */
   EXP100_AWAITING_CONTINUE,   /* waiting for the 100 Continue header */
@@ -59,6 +78,8 @@
                              -1 means unlimited */
   curl_off_t bytecount;         /* total number of bytes read */
   curl_off_t writebytecount;    /* number of bytes written */
+  curl_off_t offset;            /* possible resume offset read from the
+                                   Content-Range: header */
 
   struct curltime start;         /* transfer started at this time */
   unsigned int headerbytecount;  /* received server headers (not CONNECT
@@ -72,11 +93,8 @@
                                      in a CURLE_GOT_NOTHING error code */
   int headerline;               /* counts header lines to better track the
                                    first one */
-  curl_off_t offset;            /* possible resume offset read from the
-                                   Content-Range: header */
   int httpcode;                 /* error code from the 'HTTP/1.? XXX' or
                                    'RTSP/1.? XXX' line */
-  int keepon;
   unsigned char httpversion_sent; /* Version in request (09, 10, 11, etc.) */
   unsigned char httpversion;    /* Version in response (09, 10, 11, etc.) */
   enum upgrade101 upgr101;      /* 101 upgrade state */
@@ -94,7 +112,15 @@
                        header data */
   char *newurl;     /* Set to the new URL to use when a redirect or a retry is
                        wanted */
+  uint8_t io_flags; /* REQ_IO_RECV | REQ_IO_SEND */
 
+  char *userpwd;      /* auth header */
+#ifndef CURL_DISABLE_PROXY
+  char *proxyuserpwd; /* proxy auth header */
+#endif
+#ifndef CURL_DISABLE_COOKIES
+  char *cookiehost;
+#endif
 #ifndef CURL_DISABLE_COOKIES
   unsigned char setcookies;
 #endif
@@ -109,8 +135,8 @@
   BIT(eos_sent);      /* iff EOS has been sent to the server */
   BIT(rewind_read);   /* iff reader needs rewind at next start */
   BIT(upload_done);   /* set to TRUE when all request data has been sent */
-  BIT(upload_aborted); /* set to TRUE when upload was aborted. Will also
-                        * show `upload_done` as TRUE. */
+  BIT(upload_aborted); /* set to TRUE when upload was aborted. Also
+                        * shows `upload_done` as TRUE. */
   BIT(ignorebody);    /* we read a response-body but we ignore it! */
   BIT(http_bodyless); /* HTTP response status code is between 100 and 199,
                          204 or 304 */
@@ -207,13 +233,13 @@
 
 /**
  * Stop sending any more request data to the server.
- * Will clear the send buffer and mark request sending as done.
+ * Clear the send buffer and mark request sending as done.
  */
 CURLcode Curl_req_abort_sending(struct Curl_easy *data);
 
 /**
  * Stop sending and receiving any more request data.
- * Will abort sending if not done.
+ * Abort sending if not done.
  */
 CURLcode Curl_req_stop_send_recv(struct Curl_easy *data);
 
diff --git a/Utilities/cmcurl/lib/rtsp.c b/Utilities/cmcurl/lib/rtsp.c
index 11848c8..b08767f 100644
--- a/Utilities/cmcurl/lib/rtsp.c
+++ b/Utilities/cmcurl/lib/rtsp.c
@@ -126,19 +126,13 @@
 /*
  * Function to check on various aspects of a connection.
  */
-static uint32_t rtsp_conncheck(struct Curl_easy *data,
-                               struct connectdata *conn,
-                               uint32_t checks_to_perform)
+static bool rtsp_conn_is_dead(struct Curl_easy *data,
+                              struct connectdata *conn)
 {
-  unsigned int ret_val = CONNRESULT_NONE;
-
-  if(checks_to_perform & CONNCHECK_ISDEAD) {
-    bool input_pending;
-    if(!Curl_conn_is_alive(data, conn, &input_pending))
-      ret_val |= CONNRESULT_DEAD;
-  }
-
-  return ret_val;
+  bool input_pending;
+  /* Contrary to default handling, this protocol allows pending
+   * input on an unused connection. */
+  return !Curl_conn_is_alive(data, conn, &input_pending);
 }
 
 static CURLcode rtsp_connect(struct Curl_easy *data, bool *done)
@@ -166,7 +160,7 @@
   struct rtsp_conn *rtspc =
     Curl_conn_meta_get(data->conn, CURL_META_RTSP_CONN);
   struct RTSP *rtsp = Curl_meta_get(data, CURL_META_RTSP_EASY);
-  CURLcode httpStatus;
+  CURLcode result;
 
   if(!rtspc || !rtsp)
     return CURLE_FAILED_INIT;
@@ -175,9 +169,9 @@
   if(data->set.rtspreq == RTSPREQ_RECEIVE)
     premature = TRUE;
 
-  httpStatus = Curl_http_done(data, status, premature);
+  result = Curl_http_done(data, status, premature);
 
-  if(!status && !httpStatus) {
+  if(!status && !result) {
     /* Check the sequence numbers */
     uint32_t CSeq_sent = rtsp->CSeq_sent;
     uint32_t CSeq_recv = rtsp->CSeq_recv;
@@ -197,7 +191,7 @@
     }
   }
 
-  return httpStatus;
+  return result;
 }
 
 static CURLcode rtsp_setup_body(struct Curl_easy *data,
@@ -240,7 +234,7 @@
       /* As stated in the http comments, it is probably not wise to
        * actually set a custom Content-Length in the headers */
       if(!Curl_checkheaders(data, STRCONST("Content-Length"))) {
-        result = curlx_dyn_addf(reqp, "Content-Length: %" FMT_OFF_T"\r\n",
+        result = curlx_dyn_addf(reqp, "Content-Length: %" FMT_OFF_T "\r\n",
                                 req_clen);
         if(result)
           return result;
@@ -439,13 +433,13 @@
     }
   }
 
-  /* The User-Agent string might have been allocated in url.c already, because
+  /* The User-Agent string might have been allocated already, because
      it might have been used in the proxy connect, but if we have got a header
      with the user-agent string specified, we erase the previously made string
      here. */
   if(Curl_checkheaders(data, STRCONST("User-Agent")) &&
      data->state.aptr.uagent) {
-    Curl_safefree(data->state.aptr.uagent);
+    curlx_safefree(data->state.aptr.uagent);
   }
   else if(!Curl_checkheaders(data, STRCONST("User-Agent")) &&
           data->set.str[STRING_USERAGENT]) {
@@ -459,12 +453,12 @@
     goto out;
 
 #ifndef CURL_DISABLE_PROXY
-  p_proxyuserpwd = data->state.aptr.proxyuserpwd;
+  p_proxyuserpwd = data->req.proxyuserpwd;
 #endif
-  p_userpwd = data->state.aptr.userpwd;
+  p_userpwd = data->req.userpwd;
 
   /* Referrer */
-  Curl_safefree(data->state.aptr.ref);
+  curlx_safefree(data->state.aptr.ref);
   if(Curl_bufref_ptr(&data->state.referer) &&
      !Curl_checkheaders(data, STRCONST("Referer")))
     data->state.aptr.ref =
@@ -544,12 +538,6 @@
                           p_proxyuserpwd ? p_proxyuserpwd : "",
                           p_userpwd ? p_userpwd : "");
 
-  /*
-   * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM
-   * with basic and digest, it will be freed anyway by the next request
-   */
-  Curl_safefree(data->state.aptr.userpwd);
-
   if(result)
     goto out;
 
@@ -686,7 +674,7 @@
             /* This could be the next response, no consume and return */
             if(*pconsumed) {
               DEBUGF(infof(data, "RTP rtsp_filter_rtp[SKIP] RTSP/ prefix, "
-                           "skipping %zd bytes of junk", *pconsumed));
+                           "skipping %zu bytes of junk", *pconsumed));
             }
             rtspc->state = RTP_PARSE_SKIP;
             rtspc->in_header = TRUE;
@@ -1017,7 +1005,7 @@
      *
      * Allow any non whitespace content, up to the field separator or end of
      * line. RFC 2326 is not 100% clear on the session ID and for example
-     * gstreamer does url-encoded session ID's not covered by the standard.
+     * gstreamer does URL-encoded session ID's not covered by the standard.
      */
     end = start;
     while((*end > ' ') && (*end != ';'))
@@ -1057,7 +1045,7 @@
 /*
  * RTSP handler interface.
  */
-static const struct Curl_protocol Curl_protocol_rtsp = {
+const struct Curl_protocol Curl_protocol_rtsp = {
   rtsp_setup_connection,                /* setup_connection */
   rtsp_do,                              /* do_it */
   rtsp_done,                            /* done */
@@ -1072,25 +1060,9 @@
   ZERO_NULL,                            /* disconnect */
   rtsp_rtp_write_resp,                  /* write_resp */
   rtsp_rtp_write_resp_hd,               /* write_resp_hd */
-  rtsp_conncheck,                       /* connection_check */
+  rtsp_conn_is_dead,                    /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   Curl_http_follow,                     /* follow */
 };
 
 #endif /* CURL_DISABLE_RTSP */
-
-/*
- * RTSP handler interface.
- */
-const struct Curl_scheme Curl_scheme_rtsp = {
-  "rtsp",                               /* scheme */
-#ifdef CURL_DISABLE_RTSP
-  ZERO_NULL,
-#else
-  &Curl_protocol_rtsp,
-#endif
-  CURLPROTO_RTSP,                       /* protocol */
-  CURLPROTO_RTSP,                       /* family */
-  PROTOPT_CONN_REUSE,                   /* flags */
-  PORT_RTSP,                            /* defport */
-};
diff --git a/Utilities/cmcurl/lib/rtsp.h b/Utilities/cmcurl/lib/rtsp.h
index 92b28e5..dd5df3f 100644
--- a/Utilities/cmcurl/lib/rtsp.h
+++ b/Utilities/cmcurl/lib/rtsp.h
@@ -23,9 +23,9 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_rtsp;
 #ifndef CURL_DISABLE_RTSP
 CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header);
+extern const struct Curl_protocol Curl_protocol_rtsp;
 #else
 #define Curl_rtsp_parseheader(x, y) CURLE_NOT_BUILT_IN
 #endif
diff --git a/Utilities/cmcurl/lib/select.c b/Utilities/cmcurl/lib/select.c
index 9a11924..888490f 100644
--- a/Utilities/cmcurl/lib/select.c
+++ b/Utilities/cmcurl/lib/select.c
@@ -75,17 +75,17 @@
 
 #ifdef USE_WINSOCK
   /* Winsock select() must not be called with an fd_set that contains zero
-    fd flags, or it will return WSAEINVAL. But, it also cannot be called
-    with no fd_sets at all!  From the documentation:
+     fd flags, or it will return WSAEINVAL. It also cannot be called with
+     no fd_sets at all!  From the documentation:
 
-    Any two of the parameters, readfds, writefds, or exceptfds, can be
-    given as null. At least one must be non-null, and any non-null
-    descriptor set must contain at least one handle to a socket.
+     Any two of the parameters, readfds, writefds, or exceptfds, can be
+     given as null. At least one must be non-null, and any non-null
+     descriptor set must contain at least one handle to a socket.
 
-    It is unclear why Winsock does not handle this for us instead of
-    calling this an error. Luckily, with Winsock, we can _also_ ask how
-    many bits are set on an fd_set. So, let's check it beforehand.
-  */
+     It is unclear why Winsock does not handle this for us instead of
+     calling this an error. Luckily, with Winsock, we can _also_ ask how
+     many bits are set on an fd_set. Therefore, let's check it beforehand.
+   */
   return select((int)maxfd + 1,
                 fds_read && fds_read->fd_count ? fds_read : NULL,
                 fds_write && fds_write->fd_count ? fds_write : NULL,
@@ -274,7 +274,7 @@
     if(ufds[i].fd == CURL_SOCKET_BAD)
       continue;
     VERIFY_SOCK(ufds[i].fd);
-    if(ufds[i].events & (POLLIN |POLLOUT |POLLPRI |
+    if(ufds[i].events & (POLLIN | POLLOUT | POLLPRI |
                          POLLRDNORM | POLLWRNORM | POLLRDBAND)) {
       if(ufds[i].fd > maxfd)
         maxfd = ufds[i].fd;
@@ -636,19 +636,25 @@
                           bool do_in, bool do_out)
 {
   return Curl_pollset_change(data, ps, sock,
-                             (do_in ? CURL_POLL_IN : 0)|
+                             (do_in ? CURL_POLL_IN : 0) |
                              (do_out ? CURL_POLL_OUT : 0),
-                             (!do_in ? CURL_POLL_IN : 0)|
+                             (!do_in ? CURL_POLL_IN : 0) |
                              (!do_out ? CURL_POLL_OUT : 0));
 }
 
+/*
+ * Return values:
+ *   -1 = error
+ *    0 = timeout
+ *    N = number of structures with non zero revent fields
+ */
 int Curl_pollset_poll(struct Curl_easy *data,
                       struct easy_pollset *ps,
                       timediff_t timeout_ms)
 {
   struct pollfd *pfds;
   unsigned int i, npfds;
-  int result;
+  int rc;
 
   (void)data;
   DEBUGASSERT(data);
@@ -677,9 +683,9 @@
     }
   }
 
-  result = Curl_poll(pfds, npfds, timeout_ms);
+  rc = Curl_poll(pfds, npfds, timeout_ms);
   curlx_free(pfds);
-  return result;
+  return rc;
 }
 
 void Curl_pollset_check(struct Curl_easy *data,
diff --git a/Utilities/cmcurl/lib/select.h b/Utilities/cmcurl/lib/select.h
index ffcd44c..87b6954 100644
--- a/Utilities/cmcurl/lib/select.h
+++ b/Utilities/cmcurl/lib/select.h
@@ -35,9 +35,7 @@
  * Definition of pollfd struct and constants for platforms lacking them.
  */
 
-#if !defined(HAVE_SYS_POLL_H) && \
-    !defined(HAVE_POLL_H) && \
-    !defined(POLLIN)
+#if !defined(HAVE_SYS_POLL_H) && !defined(HAVE_POLL_H) && !defined(POLLIN)
 
 #define POLLIN      0x01
 #define POLLPRI     0x02
diff --git a/Utilities/cmcurl/lib/sendf.c b/Utilities/cmcurl/lib/sendf.c
index 92e77b4..7c977d3 100644
--- a/Utilities/cmcurl/lib/sendf.c
+++ b/Utilities/cmcurl/lib/sendf.c
@@ -372,8 +372,8 @@
    The bit pattern defines to what "streams" to write to. Body and/or header.
    The defines are in sendf.h of course.
  */
-CURLcode Curl_client_write(struct Curl_easy *data,
-                           int type, const char *buf, size_t len)
+CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf,
+                           size_t len)
 {
   CURLcode result;
 
@@ -675,7 +675,7 @@
   case 0:
     if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) {
       failf(data, "client read function EOF fail, "
-            "only %"FMT_OFF_T"/%"FMT_OFF_T " of needed bytes read",
+            "only %" FMT_OFF_T "/%" FMT_OFF_T " of needed bytes read",
             ctx->read_len, ctx->total_len);
       result = CURLE_READ_ERROR;
       break;
@@ -729,8 +729,8 @@
     *peos = (bool)ctx->seen_eos;
     break;
   }
-  CURL_TRC_READ(data, "cr_in_read(len=%zu, total=%"FMT_OFF_T
-                ", read=%"FMT_OFF_T") -> %d, nread=%zu, eos=%d",
+  CURL_TRC_READ(data, "cr_in_read(len=%zu, total=%" FMT_OFF_T
+                ", read=%" FMT_OFF_T ") -> %d, nread=%zu, eos=%d",
                 blen, ctx->total_len, ctx->read_len, result,
                 *pnread, *peos);
   return result;
@@ -1009,6 +1009,8 @@
 
     if(!nread || !memchr(buf, '\n', nread)) {
       /* nothing to convert, return this right away */
+      if(nread)
+        ctx->prev_cr = (buf[nread - 1] == '\r');
       if(ctx->read_eos)
         ctx->eos = TRUE;
       *pnread = nread;
@@ -1128,7 +1130,7 @@
   struct cr_in_ctx *ctx;
 
   result = Curl_creader_create(&r, data, &cr_in, CURL_CR_CLIENT);
-  if(result)
+  if(result || !r)
     goto out;
   ctx = r->ctx;
   ctx->total_len = len;
@@ -1136,7 +1138,7 @@
   cl_reset_reader(data);
   result = do_init_reader_stack(data, r);
 out:
-  CURL_TRC_READ(data, "add fread reader, len=%"FMT_OFF_T " -> %d",
+  CURL_TRC_READ(data, "add fread reader, len=%" FMT_OFF_T " -> %d",
                 len, result);
   return result;
 }
diff --git a/Utilities/cmcurl/lib/setopt.c b/Utilities/cmcurl/lib/setopt.c
index 84f3e02..b8a6327 100644
--- a/Utilities/cmcurl/lib/setopt.c
+++ b/Utilities/cmcurl/lib/setopt.c
@@ -49,6 +49,7 @@
 #include "curlx/strdup.h"
 #include "escape.h"
 #include "bufref.h"
+#include "vauth/vauth.h"
 
 static CURLcode setopt_set_timeout_sec(timediff_t *ptimeout_ms, long secs)
 {
@@ -83,7 +84,7 @@
   /* Release the previous storage at `charp' and replace by a dynamic storage
      copy of `s'. Return CURLE_OK or CURLE_OUT_OF_MEMORY. */
 
-  Curl_safefree(*charp);
+  curlx_safefree(*charp);
 
   if(s) {
     if(strlen(s) > CURL_MAX_INPUT_LENGTH)
@@ -103,7 +104,7 @@
   /* free the previous storage at `blobp' and replace by a dynamic storage
      copy of blob. If CURL_BLOB_COPY is set, the data is copied. */
 
-  Curl_safefree(*blobp);
+  curlx_safefree(*blobp);
 
   if(blob) {
     struct curl_blob *nblob;
@@ -137,8 +138,8 @@
   DEBUGASSERT(userp);
   DEBUGASSERT(passwdp);
 
-  /* Parse the login details if specified. It not then we treat NULL as a hint
-     to clear the existing data */
+  /* Parse the login details if specified. If not, then we treat NULL as a
+     hint to clear the existing data */
   if(option) {
     size_t len = strlen(option);
     CURLcode result;
@@ -488,7 +489,7 @@
   case CURLOPT_UPLOAD:
   case CURLOPT_PUT:
     /*
-     * We want to sent data to the remote host. If this is HTTP, that equals
+     * We want to send data to the remote host. If this is HTTP, that equals
      * using the PUT request.
      */
     if(enabled) {
@@ -613,7 +614,7 @@
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
   case CURLOPT_SOCKS5_GSSAPI_NEC:
     /*
-     * Set flag for NEC SOCK5 support
+     * Set flag for NEC SOCKS5 support
      */
     s->socks5_gssapi_nec = enabled;
     break;
@@ -1011,7 +1012,7 @@
   case CURLOPT_SSL_ENABLE_NPN:
     break;
   case CURLOPT_SSLENGINE_DEFAULT:
-    Curl_safefree(s->str[STRING_SSL_ENGINE]);
+    curlx_safefree(s->str[STRING_SSL_ENGINE]);
     result = Curl_ssl_set_engine_default(data);
     break;
   default:
@@ -1243,7 +1244,7 @@
       return CURLE_BAD_FUNCTION_ARGUMENT;
     if(s->postfieldsize < arg &&
        s->postfields == s->str[STRING_COPYPOSTFIELDS]) {
-      Curl_safefree(s->str[STRING_COPYPOSTFIELDS]);
+      curlx_safefree(s->str[STRING_COPYPOSTFIELDS]);
       s->postfields = NULL;
     }
     s->postfieldsize = arg;
@@ -1439,7 +1440,7 @@
     s->opt_no_body = FALSE; /* this is implied */
 #ifndef CURL_DISABLE_FORM_API
     Curl_mime_cleanpart(data->state.formp);
-    Curl_safefree(data->state.formp);
+    curlx_safefree(data->state.formp);
     data->state.mimepost = NULL;
 #endif
   }
@@ -1455,6 +1456,14 @@
   CURLcode result = CURLE_OK;
   struct UserDefined *s = &data->set;
   switch(option) {
+  case CURLOPT_CURLU:
+    /*
+     * pass CURLU to set URL
+     */
+    Curl_bufref_free(&data->state.url);
+    curlx_safefree(s->str[STRING_SET_URL]);
+    s->uh = va_arg(param, CURLU *);
+    break;
 #ifndef CURL_DISABLE_HTTP
 #ifndef CURL_DISABLE_FORM_API
   case CURLOPT_HTTPPOST:
@@ -1465,7 +1474,7 @@
     s->method = HTTPREQ_POST_FORM;
     s->opt_no_body = FALSE; /* this is implied */
     Curl_mime_cleanpart(data->state.formp);
-    Curl_safefree(data->state.formp);
+    curlx_safefree(data->state.formp);
     data->state.mimepost = NULL;
     break;
 #endif /* !CURL_DISABLE_FORM_API */
@@ -1490,67 +1499,17 @@
   case CURLOPT_SHARE: {
     struct Curl_share *set = va_arg(param, struct Curl_share *);
 
-    /* disconnect from old share, if any */
-    if(data->share) {
-      Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE);
+    /* disconnect from old share, if any and possible */
+    result = Curl_share_easy_unlink(data);
+    if(result)
+      return result;
 
-#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
-      if(data->share->cookies == data->cookies)
-        data->cookies = NULL;
-#endif
-
-#ifndef CURL_DISABLE_HSTS
-      if(data->share->hsts == data->hsts)
-        data->hsts = NULL;
-#endif
-#ifdef USE_LIBPSL
-      if(data->psl == &data->share->psl)
-        data->psl = data->multi ? &data->multi->psl : NULL;
-#endif
-      if(data->share->specifier & (1 << CURL_LOCK_DATA_DNS)) {
-        Curl_resolv_unlink(data, &data->state.dns[0]);
-        Curl_resolv_unlink(data, &data->state.dns[1]);
-      }
-
-      data->share->dirty--;
-
-      Curl_share_unlock(data, CURL_LOCK_DATA_SHARE);
-      data->share = NULL;
+    /* use new share if it set */
+    if(GOOD_SHARE_HANDLE(set)) {
+      result = Curl_share_easy_link(data, set);
+      if(result)
+        return result;
     }
-
-    if(GOOD_SHARE_HANDLE(set))
-      /* use new share if it set */
-      data->share = set;
-    if(data->share) {
-
-      Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE);
-
-      data->share->dirty++;
-
-#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
-      if(data->share->cookies) {
-        /* use shared cookie list, first free own one if any */
-        Curl_cookie_cleanup(data->cookies);
-        /* enable cookies since we now use a share that uses cookies! */
-        data->cookies = data->share->cookies;
-      }
-#endif /* CURL_DISABLE_HTTP */
-#ifndef CURL_DISABLE_HSTS
-      if(data->share->hsts) {
-        /* first free the private one if any */
-        Curl_hsts_cleanup(&data->hsts);
-        data->hsts = data->share->hsts;
-      }
-#endif
-#ifdef USE_LIBPSL
-      if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL))
-        data->psl = &data->share->psl;
-#endif
-
-      Curl_share_unlock(data, CURL_LOCK_DATA_SHARE);
-    }
-    /* check for host cache not needed,
-     * it will be done by curl_easy_perform */
     break;
   }
 
@@ -1664,6 +1623,18 @@
 #endif
 
 #ifndef CURL_DISABLE_PROXY
+static CURLcode setproxy(struct Curl_easy *data, const char *proxy)
+{
+  if((data->set.str[STRING_PROXY] && proxy) &&
+     /* there was one set, is this a new one? */
+     !strcmp(data->set.str[STRING_PROXY], proxy))
+    return CURLE_OK; /* same one as before */
+
+  Curl_auth_digest_cleanup(&data->state.proxydigest);
+  memset(&data->state.authproxy, 0, sizeof(data->state.authproxy));
+  return Curl_setstropt(&data->set.str[STRING_PROXY], proxy);
+}
+
 static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option,
                                   const char *ptr)
 {
@@ -1680,12 +1651,12 @@
 
     /* URL decode the components */
     if(!result && u) {
-      Curl_safefree(s->str[STRING_PROXYUSERNAME]);
+      curlx_safefree(s->str[STRING_PROXYUSERNAME]);
       result = Curl_urldecode(u, 0, &s->str[STRING_PROXYUSERNAME], NULL,
                               REJECT_ZERO);
     }
     if(!result && p) {
-      Curl_safefree(s->str[STRING_PROXYPASSWORD]);
+      curlx_safefree(s->str[STRING_PROXYPASSWORD]);
       result = Curl_urldecode(p, 0, &s->str[STRING_PROXYPASSWORD], NULL,
                               REJECT_ZERO);
     }
@@ -1752,14 +1723,14 @@
     /*
      * Set proxy server:port to use as proxy.
      *
-     * If the proxy is set to "" (and CURLOPT_SOCKS_PROXY is set to "" or NULL)
-     * we explicitly say that we do not want to use a proxy
-     * (even though there might be environment variables saying so).
+     * If the proxy is set to "" (and CURLOPT_PRE_PROXY is set to "" or NULL)
+     * we explicitly say that we do not want to use a proxy (even though there
+     * might be environment variables saying so).
      *
      * Setting it to NULL, means no proxy but allows the environment variables
-     * to decide for us (if CURLOPT_SOCKS_PROXY setting it to NULL).
+     * to decide for us (if CURLOPT_PRE_PROXY setting it to NULL).
      */
-    return Curl_setstropt(&s->str[STRING_PROXY], ptr);
+    return setproxy(data, ptr);
   case CURLOPT_PRE_PROXY:
     /*
      * Set proxy server:port to use as SOCKS proxy.
@@ -1950,7 +1921,7 @@
      */
     s->postfields = ptr;
     /* Release old copied data. */
-    Curl_safefree(s->str[STRING_COPYPOSTFIELDS]);
+    curlx_safefree(s->str[STRING_COPYPOSTFIELDS]);
     s->method = HTTPREQ_POST;
     break;
 #endif /* !CURL_DISABLE_HTTP || !CURL_DISABLE_MQTT */
@@ -2000,7 +1971,6 @@
      * String to set in the HTTP Referer: field.
      */
     result = Curl_setstropt(&s->str[STRING_SET_REFERER], ptr);
-    Curl_bufref_set(&data->state.referer, s->str[STRING_SET_REFERER], 0, NULL);
     break;
 
   case CURLOPT_USERAGENT:
@@ -2218,14 +2188,6 @@
      * What range of the file you want to transfer
      */
     return Curl_setstropt(&s->str[STRING_SET_RANGE], ptr);
-  case CURLOPT_CURLU:
-    /*
-     * pass CURLU to set URL
-     */
-    Curl_bufref_free(&data->state.url);
-    Curl_safefree(s->str[STRING_SET_URL]);
-    s->uh = (CURLU *)ptr;
-    break;
   case CURLOPT_SSLCERT:
     /*
      * String that holds filename of the SSL certificate to use
@@ -2434,30 +2396,18 @@
     break;
 #endif
 #endif
-#ifdef CURLRES_ARES
+#ifdef USE_RESOLV_ARES
   case CURLOPT_DNS_SERVERS:
-    result = Curl_setstropt(&s->str[STRING_DNS_SERVERS], ptr);
-    if(result)
-      break;
-    return Curl_async_ares_set_dns_servers(data);
+    return Curl_setstropt(&s->str[STRING_DNS_SERVERS], ptr);
 
   case CURLOPT_DNS_INTERFACE:
-    result = Curl_setstropt(&s->str[STRING_DNS_INTERFACE], ptr);
-    if(result)
-      break;
-    return Curl_async_ares_set_dns_interface(data);
+    return Curl_setstropt(&s->str[STRING_DNS_INTERFACE], ptr);
 
   case CURLOPT_DNS_LOCAL_IP4:
-    result = Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP4], ptr);
-    if(result)
-      break;
-    return Curl_async_ares_set_dns_local_ip4(data);
+    return Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP4], ptr);
 
   case CURLOPT_DNS_LOCAL_IP6:
-    result = Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP6], ptr);
-    if(result)
-      break;
-    return Curl_async_ares_set_dns_local_ip6(data);
+    return Curl_setstropt(&s->str[STRING_DNS_LOCAL_IP6], ptr);
 
 #endif
 #ifdef USE_UNIX_SOCKETS
@@ -2783,7 +2733,7 @@
     if(s->postfieldsize < offt &&
        s->postfields == s->str[STRING_COPYPOSTFIELDS]) {
       /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */
-      Curl_safefree(s->str[STRING_COPYPOSTFIELDS]);
+      curlx_safefree(s->str[STRING_COPYPOSTFIELDS]);
       s->postfields = NULL;
     }
     s->postfieldsize = offt;
@@ -2939,6 +2889,7 @@
     case CURLOPT_SHARE:            /* CURLSH * */
     case CURLOPT_STREAM_DEPENDS:   /* CURL * */
     case CURLOPT_STREAM_DEPENDS_E: /* CURL * */
+    case CURLOPT_CURLU:            /* CURLU * */
       return setopt_pointers(data, option, param);
     default:
       break;
@@ -2962,21 +2913,21 @@
  */
 
 #undef curl_easy_setopt
-CURLcode curl_easy_setopt(CURL *d, CURLoption tag, ...)
+CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...)
 {
   va_list arg;
   CURLcode result;
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
 
   if(!data)
     return CURLE_BAD_FUNCTION_ARGUMENT;
 
-  va_start(arg, tag);
+  va_start(arg, option);
 
-  result = Curl_vsetopt(data, tag, arg);
+  result = Curl_vsetopt(data, option, arg);
 
   va_end(arg);
   if(result == CURLE_BAD_FUNCTION_ARGUMENT)
-    failf(data, "setopt 0x%x got bad argument", tag);
+    failf(data, "setopt 0x%x got bad argument", option);
   return result;
 }
diff --git a/Utilities/cmcurl/lib/setup-os400.h b/Utilities/cmcurl/lib/setup-os400.h
index 084cd9f..b5bd733 100644
--- a/Utilities/cmcurl/lib/setup-os400.h
+++ b/Utilities/cmcurl/lib/setup-os400.h
@@ -67,22 +67,22 @@
 
 /* GSSAPI wrappers. */
 
-extern OM_uint32 Curl_gss_import_name_a(OM_uint32 * minor_status,
+extern OM_uint32 Curl_gss_import_name_a(OM_uint32 *minor_status,
                                         gss_buffer_t in_name,
                                         gss_OID in_name_type,
-                                        gss_name_t * out_name);
+                                        gss_name_t *out_name);
 #define gss_import_name         Curl_gss_import_name_a
 
-extern OM_uint32 Curl_gss_display_status_a(OM_uint32 * minor_status,
+extern OM_uint32 Curl_gss_display_status_a(OM_uint32 *minor_status,
                                            OM_uint32 status_value,
                                            int status_type, gss_OID mech_type,
-                                           gss_msg_ctx_t * message_context,
+                                           gss_msg_ctx_t *message_context,
                                            gss_buffer_t status_string);
 #define gss_display_status      Curl_gss_display_status_a
 
-extern OM_uint32 Curl_gss_init_sec_context_a(OM_uint32 * minor_status,
+extern OM_uint32 Curl_gss_init_sec_context_a(OM_uint32 *minor_status,
                                              gss_cred_id_t cred_handle,
-                                             gss_ctx_id_t * context_handle,
+                                             gss_ctx_id_t *context_handle,
                                              gss_name_t target_name,
                                              gss_OID mech_type,
                                              gss_flags_t req_flags,
@@ -90,10 +90,10 @@
                                              gss_channel_bindings_t
                                              input_chan_bindings,
                                              gss_buffer_t input_token,
-                                             gss_OID * actual_mech_type,
+                                             gss_OID *actual_mech_type,
                                              gss_buffer_t output_token,
-                                             gss_flags_t * ret_flags,
-                                             OM_uint32 * time_rec);
+                                             gss_flags_t *ret_flags,
+                                             OM_uint32 *time_rec);
 #define gss_init_sec_context    Curl_gss_init_sec_context_a
 
 extern OM_uint32 Curl_gss_delete_sec_context_a(OM_uint32 *minor_status,
diff --git a/Utilities/cmcurl/lib/sha256.c b/Utilities/cmcurl/lib/sha256.c
index 7b06365..d97f45f 100644
--- a/Utilities/cmcurl/lib/sha256.c
+++ b/Utilities/cmcurl/lib/sha256.c
@@ -40,10 +40,11 @@
 /* Please keep the SSL backend-specific #if branches in this order:
  *
  * 1. USE_OPENSSL
- * 2. USE_GNUTLS
- * 3. USE_MBEDTLS
- * 4. USE_COMMON_CRYPTO
- * 5. USE_WIN32_CRYPTO
+ * 2. USE_WOLFSSL
+ * 3. USE_GNUTLS
+ * 4. USE_MBEDTLS
+ * 5. USE_COMMON_CRYPTO
+ * 6. USE_WIN32_CRYPTO
  *
  * This ensures that the same SSL branch gets activated throughout this source
  * file even if multiple backends are enabled at the same time.
@@ -86,6 +87,31 @@
   EVP_MD_CTX_destroy(ctx->openssl_ctx);
 }
 
+#elif defined(USE_WOLFSSL)
+#include <wolfssl/options.h>
+#include <wolfssl/wolfcrypt/sha256.h>
+
+typedef struct wc_Sha256 my_sha256_ctx;
+
+static CURLcode my_sha256_init(void *in)
+{
+  if(wc_InitSha256(in))
+    return CURLE_FAILED_INIT;
+  return CURLE_OK;
+}
+
+static void my_sha256_update(void *in,
+                             const unsigned char *data,
+                             unsigned int length)
+{
+  (void)wc_Sha256Update(in, data, (word32)length);
+}
+
+static void my_sha256_final(unsigned char *digest, void *in)
+{
+  (void)wc_Sha256Final(in, digest);
+}
+
 #elif defined(USE_GNUTLS)
 #include <nettle/sha.h>
 
@@ -229,7 +255,7 @@
     (a)[0] = (unsigned char)((((unsigned long)(val)) >> 24) & 0xff); \
     (a)[1] = (unsigned char)((((unsigned long)(val)) >> 16) & 0xff); \
     (a)[2] = (unsigned char)((((unsigned long)(val)) >>  8) & 0xff); \
-    (a)[3] = (unsigned char) (((unsigned long)(val)) & 0xff);        \
+    (a)[3] = (unsigned char)(((unsigned long)(val)) & 0xff);         \
   } while(0)
 
 #define WPA_PUT_BE64(a, val)                            \
@@ -452,14 +478,19 @@
  * Returns CURLE_OK on success.
  */
 CURLcode Curl_sha256it(unsigned char *output, const unsigned char *input,
-                       const size_t len)
+                       size_t len)
 {
   CURLcode result;
   my_sha256_ctx ctx;
 
   result = my_sha256_init(&ctx);
   if(!result) {
-    my_sha256_update(&ctx, input, curlx_uztoui(len));
+    do {
+      unsigned int ilen = (unsigned int)CURLMIN(len, UINT_MAX);
+      my_sha256_update(&ctx, input, ilen);
+      len -= ilen;
+      input += ilen;
+    } while(len);
     my_sha256_final(output, &ctx);
   }
   return result;
diff --git a/Utilities/cmcurl/lib/slist.c b/Utilities/cmcurl/lib/slist.c
index c6dcdc1..83fd291 100644
--- a/Utilities/cmcurl/lib/slist.c
+++ b/Utilities/cmcurl/lib/slist.c
@@ -132,7 +132,7 @@
   item = list;
   do {
     next = item->next;
-    Curl_safefree(item->data);
+    curlx_safefree(item->data);
     curlx_free(item);
     item = next;
   } while(next);
diff --git a/Utilities/cmcurl/lib/smb.c b/Utilities/cmcurl/lib/smb.c
index 00297ad..6a97d2e 100644
--- a/Utilities/cmcurl/lib/smb.c
+++ b/Utilities/cmcurl/lib/smb.c
@@ -25,7 +25,7 @@
 #include "curl_setup.h"
 #include "urldata.h"
 
-#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE)
+#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE)
 
 #ifdef HAVE_ARPA_INET_H
 #include <arpa/inet.h>  /* for htons() */
@@ -376,7 +376,7 @@
   struct smb_request *req = entry;
   (void)key;
   (void)klen;
-  Curl_safefree(req->path);
+  curlx_safefree(req->path);
   curlx_free(req);
 }
 
@@ -385,10 +385,10 @@
   struct smb_conn *smbc = entry;
   (void)key;
   (void)klen;
-  Curl_safefree(smbc->share);
-  Curl_safefree(smbc->domain);
-  Curl_safefree(smbc->recv_buf);
-  Curl_safefree(smbc->send_buf);
+  curlx_safefree(smbc->share);
+  curlx_safefree(smbc->domain);
+  curlx_safefree(smbc->recv_buf);
+  curlx_safefree(smbc->send_buf);
   curlx_free(smbc);
 }
 
@@ -406,7 +406,7 @@
     return result;
 
   /* Parse the path for the share */
-  Curl_safefree(smbc->share);
+  curlx_safefree(smbc->share);
   smbc->share = curlx_strdup((*path == '/' || *path == '\\')
                              ? path + 1 : path);
   curlx_free(path);
@@ -419,7 +419,7 @@
 
   /* The share must be present */
   if(!slash) {
-    Curl_safefree(smbc->share);
+    curlx_safefree(smbc->share);
     failf(data, "missing share in URL path for SMB");
     return CURLE_URL_MALFORMAT;
   }
@@ -1208,7 +1208,7 @@
 /*
  * SMB handler interface
  */
-static const struct Curl_protocol Curl_protocol_smb = {
+const struct Curl_protocol Curl_protocol_smb = {
   smb_setup_connection,                 /* setup_connection */
   smb_do,                               /* do_it */
   ZERO_NULL,                            /* done */
@@ -1223,42 +1223,9 @@
   ZERO_NULL,                            /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
 
-#endif /* CURL_DISABLE_SMB && USE_CURL_NTLM_CORE && SIZEOF_CURL_OFF_T > 4 */
-
-/*
- * SMB handler interface
- */
-const struct Curl_scheme Curl_scheme_smb = {
-  "smb",                                /* scheme */
-#if defined(CURL_DISABLE_SMB) || !defined(USE_CURL_NTLM_CORE)
-  ZERO_NULL,
-#else
-  &Curl_protocol_smb,
-#endif
-  CURLPROTO_SMB,                        /* protocol */
-  CURLPROTO_SMB,                        /* family */
-  PROTOPT_CONN_REUSE,                   /* flags */
-  PORT_SMB,                             /* defport */
-};
-
-/*
- * SMBS handler interface
- */
-const struct Curl_scheme Curl_scheme_smbs = {
-  "smbs",                               /* scheme */
-#if defined(CURL_DISABLE_SMB) || !defined(USE_CURL_NTLM_CORE) || \
-  !defined(USE_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_smb,
-#endif
-  CURLPROTO_SMBS,                       /* protocol */
-  CURLPROTO_SMB,                        /* family */
-  PROTOPT_SSL | PROTOPT_CONN_REUSE,     /* flags */
-  PORT_SMBS,                            /* defport */
-};
+#endif /* CURL_ENABLE_SMB && USE_CURL_NTLM_CORE && SIZEOF_CURL_OFF_T > 4 */
diff --git a/Utilities/cmcurl/lib/smb.h b/Utilities/cmcurl/lib/smb.h
index d375404..5a92ad1 100644
--- a/Utilities/cmcurl/lib/smb.h
+++ b/Utilities/cmcurl/lib/smb.h
@@ -24,7 +24,8 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_smb;
-extern const struct Curl_scheme Curl_scheme_smbs;
+#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE)
+extern const struct Curl_protocol Curl_protocol_smb;
+#endif
 
 #endif /* HEADER_CURL_SMB_H */
diff --git a/Utilities/cmcurl/lib/smtp.c b/Utilities/cmcurl/lib/smtp.c
index 6088985..b5c425c 100644
--- a/Utilities/cmcurl/lib/smtp.c
+++ b/Utilities/cmcurl/lib/smtp.c
@@ -431,7 +431,7 @@
     ctx->eos = TRUE;
   }
   *peos = (bool)ctx->eos;
-  DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d",
+  DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zu, %d",
                blen, result, *pnread, *peos));
   return result;
 }
@@ -485,7 +485,7 @@
                            const char *line, size_t len, int *resp)
 {
   struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN);
-  bool result = FALSE;
+  bool end = FALSE;
   (void)data;
 
   DEBUGASSERT(smtpc);
@@ -504,7 +504,7 @@
     char tmpline[6];
     curl_off_t code;
     const char *p = tmpline;
-    result = TRUE;
+    end = TRUE;
     memcpy(tmpline, line, (len == 5 ? 5 : 3));
     tmpline[len == 5 ? 5 : 3] = 0;
     if(curlx_str_number(&p, &code, len == 5 ? 99999 : 999))
@@ -518,11 +518,11 @@
   /* Do we have a multiline (continuation) response? */
   else if(line[3] == '-' &&
           (smtpc->state == SMTP_EHLO || smtpc->state == SMTP_COMMAND)) {
-    result = TRUE;
+    end = TRUE;
     *resp = 1;  /* Internal response code */
   }
 
-  return result;
+  return end;
 }
 
 /***********************************************************************
@@ -1724,7 +1724,7 @@
     return CURLE_OK;
 
   /* Cleanup our per-request based variables */
-  Curl_safefree(smtp->custom);
+  curlx_safefree(smtp->custom);
 
   if(status) {
     connclose(conn, "SMTP done with bad status"); /* marked for closure */
@@ -1953,7 +1953,7 @@
   (void)key;
   (void)klen;
   Curl_pp_disconnect(&smtpc->pp);
-  Curl_safefree(smtpc->domain);
+  curlx_safefree(smtpc->domain);
   curlx_free(smtpc);
 }
 
@@ -1984,7 +1984,7 @@
 /*
  * SMTP protocol handler.
  */
-static const struct Curl_protocol Curl_protocol_smtp = {
+const struct Curl_protocol Curl_protocol_smtp = {
   smtp_setup_connection,            /* setup_connection */
   smtp_do,                          /* do_it */
   smtp_done,                        /* done */
@@ -1999,43 +1999,9 @@
   smtp_disconnect,                  /* disconnect */
   ZERO_NULL,                        /* write_resp */
   ZERO_NULL,                        /* write_resp_hd */
-  ZERO_NULL,                        /* connection_check */
+  ZERO_NULL,                        /* connection_is_dead */
   ZERO_NULL,                        /* attach connection */
   ZERO_NULL,                        /* follow */
 };
 
 #endif /* CURL_DISABLE_SMTP */
-
-/*
- * SMTP protocol handler.
- */
-const struct Curl_scheme Curl_scheme_smtp = {
-  "smtp",                           /* scheme */
-#ifdef CURL_DISABLE_SMTP
-  ZERO_NULL,
-#else
-  &Curl_protocol_smtp,
-#endif
-  CURLPROTO_SMTP,                   /* protocol */
-  CURLPROTO_SMTP,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */
-  PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE,
-  PORT_SMTP,                        /* defport */
-};
-
-/*
- * SMTPS protocol handler.
- */
-const struct Curl_scheme Curl_scheme_smtps = {
-  "smtps",                          /* scheme */
-#if defined(CURL_DISABLE_SMTP) || !defined(USE_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_smtp,
-#endif
-  CURLPROTO_SMTPS,                  /* protocol */
-  CURLPROTO_SMTP,                   /* family */
-  PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
-  PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE,
-  PORT_SMTPS,                       /* defport */
-};
diff --git a/Utilities/cmcurl/lib/smtp.h b/Utilities/cmcurl/lib/smtp.h
index 39f8d78..75b8197 100644
--- a/Utilities/cmcurl/lib/smtp.h
+++ b/Utilities/cmcurl/lib/smtp.h
@@ -23,7 +23,8 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_smtp;
-extern const struct Curl_scheme Curl_scheme_smtps;
+#ifndef CURL_DISABLE_SMTP
+extern const struct Curl_protocol Curl_protocol_smtp;
+#endif
 
 #endif /* HEADER_CURL_SMTP_H */
diff --git a/Utilities/cmcurl/lib/socketpair.h b/Utilities/cmcurl/lib/socketpair.h
index 2aa1733..0427e72 100644
--- a/Utilities/cmcurl/lib/socketpair.h
+++ b/Utilities/cmcurl/lib/socketpair.h
@@ -37,7 +37,7 @@
 CURLcode Curl_wakeup_consume(curl_socket_t socks[2], bool all);
 
 #else
-#define Curl_wakeup_destroy(x)  Curl_nop_stmt
+#define Curl_wakeup_destroy(x) Curl_nop_stmt
 #endif
 
 #endif /* HEADER_CURL_SOCKETPAIR_H */
diff --git a/Utilities/cmcurl/lib/socks.c b/Utilities/cmcurl/lib/socks.c
index b7c3bd6..b3da5be 100644
--- a/Utilities/cmcurl/lib/socks.c
+++ b/Utilities/cmcurl/lib/socks.c
@@ -38,6 +38,7 @@
 #include "curl_trc.h"
 #include "select.h"
 #include "cfilters.h"
+#include "cf-dns.h"
 #include "connect.h"
 #include "socks.h"
 #include "curlx/inet_pton.h"
@@ -94,18 +95,21 @@
 #define SOCKS_CHUNKS        1
 
 
-struct socks_state {
+struct socks_ctx {
   enum socks_state_t state;
   struct bufq iobuf;
-  const char *hostname;
-  int remote_port;
-  const char *proxy_user;
-  const char *proxy_password;
+  uint16_t remote_port;
+  const char *user;
+  const char *passwd;
   CURLproxycode presult;
+  uint32_t resolv_id;
+  uint8_t ip_version;
+  uint8_t proxy_type;
   unsigned char version;
   BIT(resolve_local);
   BIT(start_resolving);
   BIT(socks4a);
+  char hostname[1];
 };
 
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
@@ -127,13 +131,15 @@
   *pnread = 0;
   for(;;) {
     timediff_t timeout_ms = Curl_timeleft_ms(data);
+    curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
+
     if(timeout_ms < 0) {
       /* we already got the timeout */
       return CURLE_OPERATION_TIMEDOUT;
     }
     if(!timeout_ms)
       timeout_ms = TIMEDIFF_T_MAX;
-    if(SOCKET_READABLE(cf->conn->sock[cf->sockindex], timeout_ms) <= 0)
+    if(SOCKET_READABLE(sock, timeout_ms) <= 0)
       return CURLE_OPERATION_TIMEDOUT;
     result = Curl_conn_cf_recv(cf->next, data, buf, blen, &nread);
     if(result == CURLE_AGAIN)
@@ -162,7 +168,7 @@
 #endif
 
 /* always use this function to change state, to make debugging easier */
-static void socksstate(struct socks_state *sx,
+static void socksstate(struct socks_ctx *sx,
                        struct Curl_cfilter *cf,
                        struct Curl_easy *data,
                        enum socks_state_t state
@@ -189,7 +195,7 @@
 #endif
 }
 
-static CURLproxycode socks_failed(struct socks_state *sx,
+static CURLproxycode socks_failed(struct socks_ctx *sx,
                                   struct Curl_cfilter *cf,
                                   struct Curl_easy *data,
                                   CURLproxycode presult)
@@ -199,7 +205,7 @@
   return presult;
 }
 
-static CURLproxycode socks_flush(struct socks_state *sx,
+static CURLproxycode socks_flush(struct socks_ctx *sx,
                                  struct Curl_cfilter *cf,
                                  struct Curl_easy *data,
                                  bool *done)
@@ -223,7 +229,7 @@
   return CURLPX_OK;
 }
 
-static CURLproxycode socks_recv(struct socks_state *sx,
+static CURLproxycode socks_recv(struct socks_ctx *sx,
                                 struct Curl_cfilter *cf,
                                 struct Curl_easy *data,
                                 size_t min_bytes,
@@ -257,7 +263,7 @@
   return CURLPX_OK;
 }
 
-static CURLproxycode socks4_req_add_hd(struct socks_state *sx,
+static CURLproxycode socks4_req_add_hd(struct socks_ctx *sx,
                                        struct Curl_easy *data)
 {
   unsigned char buf[4];
@@ -276,14 +282,14 @@
   return CURLPX_OK;
 }
 
-static CURLproxycode socks4_req_add_user(struct socks_state *sx,
+static CURLproxycode socks4_req_add_user(struct socks_ctx *sx,
                                          struct Curl_easy *data)
 {
   CURLcode result;
   size_t nwritten;
 
-  if(sx->proxy_user) {
-    size_t plen = strlen(sx->proxy_user);
+  if(sx->user) {
+    size_t plen = strlen(sx->user);
     if(plen > 255) {
       /* there is no real size limit to this field in the protocol, but
          SOCKS5 limits the proxy user field to 255 bytes and it seems likely
@@ -292,7 +298,7 @@
       return CURLPX_LONG_USER;
     }
     /* add proxy name WITH trailing zero */
-    result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_user, plen + 1,
+    result = Curl_bufq_cwrite(&sx->iobuf, sx->user, plen + 1,
                               &nwritten);
     if(result || (nwritten != (plen + 1)))
       return CURLPX_SEND_REQUEST;
@@ -307,73 +313,58 @@
   return CURLPX_OK;
 }
 
-static CURLproxycode socks4_resolving(struct socks_state *sx,
+static CURLproxycode socks4_resolving(struct socks_ctx *sx,
                                       struct Curl_cfilter *cf,
                                       struct Curl_easy *data,
                                       bool *done)
 {
-  struct Curl_dns_entry *dns = NULL;
-  struct Curl_addrinfo *hp = NULL;
+  const struct Curl_addrinfo *ai = NULL;
   CURLcode result;
   size_t nwritten;
+  bool dns_done;
 
   *done = FALSE;
   if(sx->start_resolving) {
     /* need to resolve hostname to add destination address */
     sx->start_resolving = FALSE;
-    DEBUGASSERT(sx->hostname && *sx->hostname);
-
-    result = Curl_resolv(data, sx->hostname, sx->remote_port,
-                         cf->conn->ip_version, TRUE, &dns);
-    if(result == CURLE_AGAIN) {
-      CURL_TRC_CF(data, cf, "SOCKS4 non-blocking resolve of %s", sx->hostname);
-      return CURLPX_OK;
+    result = Curl_cf_dns_insert_after(
+      cf, data, Curl_resolv_dns_queries(data, sx->ip_version),
+      sx->hostname, sx->remote_port, TRNSPRT_TCP, TRUE);
+    if(result) {
+      failf(data, "unable to create DNS filter for socks");
+      return CURLPX_UNKNOWN_FAIL;
     }
-    else if(result)
-      return CURLPX_RESOLVE_HOST;
-  }
-  else {
-    /* check if we have the name resolved by now */
-    result = Curl_resolv_check(data, &dns);
-    if(!result && !dns)
-      return CURLPX_OK;
   }
 
-  if(result || !dns) {
-    failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", sx->hostname);
-    if(dns)
-      Curl_resolv_unlink(data, &dns);
+  /* resolve the hostname by connecting the DNS filter */
+  result = Curl_conn_cf_connect(cf->next, data, &dns_done);
+  if(result) {
+    failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.",
+          sx->hostname);
     return CURLPX_RESOLVE_HOST;
   }
+  else if(!dns_done)
+    return CURLPX_OK;
 
-  /*
-   * We cannot use 'hostent' as a struct that Curl_resolv() returns. It
-   * returns a Curl_addrinfo pointer that may not always look the same.
-   */
-  /* scan for the first IPv4 address */
-  hp = dns->addr;
-  while(hp && (hp->ai_family != AF_INET))
-    hp = hp->ai_next;
-
-  if(hp) {
+  ai = Curl_cf_dns_get_ai(cf->next, data, AF_INET, 0);
+  if(ai) {
     struct sockaddr_in *saddr_in;
     char ipbuf[64];
 
-    Curl_printable_address(hp, ipbuf, sizeof(ipbuf));
+    Curl_printable_address(ai, ipbuf, sizeof(ipbuf));
     CURL_TRC_CF(data, cf, "SOCKS4 connect to IPv4 %s (locally resolved)",
                 ipbuf);
 
-    saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr;
+    saddr_in = (struct sockaddr_in *)(void *)ai->ai_addr;
     result = Curl_bufq_write(&sx->iobuf,
                              (unsigned char *)&saddr_in->sin_addr.s_addr, 4,
                              &nwritten);
 
-    Curl_resolv_unlink(data, &dns); /* not used anymore from now on */
     if(result || (nwritten != 4))
       return CURLPX_SEND_REQUEST;
   }
   else {
-    Curl_resolv_unlink(data, &dns);
+    /* No ipv4 address resolved */
     failf(data, "SOCKS4 connection to %s not supported", sx->hostname);
     return CURLPX_RESOLVE_HOST;
   }
@@ -382,7 +373,7 @@
   return CURLPX_OK;
 }
 
-static CURLproxycode socks4_check_resp(struct socks_state *sx,
+static CURLproxycode socks4_check_resp(struct socks_ctx *sx,
                                        struct Curl_cfilter *cf,
                                        struct Curl_easy *data)
 {
@@ -431,7 +422,7 @@
           "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)"
           ", request rejected or failed.",
           resp[4], resp[5], resp[6], resp[7],
-          ((resp[2] << 8) | resp[3]), resp[1]);
+          (unsigned int)((resp[2] << 8) | resp[3]), resp[1]);
     return CURLPX_REQUEST_FAILED;
   case 92:
     failf(data,
@@ -439,7 +430,7 @@
           ", request rejected because SOCKS server cannot connect to "
           "identd on the client.",
           resp[4], resp[5], resp[6], resp[7],
-          ((resp[2] << 8) | resp[3]), resp[1]);
+          (unsigned int)((resp[2] << 8) | resp[3]), resp[1]);
     return CURLPX_IDENTD;
   case 93:
     failf(data,
@@ -447,14 +438,14 @@
           ", request rejected because the client program and identd "
           "report different user-ids.",
           resp[4], resp[5], resp[6], resp[7],
-          ((resp[2] << 8) | resp[3]), resp[1]);
+          (unsigned int)((resp[2] << 8) | resp[3]), resp[1]);
     return CURLPX_IDENTD_DIFFER;
   default:
     failf(data,
           "[SOCKS] cannot complete SOCKS4 connection to %u.%u.%u.%u:%u. (%u)"
           ", Unknown.",
           resp[4], resp[5], resp[6], resp[7],
-          ((resp[2] << 8) | resp[3]), resp[1]);
+          (unsigned int)((resp[2] << 8) | resp[3]), resp[1]);
     return CURLPX_UNKNOWN_FAIL;
   }
 }
@@ -471,7 +462,7 @@
  *   Nonsupport "Identification Protocol (RFC1413)"
  */
 static CURLproxycode socks4_connect(struct Curl_cfilter *cf,
-                                    struct socks_state *sx,
+                                    struct socks_ctx *sx,
                                     struct Curl_easy *data)
 {
   size_t nwritten;
@@ -489,16 +480,14 @@
   case SOCKS4_ST_START:
     Curl_bufq_reset(&sx->iobuf);
     sx->start_resolving = FALSE;
-    sx->socks4a = (cf->conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A);
+    sx->socks4a = (sx->proxy_type == CURLPROXY_SOCKS4A);
     sx->resolve_local = !sx->socks4a;
     sx->presult = CURLPX_OK;
 
     /* SOCKS4 can only do IPv4, insist! */
-    cf->conn->ip_version = CURL_IPRESOLVE_V4;
-    CURL_TRC_CF(data, cf, "SOCKS4%s communication to%s %s:%d",
-                sx->socks4a ? "a" : "",
-                cf->conn->bits.httpproxy ? " HTTP proxy" : "",
-                sx->hostname, sx->remote_port);
+    sx->ip_version = CURL_IPRESOLVE_V4;
+    CURL_TRC_CF(data, cf, "SOCKS4%s connecting to %s:%u",
+                sx->socks4a ? "a" : "", sx->hostname, sx->remote_port);
 
     /*
      * Compose socks4 request
@@ -591,7 +580,7 @@
 }
 
 static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf,
-                                      struct socks_state *sx,
+                                      struct socks_ctx *sx,
                                       struct Curl_easy *data)
 {
   const unsigned char auth = data->set.socks5auth;
@@ -613,7 +602,7 @@
           "CURLOPT_SOCKS5_AUTH: %u", auth);
   if(!(auth & CURLAUTH_BASIC))
     /* disable username/password auth */
-    sx->proxy_user = NULL;
+    sx->user = NULL;
 
   req[0] = 5;   /* version */
   nauths = 1;
@@ -624,7 +613,7 @@
     req[1 + nauths] = 1; /* GSS-API */
   }
 #endif
-  if(sx->proxy_user) {
+  if(sx->user) {
     ++nauths;
     req[1 + nauths] = 2; /* username/password */
   }
@@ -637,7 +626,7 @@
   return CURLPX_OK;
 }
 
-static CURLproxycode socks5_check_resp0(struct socks_state *sx,
+static CURLproxycode socks5_check_resp0(struct socks_ctx *sx,
                                         struct Curl_cfilter *cf,
                                         struct Curl_easy *data)
 {
@@ -689,7 +678,7 @@
 }
 
 static CURLproxycode socks5_auth_init(struct Curl_cfilter *cf,
-                                      struct socks_state *sx,
+                                      struct socks_ctx *sx,
                                       struct Curl_easy *data)
 {
   /* Needs username and password */
@@ -697,9 +686,9 @@
   unsigned char buf[2];
   CURLcode result;
 
-  if(sx->proxy_user && sx->proxy_password) {
-    ulen = strlen(sx->proxy_user);
-    plen = strlen(sx->proxy_password);
+  if(sx->user && sx->passwd) {
+    ulen = strlen(sx->user);
+    plen = strlen(sx->passwd);
     /* the lengths must fit in a single byte */
     if(ulen > 255) {
       failf(data, "Excessive username length for proxy auth");
@@ -724,7 +713,7 @@
   if(result || (nwritten != 2))
     return CURLPX_SEND_REQUEST;
   if(ulen) {
-    result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_user, ulen, &nwritten);
+    result = Curl_bufq_cwrite(&sx->iobuf, sx->user, ulen, &nwritten);
     if(result || (nwritten != ulen))
       return CURLPX_SEND_REQUEST;
   }
@@ -733,7 +722,7 @@
   if(result || (nwritten != 1))
     return CURLPX_SEND_REQUEST;
   if(plen) {
-    result = Curl_bufq_cwrite(&sx->iobuf, sx->proxy_password, plen, &nwritten);
+    result = Curl_bufq_cwrite(&sx->iobuf, sx->passwd, plen, &nwritten);
     if(result || (nwritten != plen))
       return CURLPX_SEND_REQUEST;
   }
@@ -741,7 +730,7 @@
   return CURLPX_OK;
 }
 
-static CURLproxycode socks5_check_auth_resp(struct socks_state *sx,
+static CURLproxycode socks5_check_auth_resp(struct socks_ctx *sx,
                                             struct Curl_cfilter *cf,
                                             struct Curl_easy *data)
 {
@@ -766,7 +755,7 @@
   return CURLPX_OK;
 }
 
-static CURLproxycode socks5_req1_init(struct socks_state *sx,
+static CURLproxycode socks5_req1_init(struct socks_ctx *sx,
                                       struct Curl_cfilter *cf,
                                       struct Curl_easy *data)
 {
@@ -790,7 +779,7 @@
 
   /* remote resolving, send what type+addr/string to resolve */
 #ifdef USE_IPV6
-  if(cf->conn->bits.ipv6_ip) {
+  if(strchr(sx->hostname, ':')) {
     desttype = 4;
     destination = ipbuf;
     destlen = 16;
@@ -806,6 +795,10 @@
   }
   else {
     const size_t hostname_len = strlen(sx->hostname);
+    /* socks5_req0_init() already rejects hostnames longer than 255 bytes, so
+       this cast to unsigned char is safe. Assert to guard against future
+       refactoring that might remove or reorder that earlier check. */
+    DEBUGASSERT(hostname_len <= 255);
     desttype = 3;
     destination = (const unsigned char *)sx->hostname;
     destlen = (unsigned char)hostname_len; /* one byte length */
@@ -826,18 +819,17 @@
   result = Curl_bufq_write(&sx->iobuf, req, 2, &nwritten);
   if(result || (nwritten != 2))
     return CURLPX_SEND_REQUEST;
-  CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (remotely resolved)",
+  CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%u (remotely resolved)",
               sx->hostname, sx->remote_port);
   return CURLPX_OK;
 }
 
-static CURLproxycode socks5_resolving(struct socks_state *sx,
+static CURLproxycode socks5_resolving(struct socks_ctx *sx,
                                       struct Curl_cfilter *cf,
                                       struct Curl_easy *data,
                                       bool *done)
 {
-  struct Curl_dns_entry *dns = NULL;
-  struct Curl_addrinfo *hp = NULL;
+  const struct Curl_addrinfo *ai = NULL;
   char dest[MAX_IPADR_LEN];  /* printable address */
   const unsigned char *destination = NULL;
   unsigned char desttype = 1, destlen = 4;
@@ -845,71 +837,62 @@
   CURLcode result;
   CURLproxycode presult = CURLPX_OK;
   size_t nwritten;
+  bool dns_done;
 
   *done = FALSE;
   if(sx->start_resolving) {
     /* need to resolve hostname to add destination address */
     sx->start_resolving = FALSE;
-    DEBUGASSERT(sx->hostname && *sx->hostname);
-
-    result = Curl_resolv(data, sx->hostname, sx->remote_port,
-                         cf->conn->ip_version, TRUE, &dns);
-    if(result == CURLE_AGAIN) {
-      CURL_TRC_CF(data, cf, "SOCKS5 non-blocking resolve of %s", sx->hostname);
-      return CURLPX_OK;
+    result = Curl_cf_dns_insert_after(
+      cf, data, Curl_resolv_dns_queries(data, sx->ip_version),
+      sx->hostname, sx->remote_port, TRNSPRT_TCP, TRUE);
+    if(result) {
+      failf(data, "unable to create DNS filter for socks");
+      return CURLPX_UNKNOWN_FAIL;
     }
-    else if(result)
-      return CURLPX_RESOLVE_HOST;
-  }
-  else {
-    /* check if we have the name resolved by now */
-    result = Curl_resolv_check(data, &dns);
-    if(!result && !dns)
-      return CURLPX_OK;
   }
 
-  if(result || !dns) {
+  /* resolve the hostname by connecting the DNS filter */
+  result = Curl_conn_cf_connect(cf->next, data, &dns_done);
+  if(result) {
     failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", sx->hostname);
-    presult = CURLPX_RESOLVE_HOST;
-    goto out;
+    return CURLPX_RESOLVE_HOST;
   }
+  else if(!dns_done)
+    return CURLPX_OK;
 
-  if(dns)
-    hp = dns->addr;
 #ifdef USE_IPV6
-  if(data->set.ipver != CURL_IPRESOLVE_WHATEVER) {
-    int wanted_family = data->set.ipver == CURL_IPRESOLVE_V4 ?
-      AF_INET : AF_INET6;
-    /* scan for the first proper address */
-    while(hp && (hp->ai_family != wanted_family))
-      hp = hp->ai_next;
-  }
+  if(data->set.ipver != CURL_IPRESOLVE_V4)
+    ai = Curl_cf_dns_get_ai(cf->next, data, AF_INET6, 0);
 #endif
-  if(!hp) {
+  if(!ai)
+    ai = Curl_cf_dns_get_ai(cf->next, data, AF_INET, 0);
+
+  if(!ai) {
     failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", sx->hostname);
     presult = CURLPX_RESOLVE_HOST;
     goto out;
   }
 
-  Curl_printable_address(hp, dest, sizeof(dest));
+  Curl_printable_address(ai, dest, sizeof(dest));
 
-  if(hp->ai_family == AF_INET) {
+  if(ai->ai_family == AF_INET) {
     struct sockaddr_in *saddr_in;
     desttype = 1; /* ATYP: IPv4 = 1 */
     destlen = 4;
-    saddr_in = (struct sockaddr_in *)(void *)hp->ai_addr;
+    saddr_in = (struct sockaddr_in *)(void *)ai->ai_addr;
     destination = (const unsigned char *)&saddr_in->sin_addr.s_addr;
-    CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%d (locally resolved)",
+    CURL_TRC_CF(data, cf, "SOCKS5 connect to %s:%u (locally resolved)",
                 dest, sx->remote_port);
   }
 #ifdef USE_IPV6
-  else if(hp->ai_family == AF_INET6) {
+  else if(ai->ai_family == AF_INET6) {
     struct sockaddr_in6 *saddr_in6;
     desttype = 4; /* ATYP: IPv6 = 4 */
     destlen = 16;
-    saddr_in6 = (struct sockaddr_in6 *)(void *)hp->ai_addr;
+    saddr_in6 = (struct sockaddr_in6 *)(void *)ai->ai_addr;
     destination = (const unsigned char *)&saddr_in6->sin6_addr.s6_addr;
-    CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%d (locally resolved)",
+    CURL_TRC_CF(data, cf, "SOCKS5 connect to [%s]:%u (locally resolved)",
                 dest, sx->remote_port);
   }
 #endif
@@ -941,13 +924,11 @@
   }
 
 out:
-  if(dns)
-    Curl_resolv_unlink(data, &dns);
   *done = (presult == CURLPX_OK);
   return presult;
 }
 
-static CURLproxycode socks5_recv_resp1(struct socks_state *sx,
+static CURLproxycode socks5_recv_resp1(struct socks_ctx *sx,
                                        struct Curl_cfilter *cf,
                                        struct Curl_easy *data,
                                        bool *done)
@@ -1046,7 +1027,7 @@
  * destination server.
  */
 static CURLproxycode socks5_connect(struct Curl_cfilter *cf,
-                                    struct socks_state *sx,
+                                    struct socks_ctx *sx,
                                     struct Curl_easy *data)
 {
   CURLproxycode presult;
@@ -1056,14 +1037,13 @@
   switch(sx->state) {
   case SOCKS_ST_INIT:
     sx->version = 5;
-    sx->resolve_local = (cf->conn->socks_proxy.proxytype == CURLPROXY_SOCKS5);
+    sx->resolve_local = (sx->proxy_type == CURLPROXY_SOCKS5);
     sxstate(sx, cf, data, SOCKS5_ST_START);
     FALLTHROUGH();
 
   case SOCKS5_ST_START:
-    if(cf->conn->bits.httpproxy)
-      CURL_TRC_CF(data, cf, "SOCKS5: connecting to HTTP proxy %s port %d",
-                  sx->hostname, sx->remote_port);
+    CURL_TRC_CF(data, cf, "SOCKS5: connecting to %s:%u",
+                sx->hostname, sx->remote_port);
     presult = socks5_req0_init(cf, sx, data);
     if(presult)
       return socks_failed(sx, cf, data, presult);
@@ -1198,13 +1178,11 @@
   }
 }
 
-static void socks_proxy_cf_free(struct Curl_cfilter *cf)
+static void socks_proxy_ctx_free(struct socks_ctx *ctx)
 {
-  struct socks_state *sxstate = cf->ctx;
-  if(sxstate) {
-    Curl_bufq_free(&sxstate->iobuf);
-    curlx_free(sxstate);
-    cf->ctx = NULL;
+  if(ctx) {
+    Curl_bufq_free(&ctx->iobuf);
+    curlx_free(ctx);
   }
 }
 
@@ -1219,11 +1197,9 @@
                                        struct Curl_easy *data,
                                        bool *done)
 {
-  CURLcode result;
-  struct connectdata *conn = cf->conn;
-  int sockindex = cf->sockindex;
-  struct socks_state *sx = cf->ctx;
+  struct socks_ctx *ctx = cf->ctx;
   CURLproxycode pxresult = CURLPX_OK;
+  CURLcode result;
 
   if(cf->connected) {
     *done = TRUE;
@@ -1234,47 +1210,19 @@
   if(result || !*done)
     return result;
 
-  if(!sx) {
-    cf->ctx = sx = curlx_calloc(1, sizeof(*sx));
-    if(!sx) {
-      result = CURLE_OUT_OF_MEMORY;
-      goto out;
-    }
-
-    /* for the secondary socket (FTP), use the "connect to host"
-     * but ignore the "connect to port" (use the secondary port)
-     */
-    sx->hostname =
-      conn->bits.httpproxy ?
-      conn->http_proxy.host.name :
-      conn->bits.conn_to_host ?
-      conn->conn_to_host.name :
-      sockindex == SECONDARYSOCKET ?
-      conn->secondaryhostname : conn->host.name;
-    sx->remote_port =
-      conn->bits.httpproxy ? (int)conn->http_proxy.port :
-      sockindex == SECONDARYSOCKET ? conn->secondary_port :
-      conn->bits.conn_to_port ? conn->conn_to_port :
-      conn->remote_port;
-    sx->proxy_user = conn->socks_proxy.user;
-    sx->proxy_password = conn->socks_proxy.passwd;
-    Curl_bufq_init2(&sx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS,
-                    BUFQ_OPT_SOFT_LIMIT);
-  }
-
-  switch(conn->socks_proxy.proxytype) {
+  switch(ctx->proxy_type) {
   case CURLPROXY_SOCKS5:
   case CURLPROXY_SOCKS5_HOSTNAME:
-    pxresult = socks5_connect(cf, sx, data);
+    pxresult = socks5_connect(cf, ctx, data);
     break;
 
   case CURLPROXY_SOCKS4:
   case CURLPROXY_SOCKS4A:
-    pxresult = socks4_connect(cf, sx, data);
+    pxresult = socks4_connect(cf, ctx, data);
     break;
 
   default:
-    failf(data, "unknown proxytype option given");
+    DEBUGASSERT(0); /* should not come here, checked it at creation time */
     result = CURLE_COULDNT_CONNECT;
     goto out;
   }
@@ -1284,7 +1232,7 @@
     data->info.pxcode = pxresult;
     goto out;
   }
-  else if(sx->state != SOCKS_ST_SUCCESS)
+  else if(ctx->state != SOCKS_ST_SUCCESS)
     goto out;
 
 #ifdef CURLVERBOSE
@@ -1292,22 +1240,25 @@
     struct ip_quadruple ipquad;
     bool is_ipv6;
     if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad))
-      infof(data, "Opened %sSOCKS connection from %s port %u to %s port %u "
+      infof(data, "Opened %sSOCKS connection from %s port %d to %s port %d "
             "(via %s port %u)",
-            (sockindex == SECONDARYSOCKET) ? "2nd " : "",
+            (cf->sockindex == SECONDARYSOCKET) ? "2nd " : "",
             ipquad.local_ip, ipquad.local_port,
-            sx->hostname, sx->remote_port,
+            ctx->hostname, ctx->remote_port,
             ipquad.remote_ip, ipquad.remote_port);
     else
       infof(data, "Opened %sSOCKS connection",
-            (sockindex == SECONDARYSOCKET) ? "2nd " : "");
+            (cf->sockindex == SECONDARYSOCKET) ? "2nd " : "");
   }
 #endif
-  socks_proxy_cf_free(cf);
   cf->connected = TRUE;
 
 out:
   *done = (bool)cf->connected;
+  if(*done || result) {
+    ctx->user = NULL;
+    ctx->passwd = NULL;
+  }
   return result;
 }
 
@@ -1315,7 +1266,7 @@
                                         struct Curl_easy *data,
                                         struct easy_pollset *ps)
 {
-  struct socks_state *sx = cf->ctx;
+  struct socks_ctx *sx = cf->ctx;
   CURLcode result = CURLE_OK;
 
   if(!cf->connected && sx) {
@@ -1342,24 +1293,24 @@
 static void socks_proxy_cf_close(struct Curl_cfilter *cf,
                                  struct Curl_easy *data)
 {
-  DEBUGASSERT(cf->next);
   cf->connected = FALSE;
-  socks_proxy_cf_free(cf);
-  cf->next->cft->do_close(cf->next, data);
+  if(cf->next)
+    cf->next->cft->do_close(cf->next, data);
 }
 
 static void socks_proxy_cf_destroy(struct Curl_cfilter *cf,
                                    struct Curl_easy *data)
 {
   (void)data;
-  socks_proxy_cf_free(cf);
+  socks_proxy_ctx_free(cf->ctx);
+  cf->ctx = NULL;
 }
 
 static CURLcode socks_cf_query(struct Curl_cfilter *cf,
                                struct Curl_easy *data,
                                int query, int *pres1, void *pres2)
 {
-  struct socks_state *sx = cf->ctx;
+  struct socks_ctx *sx = cf->ctx;
 
   switch(query) {
   case CF_QUERY_HOST_PORT:
@@ -1402,15 +1353,53 @@
 };
 
 CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
-                                          struct Curl_easy *data)
+                                          struct Curl_easy *data,
+                                          const char *hostname,
+                                          uint16_t port,
+                                          uint8_t ip_version,
+                                          uint8_t proxy_type,
+                                          const char *user,
+                                          const char *passwd)
 {
   struct Curl_cfilter *cf;
+  struct socks_ctx *ctx;
+  size_t hostlen = hostname ? strlen(hostname) : 0;
   CURLcode result;
 
-  (void)data;
-  result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL);
+  if(!hostlen)
+    return CURLE_FAILED_INIT;
+
+  switch(proxy_type) {
+  case CURLPROXY_SOCKS5:
+  case CURLPROXY_SOCKS5_HOSTNAME:
+  case CURLPROXY_SOCKS4:
+  case CURLPROXY_SOCKS4A:
+    break; /* all supported */
+  default:
+    failf(data, "unknown proxytype %d option given", proxy_type);
+    return CURLE_COULDNT_CONNECT;
+  }
+
+  /* NUL byte already part of struct size */
+  ctx = curlx_calloc(1, sizeof(*ctx) + hostlen);
+  if(!ctx) {
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  memcpy(ctx->hostname, hostname, hostlen);
+  ctx->remote_port = port;
+  ctx->ip_version = ip_version;
+  ctx->proxy_type = proxy_type;
+  ctx->user = user;
+  ctx->passwd = passwd;
+  Curl_bufq_init2(&ctx->iobuf, SOCKS_CHUNK_SIZE, SOCKS_CHUNKS,
+                  BUFQ_OPT_SOFT_LIMIT);
+
+  result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, ctx);
   if(!result)
     Curl_conn_cf_insert_after(cf_at, cf);
+  else
+    socks_proxy_ctx_free(ctx);
   return result;
 }
 
diff --git a/Utilities/cmcurl/lib/socks.h b/Utilities/cmcurl/lib/socks.h
index 520fb75..ea36832 100644
--- a/Utilities/cmcurl/lib/socks.h
+++ b/Utilities/cmcurl/lib/socks.h
@@ -46,8 +46,19 @@
                                       struct Curl_easy *data);
 #endif
 
+/* Insert a SOCKS filter after `cf_at` for connecting to `hostname`
+ * and `port` with optional credentials.
+ * Credentials are NOT duplicated and are
+ * expected to exist during connect phase.
+ */
 CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
-                                          struct Curl_easy *data);
+                                          struct Curl_easy *data,
+                                          const char *hostname,
+                                          uint16_t port,
+                                          uint8_t ip_version,
+                                          uint8_t proxy_type,
+                                          const char *user,
+                                          const char *passwd);
 
 extern struct Curl_cftype Curl_cft_socks_proxy;
 
diff --git a/Utilities/cmcurl/lib/socks_gssapi.c b/Utilities/cmcurl/lib/socks_gssapi.c
index 962fcd0..32db070 100644
--- a/Utilities/cmcurl/lib/socks_gssapi.c
+++ b/Utilities/cmcurl/lib/socks_gssapi.c
@@ -35,7 +35,7 @@
 #include "socks.h"
 #include "curlx/strdup.h"
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 #endif
@@ -106,7 +106,7 @@
   CURLcode code;
   size_t actualread;
   size_t nwritten;
-  int result;
+  CURLcode result;
   OM_uint32 gss_major_status, gss_minor_status, gss_status;
   OM_uint32 gss_ret_flags;
   int gss_conf_state, gss_enc;
@@ -156,7 +156,7 @@
                                        GSS_C_NT_HOSTBASED_SERVICE, &server);
   }
 
-  Curl_safefree(service.value);
+  curlx_safefree(service.value);
   service.length = 0;
 
   if(check_gss_err(data, gss_major_status,
@@ -183,7 +183,7 @@
                                                  &gss_ret_flags);
 
     if(gss_token != GSS_C_NO_BUFFER) {
-      Curl_safefree(gss_recv_token.value);
+      curlx_safefree(gss_recv_token.value);
       gss_recv_token.length = 0;
     }
     if(check_gss_err(data, gss_major_status,
@@ -228,7 +228,7 @@
     if(gss_major_status != GSS_S_CONTINUE_NEEDED)
       break;
 
-    /* analyse response */
+    /* analyze response */
 
     /*   GSS-API response looks like
      * +----+------+-----+----------------+
@@ -266,6 +266,13 @@
     memcpy(&us_length, socksreq + 2, sizeof(short));
     us_length = ntohs(us_length);
 
+    if(!us_length) {
+      failf(data, "Invalid zero-length GSS-API authentication token.");
+      gss_release_name(&gss_status, &server);
+      Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL);
+      return CURLE_COULDNT_CONNECT;
+    }
+
     gss_recv_token.length = us_length;
     gss_recv_token.value = curlx_malloc(gss_recv_token.length);
     if(!gss_recv_token.value) {
@@ -283,7 +290,7 @@
     if(result || (actualread != us_length)) {
       failf(data, "Failed to receive GSS-API authentication token.");
       gss_release_name(&gss_status, &server);
-      Curl_safefree(gss_recv_token.value);
+      curlx_safefree(gss_recv_token.value);
       gss_recv_token.length = 0;
       Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL);
       return CURLE_COULDNT_CONNECT;
@@ -385,14 +392,14 @@
                                 &gss_conf_state, &gss_w_token);
 
     if(check_gss_err(data, gss_major_status, gss_minor_status, "gss_wrap")) {
-      Curl_safefree(gss_send_token.value);
+      curlx_safefree(gss_send_token.value);
       gss_send_token.length = 0;
       gss_release_buffer(&gss_status, &gss_w_token);
       Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL);
       failf(data, "Failed to wrap GSS-API encryption value into token.");
       return CURLE_COULDNT_CONNECT;
     }
-    Curl_safefree(gss_send_token.value);
+    curlx_safefree(gss_send_token.value);
     gss_send_token.length = 0;
 
     us_length = htons((unsigned short)gss_w_token.length);
@@ -453,6 +460,12 @@
   memcpy(&us_length, socksreq + 2, sizeof(short));
   us_length = ntohs(us_length);
 
+  if(!us_length) {
+    failf(data, "Invalid zero-length GSS-API encryption token.");
+    Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL);
+    return CURLE_COULDNT_CONNECT;
+  }
+
   gss_recv_token.length = us_length;
   gss_recv_token.value = curlx_malloc(gss_recv_token.length);
   if(!gss_recv_token.value) {
@@ -464,7 +477,7 @@
 
   if(result || (actualread != us_length)) {
     failf(data, "Failed to receive GSS-API encryption type.");
-    Curl_safefree(gss_recv_token.value);
+    curlx_safefree(gss_recv_token.value);
     gss_recv_token.length = 0;
     Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL);
     return CURLE_COULDNT_CONNECT;
@@ -476,14 +489,14 @@
                                   0, GSS_C_QOP_DEFAULT);
 
     if(check_gss_err(data, gss_major_status, gss_minor_status, "gss_unwrap")) {
-      Curl_safefree(gss_recv_token.value);
+      curlx_safefree(gss_recv_token.value);
       gss_recv_token.length = 0;
       gss_release_buffer(&gss_status, &gss_w_token);
       Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL);
       failf(data, "Failed to unwrap GSS-API encryption value into token.");
       return CURLE_COULDNT_CONNECT;
     }
-    Curl_safefree(gss_recv_token.value);
+    curlx_safefree(gss_recv_token.value);
     gss_recv_token.length = 0;
 
     if(gss_w_token.length != 1) {
@@ -501,14 +514,14 @@
     if(gss_recv_token.length != 1) {
       failf(data, "Invalid GSS-API encryption response length (%zu).",
             gss_recv_token.length);
-      Curl_safefree(gss_recv_token.value);
+      curlx_safefree(gss_recv_token.value);
       gss_recv_token.length = 0;
       Curl_gss_delete_sec_context(&gss_status, &gss_context, NULL);
       return CURLE_COULDNT_CONNECT;
     }
 
     memcpy(socksreq, gss_recv_token.value, gss_recv_token.length);
-    Curl_safefree(gss_recv_token.value);
+    curlx_safefree(gss_recv_token.value);
     gss_recv_token.length = 0;
   }
 
@@ -526,7 +539,7 @@
   return CURLE_OK;
 }
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic pop
 #endif
 
diff --git a/Utilities/cmcurl/lib/socks_sspi.c b/Utilities/cmcurl/lib/socks_sspi.c
index cc0e043..385312a 100644
--- a/Utilities/cmcurl/lib/socks_sspi.c
+++ b/Utilities/cmcurl/lib/socks_sspi.c
@@ -159,7 +159,7 @@
                                              sspi_ret_flagsp, NULL);
 
     curlx_free(sname);
-    Curl_safefree(sspi_recv_token.pvBuffer);
+    curlx_safefree(sspi_recv_token.pvBuffer);
     sspi_recv_token.cbBuffer = 0;
 
     if(check_sspi_err(data, status, "InitializeSecurityContext")) {
@@ -196,7 +196,7 @@
       socks5_free_token(&sspi_send_token, CURLE_OK);
     sspi_send_token.cbBuffer = 0;
 
-    Curl_safefree(sspi_recv_token.pvBuffer);
+    curlx_safefree(sspi_recv_token.pvBuffer);
     sspi_recv_token.cbBuffer = 0;
 
     if(status != SEC_I_CONTINUE_NEEDED)
@@ -223,6 +223,11 @@
     memcpy(&us_length, socksreq + 2, sizeof(short));
     us_length = ntohs(us_length);
 
+    if(!us_length) {
+      failf(data, "Invalid zero-length SSPI authentication token.");
+      return CURLE_COULDNT_CONNECT;
+    }
+
     sspi_recv_token.cbBuffer = us_length;
     sspi_recv_token.pvBuffer = curlx_malloc(us_length);
 
@@ -247,9 +252,9 @@
 static CURLcode socks5_free(SecBuffer *sspi_w_token,
                             CURLcode result)
 {
-  Curl_safefree(sspi_w_token[0].pvBuffer);
-  Curl_safefree(sspi_w_token[1].pvBuffer);
-  Curl_safefree(sspi_w_token[2].pvBuffer);
+  curlx_safefree(sspi_w_token[0].pvBuffer);
+  curlx_safefree(sspi_w_token[1].pvBuffer);
+  curlx_safefree(sspi_w_token[2].pvBuffer);
   return result;
 }
 
@@ -400,6 +405,11 @@
   memcpy(&us_length, socksreq + 2, sizeof(short));
   us_length = ntohs(us_length);
 
+  if(!us_length) {
+    failf(data, "Invalid zero-length SSPI encryption token.");
+    return CURLE_COULDNT_CONNECT;
+  }
+
   sspi_w_token[0].cbBuffer = us_length;
   sspi_w_token[0].pvBuffer = curlx_malloc(us_length);
   if(!sspi_w_token[0].pvBuffer)
@@ -488,7 +498,7 @@
   if(result)
     goto error;
 
-  Curl_safefree(service_name);
+  curlx_safefree(service_name);
 
   status = Curl_pSecFn->QueryCredentialsAttributes(&cred_handle,
                                                    SECPKG_CRED_ATTR_NAMES,
diff --git a/Utilities/cmcurl/lib/telnet.c b/Utilities/cmcurl/lib/telnet.c
index 5bae99e..c5ce9c2 100644
--- a/Utilities/cmcurl/lib/telnet.c
+++ b/Utilities/cmcurl/lib/telnet.c
@@ -1266,9 +1266,9 @@
 
 #ifdef USE_WINSOCK
   /* We want to wait for both stdin and the socket. Since
-  ** the select() function in Winsock only works on sockets
-  ** we have to use the WaitForMultipleObjects() call.
-  */
+   * the select() function in Winsock only works on sockets
+   * we have to use the WaitForMultipleObjects() call.
+   */
 
   /* First, create a sockets event object */
   event_handle = WSACreateEvent();
@@ -1569,7 +1569,7 @@
 /*
  * TELNET protocol handler.
  */
-static const struct Curl_protocol Curl_protocol_telnet = {
+const struct Curl_protocol Curl_protocol_telnet = {
   ZERO_NULL,                            /* setup_connection */
   telnet_do,                            /* do_it */
   telnet_done,                          /* done */
@@ -1584,25 +1584,9 @@
   ZERO_NULL,                            /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
 
 #endif /* !CURL_DISABLE_TELNET */
-
-/*
- * TELNET protocol handler.
- */
-const struct Curl_scheme Curl_scheme_telnet = {
-  "telnet",                             /* scheme */
-#ifdef CURL_DISABLE_TELNET
-  ZERO_NULL,
-#else
-  &Curl_protocol_telnet,
-#endif
-  CURLPROTO_TELNET,                     /* protocol */
-  CURLPROTO_TELNET,                     /* family */
-  PROTOPT_NONE | PROTOPT_NOURLQUERY,    /* flags */
-  PORT_TELNET,                          /* defport */
-};
diff --git a/Utilities/cmcurl/lib/telnet.h b/Utilities/cmcurl/lib/telnet.h
index c106179..3848fff 100644
--- a/Utilities/cmcurl/lib/telnet.h
+++ b/Utilities/cmcurl/lib/telnet.h
@@ -23,6 +23,8 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_telnet;
+#ifndef CURL_DISABLE_TELNET
+extern const struct Curl_protocol Curl_protocol_telnet;
+#endif
 
 #endif /* HEADER_CURL_TELNET_H */
diff --git a/Utilities/cmcurl/lib/tftp.c b/Utilities/cmcurl/lib/tftp.c
index cc0769b..6cc672d 100644
--- a/Utilities/cmcurl/lib/tftp.c
+++ b/Utilities/cmcurl/lib/tftp.c
@@ -175,7 +175,7 @@
   /* Average reposting an ACK after 5 seconds */
   state->retry_max = (int)timeout / 5;
 
-  /* But bound the total number */
+  /* Bound the total number */
   if(state->retry_max < 3)
     state->retry_max = 3;
 
@@ -300,8 +300,8 @@
         return CURLE_TFTP_ILLEGAL;
       }
 
-      state->blksize = (int)blksize;
-      infof(data, "blksize parsed from OACK (%d) requested (%d)",
+      state->blksize = (unsigned int)blksize;
+      infof(data, "blksize parsed from OACK (%u) requested (%u)",
             state->blksize, state->requested_blksize);
     }
     else if(checkprefix(TFTP_OPTION_TSIZE, option)) {
@@ -370,9 +370,9 @@
       int rblock = getrpacketblock(&state->rpacket);
 
       if(rblock != state->block &&
-         /* There is a bug in tftpd-hpa that causes it to send us an ack for
-          * 65535 when the block number wraps to 0. So when we are expecting
-          * 0, also accept 65535. See
+         /* There is a bug in tftpd-hpa that causes it to send us an ACK for
+          * 65535 when the block number wraps to 0. To handle it, when we are
+          * expecting 0, also accept 65535. See
           * https://www.syslinux.org/archives/2010-September/015612.html
           * */
          !(state->block == 0 && rblock == 65535)) {
@@ -418,7 +418,7 @@
       return CURLE_OK;
     }
 
-    /* TFTP considers data block size < 512 bytes as an end of session. So
+    /* TFTP considers data block size < 512 bytes as an end of session, so
      * in some cases we must wait for additional data to build full (512 bytes)
      * data block.
      * */
@@ -717,7 +717,7 @@
         result = tftp_option_add(state, &sbytes, sbytes, buf);
 
       /* add blksize option */
-      curl_msnprintf(buf, sizeof(buf), "%d", state->requested_blksize);
+      curl_msnprintf(buf, sizeof(buf), "%u", state->requested_blksize);
       if(result == CURLE_OK)
         result = tftp_option_add(state, &sbytes, sbytes, TFTP_OPTION_BLKSIZE);
       if(result == CURLE_OK)
@@ -883,8 +883,8 @@
   struct tftp_conn *state = entry;
   (void)key;
   (void)klen;
-  Curl_safefree(state->rpacket.data);
-  Curl_safefree(state->spacket.data);
+  curlx_safefree(state->rpacket.data);
+  curlx_safefree(state->spacket.data);
   curlx_free(state);
 }
 
@@ -1332,7 +1332,7 @@
 /*
  * TFTP protocol handler.
  */
-static const struct Curl_protocol Curl_protocol_tftp = {
+const struct Curl_protocol Curl_protocol_tftp = {
   tftp_setup_connection,                /* setup_connection */
   tftp_do,                              /* do_it */
   tftp_done,                            /* done */
@@ -1347,25 +1347,9 @@
   ZERO_NULL,                            /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
 
 #endif
-
-/*
- * TFTP protocol handler.
- */
-const struct Curl_scheme Curl_scheme_tftp = {
-  "tftp",                               /* scheme */
-#ifdef CURL_DISABLE_TFTP
-  ZERO_NULL,
-#else
-  &Curl_protocol_tftp,
-#endif
-  CURLPROTO_TFTP,                       /* protocol */
-  CURLPROTO_TFTP,                       /* family */
-  PROTOPT_NOTCPPROXY | PROTOPT_NOURLQUERY, /* flags */
-  PORT_TFTP,                            /* defport */
-};
diff --git a/Utilities/cmcurl/lib/tftp.h b/Utilities/cmcurl/lib/tftp.h
index 91ecd29..3231010 100644
--- a/Utilities/cmcurl/lib/tftp.h
+++ b/Utilities/cmcurl/lib/tftp.h
@@ -23,7 +23,9 @@
  * SPDX-License-Identifier: curl
  *
  ***************************************************************************/
-extern const struct Curl_scheme Curl_scheme_tftp;
+#ifndef CURL_DISABLE_TFTP
+extern const struct Curl_protocol Curl_protocol_tftp;
+#endif
 
 #define TFTP_BLKSIZE_MIN 8
 #define TFTP_BLKSIZE_MAX 65464
diff --git a/Utilities/cmcurl/lib/thrdpool.c b/Utilities/cmcurl/lib/thrdpool.c
new file mode 100644
index 0000000..22faa39
--- /dev/null
+++ b/Utilities/cmcurl/lib/thrdpool.c
@@ -0,0 +1,481 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_THREADS
+
+#include "llist.h"
+#include "curl_threads.h"
+#include "curlx/timeval.h"
+#include "thrdpool.h"
+#ifdef CURLVERBOSE
+#include "curl_trc.h"
+#include "urldata.h"
+#endif
+
+
+struct thrdslot {
+  struct Curl_llist_node node;
+  struct curl_thrdpool *tpool;
+  curl_thread_t thread;
+  curl_cond_t await;
+  struct curltime starttime;
+  const char *work_description;
+  timediff_t work_timeout_ms;
+  uint32_t id;
+  BIT(running);
+  BIT(idle);
+};
+
+struct curl_thrdpool {
+  char *name;
+  uint64_t refcount;
+  curl_mutex_t lock;
+  curl_cond_t await;
+  struct Curl_llist slots;
+  struct Curl_llist zombies;
+  Curl_thrdpool_take_item_cb *fn_take;
+  Curl_thrdpool_process_item_cb *fn_process;
+  Curl_thrdpool_return_item_cb *fn_return;
+  void *fn_user_data;
+  CURLcode fatal_err;
+  uint32_t min_threads;
+  uint32_t max_threads;
+  uint32_t idle_time_ms;
+  uint32_t next_id;
+  BIT(aborted);
+  BIT(detached);
+};
+
+static void thrdpool_join_zombies(struct curl_thrdpool *tpool);
+static bool thrdpool_unlink(struct curl_thrdpool *tpool, bool locked);
+
+static void thrdslot_destroy(struct thrdslot *tslot)
+{
+  DEBUGASSERT(tslot->thread == curl_thread_t_null);
+  DEBUGASSERT(!tslot->running);
+  Curl_cond_destroy(&tslot->await);
+  curlx_free(tslot);
+}
+
+static void thrdslot_done(struct thrdslot *tslot)
+{
+  struct curl_thrdpool *tpool = tslot->tpool;
+
+  DEBUGASSERT(Curl_node_llist(&tslot->node) == &tpool->slots);
+  Curl_node_remove(&tslot->node);
+  tslot->running = FALSE;
+  Curl_llist_append(&tpool->zombies, tslot, &tslot->node);
+  Curl_cond_signal(&tpool->await);
+}
+
+static CURL_THREAD_RETURN_T CURL_STDCALL thrdslot_run(void *arg)
+{
+  struct thrdslot *tslot = arg;
+  struct curl_thrdpool *tpool = tslot->tpool;
+  void *item;
+
+  Curl_mutex_acquire(&tpool->lock);
+  DEBUGASSERT(Curl_node_llist(&tslot->node) == &tpool->slots);
+  for(;;) {
+    while(!tpool->aborted) {
+      tslot->work_description = NULL;
+      tslot->work_timeout_ms = 0;
+      item = tpool->fn_take(tpool->fn_user_data, &tslot->work_description,
+                            &tslot->work_timeout_ms);
+      if(!item)
+        break;
+      tslot->starttime = curlx_now();
+      tslot->idle = FALSE;
+      Curl_mutex_release(&tpool->lock);
+
+      tpool->fn_process(item);
+
+      Curl_mutex_acquire(&tpool->lock);
+      tslot->work_description = NULL;
+      tpool->fn_return(item, tpool->aborted ? NULL : tpool->fn_user_data);
+    }
+
+    if(tpool->aborted ||
+       (Curl_llist_count(&tpool->slots) > tpool->max_threads))
+      goto out;
+
+    tslot->idle = TRUE;
+    tslot->starttime = curlx_now();
+    thrdpool_join_zombies(tpool);
+    Curl_cond_signal(&tpool->await);
+    /* Only wait with idle timeout when we are above the minimum
+     * number of threads. Otherwise short idle timeouts will keep
+     * on activating threads that have no means to shut down. */
+    if((tpool->idle_time_ms > 0) &&
+       (Curl_llist_count(&tpool->slots) > tpool->min_threads)) {
+      CURLcode r = Curl_cond_timedwait(&tslot->await, &tpool->lock,
+                                       tpool->idle_time_ms);
+      if((r == CURLE_OPERATION_TIMEDOUT) &&
+         (Curl_llist_count(&tpool->slots) > tpool->min_threads)) {
+        goto out;
+      }
+    }
+    else {
+      Curl_cond_wait(&tslot->await, &tpool->lock);
+    }
+  }
+
+out:
+  thrdslot_done(tslot);
+  if(!thrdpool_unlink(tslot->tpool, TRUE)) {
+    /* tpool not destroyed */
+    Curl_mutex_release(&tpool->lock);
+  }
+  return 0;
+}
+
+static CURLcode thrdslot_start(struct curl_thrdpool *tpool)
+{
+  struct thrdslot *tslot;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+
+  tslot = curlx_calloc(1, sizeof(*tslot));
+  if(!tslot)
+    goto out;
+  tslot->id = tpool->next_id++;
+  tslot->tpool = tpool;
+  tslot->thread = curl_thread_t_null;
+  Curl_cond_init(&tslot->await);
+
+  tpool->refcount++;
+  tslot->running = TRUE;
+  tslot->thread = Curl_thread_create(thrdslot_run, tslot);
+  if(tslot->thread == curl_thread_t_null) { /* never started */
+    tslot->running = FALSE;
+    thrdpool_unlink(tpool, TRUE);
+    result = CURLE_FAILED_INIT;
+    goto out;
+  }
+
+  Curl_llist_append(&tpool->slots, tslot, &tslot->node);
+  tslot = NULL;
+  result = CURLE_OK;
+
+out:
+  if(tslot)
+    thrdslot_destroy(tslot);
+  return result;
+}
+
+static void thrdpool_wake_all(struct curl_thrdpool *tpool)
+{
+  struct Curl_llist_node *e;
+  for(e = Curl_llist_head(&tpool->slots); e; e = Curl_node_next(e)) {
+    struct thrdslot *tslot = Curl_node_elem(e);
+    Curl_cond_signal(&tslot->await);
+  }
+}
+
+static void thrdpool_join_zombies(struct curl_thrdpool *tpool)
+{
+  struct Curl_llist_node *e;
+
+  for(e = Curl_llist_head(&tpool->zombies); e;
+      e = Curl_llist_head(&tpool->zombies)) {
+    struct thrdslot *tslot = Curl_node_elem(e);
+
+    Curl_node_remove(&tslot->node);
+    if(tslot->thread != curl_thread_t_null) {
+      Curl_mutex_release(&tpool->lock);
+      Curl_thread_join(&tslot->thread);
+      Curl_mutex_acquire(&tpool->lock);
+      tslot->thread = curl_thread_t_null;
+    }
+    thrdslot_destroy(tslot);
+  }
+}
+
+static bool thrdpool_unlink(struct curl_thrdpool *tpool, bool locked)
+{
+  DEBUGASSERT(tpool->refcount);
+  if(tpool->refcount)
+    tpool->refcount--;
+  if(tpool->refcount)
+    return FALSE;
+
+  /* no more references, free */
+  DEBUGASSERT(tpool->aborted);
+  thrdpool_join_zombies(tpool);
+  if(locked)
+    Curl_mutex_release(&tpool->lock);
+  curlx_free(tpool->name);
+  Curl_cond_destroy(&tpool->await);
+  Curl_mutex_destroy(&tpool->lock);
+  curlx_free(tpool);
+  return TRUE;
+}
+
+static CURLcode thrdpool_signal(struct curl_thrdpool *tpool,
+                                uint32_t nthreads)
+{
+  struct Curl_llist_node *e, *n;
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(!tpool->aborted);
+  thrdpool_join_zombies(tpool);
+
+  for(e = Curl_llist_head(&tpool->slots); e && nthreads; e = n) {
+    struct thrdslot *tslot = Curl_node_elem(e);
+    n = Curl_node_next(e);
+    if(tslot->idle) {
+      Curl_cond_signal(&tslot->await);
+      --nthreads;
+    }
+    else if(!tslot->starttime.tv_sec && !tslot->starttime.tv_usec) {
+      /* starting thread, queries for work soon. */
+      --nthreads;
+    }
+  }
+
+  while(nthreads && !result &&
+        Curl_llist_count(&tpool->slots) < tpool->max_threads) {
+    result = thrdslot_start(tpool);
+    if(result)
+      break;
+    --nthreads;
+  }
+
+  return result;
+}
+
+CURLcode Curl_thrdpool_set_props(struct curl_thrdpool *tpool,
+                                 uint32_t min_threads,
+                                 uint32_t max_threads,
+                                 uint32_t idle_time_ms)
+{
+  CURLcode result = CURLE_OK;
+  size_t running;
+
+  if(!max_threads || (min_threads > max_threads))
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+
+  Curl_mutex_acquire(&tpool->lock);
+  tpool->min_threads = min_threads;
+  tpool->max_threads = max_threads;
+  tpool->idle_time_ms = idle_time_ms;
+  running = Curl_llist_count(&tpool->slots);
+  if(tpool->min_threads > running) {
+    result = thrdpool_signal(tpool, tpool->min_threads - (uint32_t)running);
+  }
+  Curl_mutex_release(&tpool->lock);
+
+  return result;
+}
+
+CURLcode Curl_thrdpool_create(struct curl_thrdpool **ptpool,
+                              const char *name,
+                              uint32_t min_threads,
+                              uint32_t max_threads,
+                              uint32_t idle_time_ms,
+                              Curl_thrdpool_take_item_cb *fn_take,
+                              Curl_thrdpool_process_item_cb *fn_process,
+                              Curl_thrdpool_return_item_cb *fn_return,
+                              void *user_data)
+{
+  struct curl_thrdpool *tpool;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+
+  tpool = curlx_calloc(1, sizeof(*tpool));
+  if(!tpool)
+    goto out;
+  tpool->refcount = 1;
+
+  Curl_mutex_init(&tpool->lock);
+  Curl_cond_init(&tpool->await);
+  Curl_llist_init(&tpool->slots, NULL);
+  Curl_llist_init(&tpool->zombies, NULL);
+  tpool->fn_take = fn_take;
+  tpool->fn_process = fn_process;
+  tpool->fn_return = fn_return;
+  tpool->fn_user_data = user_data;
+
+  tpool->name = curlx_strdup(name);
+  if(!tpool->name)
+    goto out;
+
+  result = Curl_thrdpool_set_props(tpool, min_threads, max_threads,
+                                   idle_time_ms);
+
+out:
+  if(result && tpool) {
+    tpool->aborted = TRUE;
+    thrdpool_unlink(tpool, FALSE);
+    tpool = NULL;
+  }
+  *ptpool = tpool;
+  return result;
+}
+
+void Curl_thrdpool_destroy(struct curl_thrdpool *tpool, bool join)
+{
+  Curl_mutex_acquire(&tpool->lock);
+
+  tpool->aborted = TRUE;
+
+  while(join && Curl_llist_count(&tpool->slots)) {
+    thrdpool_wake_all(tpool);
+    Curl_cond_wait(&tpool->await, &tpool->lock);
+  }
+
+  thrdpool_join_zombies(tpool);
+
+  /* detach all still running threads */
+  if(Curl_llist_count(&tpool->slots)) {
+    struct Curl_llist_node *e;
+    for(e = Curl_llist_head(&tpool->slots); e; e = Curl_node_next(e)) {
+      struct thrdslot *tslot = Curl_node_elem(e);
+      if(tslot->thread != curl_thread_t_null)
+        Curl_thread_destroy(&tslot->thread);
+    }
+    tpool->detached = TRUE;
+  }
+
+  if(!thrdpool_unlink(tpool, TRUE)) {
+    /* tpool not destroyed */
+    Curl_mutex_release(&tpool->lock);
+  }
+}
+
+CURLcode Curl_thrdpool_signal(struct curl_thrdpool *tpool, uint32_t nthreads)
+{
+  CURLcode result;
+
+  Curl_mutex_acquire(&tpool->lock);
+  result = thrdpool_signal(tpool, nthreads);
+  Curl_mutex_release(&tpool->lock);
+  return result;
+}
+
+static bool thrdpool_all_idle(struct curl_thrdpool *tpool)
+{
+  struct Curl_llist_node *e;
+  for(e = Curl_llist_head(&tpool->slots); e; e = Curl_node_next(e)) {
+    struct thrdslot *tslot = Curl_node_elem(e);
+    if(!tslot->idle)
+      return FALSE;
+  }
+  return TRUE;
+}
+
+CURLcode Curl_thrdpool_await_idle(struct curl_thrdpool *tpool,
+                                  uint32_t timeout_ms)
+{
+  CURLcode result = CURLE_OK;
+  struct curltime end = { 0 };
+
+  Curl_mutex_acquire(&tpool->lock);
+  DEBUGASSERT(!tpool->aborted);
+  if(tpool->aborted) {
+    result = CURLE_FAILED_INIT;
+    goto out;
+  }
+
+  while(!thrdpool_all_idle(tpool)) {
+    if(timeout_ms) {
+      timediff_t remain_ms;
+      CURLcode r;
+
+      if(!end.tv_sec && !end.tv_usec) {
+        end = curlx_now();
+        end.tv_sec += (time_t)(timeout_ms / 1000);
+        end.tv_usec += (int)(timeout_ms % 1000) * 1000;
+        if(end.tv_usec >= 1000000) {
+          end.tv_sec++;
+          end.tv_usec -= 1000000;
+        }
+      }
+      remain_ms = curlx_timediff_ms(curlx_now(), end);
+      if(remain_ms <= 0)
+        r = CURLE_OPERATION_TIMEDOUT;
+      else
+        r = Curl_cond_timedwait(&tpool->await, &tpool->lock,
+                                (uint32_t)remain_ms);
+      if(r == CURLE_OPERATION_TIMEDOUT) {
+        result = r;
+        break;
+      }
+    }
+    else {
+      Curl_cond_wait(&tpool->await, &tpool->lock);
+    }
+  }
+
+out:
+  thrdpool_join_zombies(tpool);
+  Curl_mutex_release(&tpool->lock);
+  return result;
+}
+
+#ifdef CURLVERBOSE
+void Curl_thrdpool_trace(struct curl_thrdpool *tpool,
+                         struct Curl_easy *data)
+{
+  struct curl_trc_feat *feat = &Curl_trc_feat_threads;
+  if(Curl_trc_ft_is_verbose(data, feat)) {
+    struct Curl_llist_node *e;
+    struct curltime now = curlx_now();
+
+    Curl_mutex_acquire(&tpool->lock);
+    if(!Curl_llist_count(&tpool->slots)) {
+      Curl_trc_feat_infof(data, feat, "[TPOOL-%s] no threads running",
+                          tpool->name);
+    }
+    for(e = Curl_llist_head(&tpool->slots); e; e = Curl_node_next(e)) {
+      struct thrdslot *tslot = Curl_node_elem(e);
+      timediff_t elapsed_ms = curlx_ptimediff_ms(&now, &tslot->starttime);
+      if(!tslot->running) {
+        Curl_trc_feat_infof(data, feat, "[TPOOL-%s] [%u]: not running",
+                            tpool->name, tslot->id);
+      }
+      else if(!tslot->starttime.tv_sec && !tslot->starttime.tv_usec) {
+        Curl_trc_feat_infof(data, feat, "[TPOOL-%s] [%u]: starting...",
+                            tpool->name, tslot->id);
+      }
+      else if(tslot->idle) {
+        Curl_trc_feat_infof(data, feat, "[TPOOL-%s] [%u]: idle for %"
+                            FMT_TIMEDIFF_T "ms",
+                            tpool->name, tslot->id, elapsed_ms);
+      }
+      else {
+        timediff_t remain_ms = tslot->work_timeout_ms ?
+          (tslot->work_timeout_ms - elapsed_ms) : 0;
+        Curl_trc_feat_infof(data, feat, "[TPOOL-%s] [%u]: busy %"
+                            FMT_TIMEDIFF_T "ms, timeout in %" FMT_TIMEDIFF_T
+                            "ms: %s",
+                            tpool->name, tslot->id, elapsed_ms, remain_ms,
+                            tslot->work_description);
+      }
+    }
+    Curl_mutex_release(&tpool->lock);
+  }
+}
+#endif
+
+#endif /* USE_THREADS */
diff --git a/Utilities/cmcurl/lib/thrdpool.h b/Utilities/cmcurl/lib/thrdpool.h
new file mode 100644
index 0000000..cc81fb6
--- /dev/null
+++ b/Utilities/cmcurl/lib/thrdpool.h
@@ -0,0 +1,106 @@
+#ifndef HEADER_CURL_THRDPOOL_H
+#define HEADER_CURL_THRDPOOL_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 "curlx/timediff.h"
+
+#ifdef USE_THREADS
+
+struct curl_thrdpool;
+struct Curl_easy;
+
+/* Invoked under thread pool lock to get an "item" to work on. Must
+ * return NULL if there is nothing to do.
+ * Caller might return a descriptive string about the "item", where
+ * available. The string needs to have the same lifetime as the
+ * item itself. */
+typedef void *Curl_thrdpool_take_item_cb(void *user_data,
+                                         const char **pdescription,
+                                         timediff_t *ptimeout_ms);
+
+/* Invoked outside thread pool lock to process the item taken. */
+typedef void Curl_thrdpool_process_item_cb(void *item);
+
+/* Invoked under thread pool lock to return a processed item back
+ * to the producer.
+ * If the thread pool has been destroyed, `user_data` will be NULL
+ * and the callback is responsible to release all `item` resources. */
+typedef void Curl_thrdpool_return_item_cb(void *item, void *user_data);
+
+/* Create a new thread pool.
+ * @param name         name of pool for tracing purposes
+ * @param min_threads  minimum number of threads to have always running
+ * @param max_threads  maximum number of threads running, ever.
+ * @param idle_time_ms maximum time a thread should wait for tasks to
+ *                     process before shutting down (unless the pool is
+ *                     already at minimum thread count), use 0 for
+ *                     infinite wait.
+ * @param fn_take      take the next item to process
+ * @param fn_process   process the item taken
+ * @param fn_return    return the processed item
+ * @param user_data    parameter passed to take/return callbacks
+ */
+CURLcode Curl_thrdpool_create(struct curl_thrdpool **ptpool,
+                              const char *name,
+                              uint32_t min_threads,
+                              uint32_t max_threads,
+                              uint32_t idle_time_ms,
+                              Curl_thrdpool_take_item_cb *fn_take,
+                              Curl_thrdpool_process_item_cb *fn_process,
+                              Curl_thrdpool_return_item_cb *fn_return,
+                              void *user_data);
+
+/* Destroy the thread pool, release its resources.
+ * With `join` being TRUE, the call will wait for all threads to finish
+ * processing before returning. On FALSE, it will detach all threads
+ * running. Ongoing item processing will continue to run and
+ * `fn_return` will be invoked with NULL user_data before the thread exits.
+ */
+void Curl_thrdpool_destroy(struct curl_thrdpool *tpool, bool join);
+
+/* Signal the pool to wake up `nthreads` idle worker threads, possible
+ * creating new threads up to the max limit. The number should reflect
+ * the items that can actually be taken for processing right away, e.g.
+ * the producers "queue" length of outstanding items.
+ */
+CURLcode Curl_thrdpool_signal(struct curl_thrdpool *tpool, uint32_t nthreads);
+
+CURLcode Curl_thrdpool_await_idle(struct curl_thrdpool *tpool,
+                                  uint32_t timeout_ms);
+
+/* Change the properties of a threadpool. */
+CURLcode Curl_thrdpool_set_props(struct curl_thrdpool *tpool,
+                                 uint32_t min_threads,
+                                 uint32_t max_threads,
+                                 uint32_t idle_time_ms);
+
+#ifdef CURLVERBOSE
+void Curl_thrdpool_trace(struct curl_thrdpool *tpool,
+                         struct Curl_easy *data);
+#endif
+
+#endif /* USE_THREADS */
+
+#endif /* HEADER_CURL_THRDPOOL_H */
diff --git a/Utilities/cmcurl/lib/thrdqueue.c b/Utilities/cmcurl/lib/thrdqueue.c
new file mode 100644
index 0000000..1521ccf
--- /dev/null
+++ b/Utilities/cmcurl/lib/thrdqueue.c
@@ -0,0 +1,411 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_THREADS
+
+#include "llist.h"
+#include "curl_threads.h"
+#include "thrdpool.h"
+#include "thrdqueue.h"
+#include "curlx/timeval.h"
+#ifdef CURLVERBOSE
+#include "curl_trc.h"
+#include "urldata.h"
+#endif
+
+
+struct curl_thrdq {
+  char *name;
+  curl_mutex_t lock;
+  curl_cond_t await;
+  struct Curl_llist sendq;
+  struct Curl_llist recvq;
+  struct curl_thrdpool *tpool;
+  Curl_thrdq_item_free_cb *fn_free;
+  Curl_thrdq_item_process_cb *fn_process;
+  Curl_thrdq_ev_cb *fn_event;
+  void *fn_user_data;
+  uint32_t send_max_len;
+  BIT(aborted);
+};
+
+struct thrdq_item {
+  struct Curl_llist_node node;
+  Curl_thrdq_item_free_cb *fn_free;
+  Curl_thrdq_item_process_cb *fn_process;
+  void *item;
+  struct curltime start;
+  timediff_t timeout_ms;
+  const char *description;
+};
+
+static struct thrdq_item *thrdq_item_create(struct curl_thrdq *tqueue,
+                                            void *item,
+                                            const char *description,
+                                            timediff_t timeout_ms)
+{
+  struct thrdq_item *qitem;
+
+  qitem = curlx_calloc(1, sizeof(*qitem));
+  if(!qitem)
+    return NULL;
+  qitem->item = item;
+  qitem->description = description;
+  qitem->fn_free = tqueue->fn_free;
+  qitem->fn_process = tqueue->fn_process;
+  if(timeout_ms) {
+    qitem->start = curlx_now();
+    qitem->timeout_ms = timeout_ms;
+  }
+  return qitem;
+}
+
+static void thrdq_item_destroy(struct thrdq_item *qitem)
+{
+  if(qitem->item)
+    qitem->fn_free(qitem->item);
+  curlx_free(qitem);
+}
+
+static void thrdq_item_list_dtor(void *user_data, void *elem)
+{
+  (void)user_data;
+  thrdq_item_destroy(elem);
+}
+
+static void *thrdq_tpool_take(void *user_data, const char **pdescription,
+                              timediff_t *ptimeout_ms)
+{
+  struct curl_thrdq *tqueue = user_data;
+  struct thrdq_item *qitem = NULL;
+  struct Curl_llist_node *e;
+  Curl_thrdq_ev_cb *fn_event = NULL;
+  void *fn_user_data = NULL;
+
+  Curl_mutex_acquire(&tqueue->lock);
+  *pdescription = NULL;
+  *ptimeout_ms = 0;
+  if(!tqueue->aborted) {
+    e = Curl_llist_head(&tqueue->sendq);
+    if(e) {
+      struct curltime now = curlx_now();
+      timediff_t timeout_ms;
+      while(e) {
+        qitem = Curl_node_take_elem(e);
+        timeout_ms = (!qitem->timeout_ms) ? 0 :
+          (qitem->timeout_ms - curlx_ptimediff_ms(&now, &qitem->start));
+        if(timeout_ms < 0) {
+          /* timed out while queued, place on receive queue */
+          Curl_llist_append(&tqueue->recvq, qitem, &qitem->node);
+          fn_event = tqueue->fn_event;
+          fn_user_data = tqueue->fn_user_data;
+          qitem = NULL;
+          e = Curl_llist_head(&tqueue->sendq);
+          continue;
+        }
+        else {
+          *pdescription = qitem->description;
+          *ptimeout_ms = timeout_ms;
+          break;
+        }
+      }
+    }
+  }
+  Curl_mutex_release(&tqueue->lock);
+  /* avoiding deadlocks */
+  if(fn_event)
+    fn_event(tqueue, CURL_THRDQ_EV_ITEM_DONE, fn_user_data);
+  return qitem;
+}
+
+static void thrdq_tpool_return(void *item, void *user_data)
+{
+  struct curl_thrdq *tqueue = user_data;
+  struct thrdq_item *qitem = item;
+  Curl_thrdq_ev_cb *fn_event = NULL;
+  void *fn_user_data = NULL;
+
+  if(!tqueue) {
+    thrdq_item_destroy(item);
+    return;
+  }
+
+  Curl_mutex_acquire(&tqueue->lock);
+  if(tqueue->aborted) {
+    thrdq_item_destroy(qitem);
+  }
+  else {
+    DEBUGASSERT(!Curl_node_llist(&qitem->node));
+    Curl_llist_append(&tqueue->recvq, qitem, &qitem->node);
+    fn_event = tqueue->fn_event;
+    fn_user_data = tqueue->fn_user_data;
+  }
+  Curl_mutex_release(&tqueue->lock);
+  /* avoiding deadlocks */
+  if(fn_event)
+    fn_event(tqueue, CURL_THRDQ_EV_ITEM_DONE, fn_user_data);
+}
+
+static void thrdq_tpool_process(void *item)
+{
+  struct thrdq_item *qitem = item;
+  qitem->fn_process(qitem->item);
+}
+
+static void thrdq_unlink(struct curl_thrdq *tqueue, bool locked, bool join)
+{
+  DEBUGASSERT(tqueue->aborted);
+  if(tqueue->tpool) {
+    if(locked)
+      Curl_mutex_release(&tqueue->lock);
+    Curl_thrdpool_destroy(tqueue->tpool, join);
+    tqueue->tpool = NULL;
+    if(locked)
+      Curl_mutex_acquire(&tqueue->lock);
+  }
+
+  Curl_llist_destroy(&tqueue->sendq, NULL);
+  Curl_llist_destroy(&tqueue->recvq, NULL);
+  curlx_free(tqueue->name);
+  Curl_cond_destroy(&tqueue->await);
+  if(locked)
+    Curl_mutex_release(&tqueue->lock);
+  Curl_mutex_destroy(&tqueue->lock);
+  curlx_free(tqueue);
+}
+
+CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue,
+                           const char *name,
+                           uint32_t max_len,
+                           uint32_t min_threads,
+                           uint32_t max_threads,
+                           uint32_t idle_time_ms,
+                           Curl_thrdq_item_free_cb *fn_free,
+                           Curl_thrdq_item_process_cb *fn_process,
+                           Curl_thrdq_ev_cb *fn_event,
+                           void *user_data)
+{
+  struct curl_thrdq *tqueue;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+
+  tqueue = curlx_calloc(1, sizeof(*tqueue));
+  if(!tqueue)
+    goto out;
+
+  Curl_mutex_init(&tqueue->lock);
+  Curl_cond_init(&tqueue->await);
+  Curl_llist_init(&tqueue->sendq, thrdq_item_list_dtor);
+  Curl_llist_init(&tqueue->recvq, thrdq_item_list_dtor);
+  tqueue->fn_free = fn_free;
+  tqueue->fn_process = fn_process;
+  tqueue->fn_event = fn_event;
+  tqueue->fn_user_data = user_data;
+  tqueue->send_max_len = max_len;
+
+  tqueue->name = curlx_strdup(name);
+  if(!tqueue->name)
+    goto out;
+
+  result = Curl_thrdpool_create(&tqueue->tpool, name,
+                                min_threads, max_threads, idle_time_ms,
+                                thrdq_tpool_take,
+                                thrdq_tpool_process,
+                                thrdq_tpool_return,
+                                tqueue);
+
+out:
+  if(result && tqueue) {
+    tqueue->aborted = TRUE;
+    thrdq_unlink(tqueue, FALSE, TRUE);
+    tqueue = NULL;
+  }
+  *ptqueue = tqueue;
+  return result;
+}
+
+void Curl_thrdq_destroy(struct curl_thrdq *tqueue, bool join)
+{
+  Curl_mutex_acquire(&tqueue->lock);
+  DEBUGASSERT(!tqueue->aborted);
+  tqueue->aborted = TRUE;
+  thrdq_unlink(tqueue, TRUE, join);
+}
+
+CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item,
+                         const char *description, timediff_t timeout_ms)
+{
+  CURLcode result = CURLE_AGAIN;
+  size_t signals = 0;
+
+  Curl_mutex_acquire(&tqueue->lock);
+  if(tqueue->aborted) {
+    DEBUGASSERT(0);
+    result = CURLE_SEND_ERROR;
+    goto out;
+  }
+  if(timeout_ms < 0) {
+    result = CURLE_OPERATION_TIMEDOUT;
+    goto out;
+  }
+
+  if(!tqueue->send_max_len ||
+     (Curl_llist_count(&tqueue->sendq) < tqueue->send_max_len)) {
+    struct thrdq_item *qitem = thrdq_item_create(tqueue, item, description,
+                                                 timeout_ms);
+    if(!qitem) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto out;
+    }
+    item = NULL;
+    Curl_llist_append(&tqueue->sendq, qitem, &qitem->node);
+    signals = Curl_llist_count(&tqueue->sendq);
+    result = CURLE_OK;
+  }
+
+out:
+  Curl_mutex_release(&tqueue->lock);
+  /* Signal thread pool unlocked to avoid deadlocks. Since we added
+   * item to the queue already, it might have been taken for processing
+   * already. Any error in signalling the pool cannot be reported to
+   * the caller since it needs to give up ownership of item. */
+  if(!result && signals)
+    (void)Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals);
+  return result;
+}
+
+CURLcode Curl_thrdq_recv(struct curl_thrdq *tqueue, void **pitem)
+{
+  CURLcode result = CURLE_AGAIN;
+  struct Curl_llist_node *e;
+
+  *pitem = NULL;
+  Curl_mutex_acquire(&tqueue->lock);
+  if(tqueue->aborted) {
+    DEBUGASSERT(0);
+    result = CURLE_RECV_ERROR;
+    goto out;
+  }
+
+  e = Curl_llist_head(&tqueue->recvq);
+  if(e) {
+    struct thrdq_item *qitem = Curl_node_take_elem(e);
+    *pitem = qitem->item;
+    qitem->item = NULL;
+    thrdq_item_destroy(qitem);
+    result = CURLE_OK;
+  }
+out:
+  Curl_mutex_release(&tqueue->lock);
+  return result;
+}
+
+static void thrdq_llist_clean_matches(struct Curl_llist *llist,
+                                      Curl_thrdq_item_match_cb *fn_match,
+                                      void *match_data)
+{
+  struct Curl_llist_node *e, *n;
+  struct thrdq_item *qitem;
+
+  for(e = Curl_llist_head(llist); e; e = n) {
+    n = Curl_node_next(e);
+    qitem = Curl_node_elem(e);
+    if(fn_match(qitem->item, match_data))
+      Curl_node_remove(e);
+  }
+}
+
+void Curl_thrdq_clear(struct curl_thrdq *tqueue,
+                      Curl_thrdq_item_match_cb *fn_match,
+                      void *match_data)
+{
+  Curl_mutex_acquire(&tqueue->lock);
+  if(tqueue->aborted) {
+    DEBUGASSERT(0);
+    goto out;
+  }
+  thrdq_llist_clean_matches(&tqueue->sendq, fn_match, match_data);
+  thrdq_llist_clean_matches(&tqueue->recvq, fn_match, match_data);
+out:
+  Curl_mutex_release(&tqueue->lock);
+}
+
+CURLcode Curl_thrdq_await_done(struct curl_thrdq *tqueue,
+                               uint32_t timeout_ms)
+{
+  return Curl_thrdpool_await_idle(tqueue->tpool, timeout_ms);
+}
+
+CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue,
+                              uint32_t max_len,
+                              uint32_t min_threads,
+                              uint32_t max_threads,
+                              uint32_t idle_time_ms)
+{
+  CURLcode result;
+  size_t signals;
+
+  Curl_mutex_acquire(&tqueue->lock);
+  tqueue->send_max_len = max_len;
+  signals = Curl_llist_count(&tqueue->sendq);
+  Curl_mutex_release(&tqueue->lock);
+
+  result = Curl_thrdpool_set_props(tqueue->tpool, min_threads,
+                                   max_threads, idle_time_ms);
+  if(!result && signals)
+    result = Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals);
+  return result;
+}
+
+#ifdef CURLVERBOSE
+void Curl_thrdq_trace(struct curl_thrdq *tqueue,
+                      struct Curl_easy *data)
+{
+  struct curl_trc_feat *feat = &Curl_trc_feat_threads;
+  if(Curl_trc_ft_is_verbose(data, feat)) {
+    struct Curl_llist_node *e;
+    struct thrdq_item *qitem;
+
+    Curl_thrdpool_trace(tqueue->tpool, data);
+    Curl_mutex_acquire(&tqueue->lock);
+    if(!Curl_llist_count(&tqueue->sendq) &&
+       !Curl_llist_count(&tqueue->recvq)) {
+      Curl_trc_feat_infof(data, feat, "[TQUEUE-%s] empty", tqueue->name);
+    }
+    for(e = Curl_llist_head(&tqueue->sendq); e; e = Curl_node_next(e)) {
+      qitem = Curl_node_elem(e);
+      Curl_trc_feat_infof(data, feat, "[TQUEUE-%s] in: %s",
+                          tqueue->name, qitem->description);
+    }
+    for(e = Curl_llist_head(&tqueue->recvq); e; e = Curl_node_next(e)) {
+      qitem = Curl_node_elem(e);
+      Curl_trc_feat_infof(data, feat, "[TQUEUE-%s] out: %s",
+                          tqueue->name, qitem->description);
+    }
+    Curl_mutex_release(&tqueue->lock);
+  }
+}
+#endif
+
+#endif /* USE_THREADS */
diff --git a/Utilities/cmcurl/lib/thrdqueue.h b/Utilities/cmcurl/lib/thrdqueue.h
new file mode 100644
index 0000000..d267f5d
--- /dev/null
+++ b/Utilities/cmcurl/lib/thrdqueue.h
@@ -0,0 +1,118 @@
+#ifndef HEADER_CURL_THRDQUEUE_H
+#define HEADER_CURL_THRDQUEUE_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 "curlx/timediff.h"
+
+#ifdef USE_THREADS
+
+struct Curl_easy;
+struct curl_thrdq;
+
+typedef enum {
+  CURL_THRDQ_EV_ITEM_DONE /* an item has been processed and is ready */
+} Curl_thrdq_event;
+
+/* Notification callback when "events" happen in the queue. May be
+ * call from any thread, queue is not locked. */
+typedef void Curl_thrdq_ev_cb(const struct curl_thrdq *tqueue,
+                              Curl_thrdq_event ev,
+                              void *user_data);
+
+/* Process a queued item. Maybe call from any thread. Queue is
+ * not locked. */
+typedef void Curl_thrdq_item_process_cb(void *item);
+
+/* Free an item. May be called from any thread at any time for an
+ * item that is in the queue (either before or after processing). */
+typedef void Curl_thrdq_item_free_cb(void *item);
+
+/* Create a new queue processing "items" by a thread pool.
+ */
+CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue,
+                           const char *name,
+                           uint32_t max_len, /* 0 for unlimited */
+                           uint32_t min_threads,
+                           uint32_t max_threads,
+                           uint32_t idle_time_ms,
+                           Curl_thrdq_item_free_cb *fn_free,
+                           Curl_thrdq_item_process_cb *fn_process,
+                           Curl_thrdq_ev_cb *fn_event, /* optional */
+                           void *user_data);
+
+/* Destroy the queue, free all queued items unprocessed and destroy
+ * the thread pool used.
+ * @param join TRUE when thread pool shall be joined. FALSE for
+ *             detaching any running threads.
+ */
+void Curl_thrdq_destroy(struct curl_thrdq *tqueue, bool join);
+
+/* Send "item" onto the queue. The caller needs to clear any reference
+ * to "item" on success, e.g. the queue takes ownership.
+ * `description` is an optional string describing the item for tracing
+ * purposes. It needs to have the same lifetime as `item`.
+ * Returns CURLE_AGAIN when the queue has already been full.
+ *
+ * With`timeout_ms` != 0, items that get stuck that long in the send
+ * queue are removed and added to the receive queue right away.
+ */
+CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item,
+                         const char *description, timediff_t timeout_ms);
+
+/* Receive the oldest, processed item from the queue again, if there is one.
+ * The caller takes ownership of the item received, e.g. the queue
+ * relinquishes all references to item.
+ * Returns CURLE_AGAIN when there is no processed item, setting `pitem`
+ * to NULL.
+ */
+CURLcode Curl_thrdq_recv(struct curl_thrdq *tqueue, void **pitem);
+
+/* Return TRUE if the passed "item" matches. */
+typedef bool Curl_thrdq_item_match_cb(void *item, void *match_data);
+
+/* Clear all scheduled/processed items that match from the queue. This
+ * will *not* be able to clear items that are being processed.
+ */
+void Curl_thrdq_clear(struct curl_thrdq *tqueue,
+                      Curl_thrdq_item_match_cb *fn_match,
+                      void *match_data);
+
+CURLcode Curl_thrdq_await_done(struct curl_thrdq *tqueue,
+                               uint32_t timeout_ms);
+
+CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue,
+                              uint32_t max_len, /* 0 for unlimited */
+                              uint32_t min_threads,
+                              uint32_t max_threads,
+                              uint32_t idle_time_ms);
+
+#ifdef CURLVERBOSE
+void Curl_thrdq_trace(struct curl_thrdq *tqueue,
+                      struct Curl_easy *data);
+#endif
+
+#endif /* USE_THREADS */
+
+#endif /* HEADER_CURL_THRDQUEUE_H */
diff --git a/Utilities/cmcurl/lib/transfer.c b/Utilities/cmcurl/lib/transfer.c
index 6dd2f52..fd1a903 100644
--- a/Utilities/cmcurl/lib/transfer.c
+++ b/Utilities/cmcurl/lib/transfer.c
@@ -59,6 +59,7 @@
 #include "hostip.h"
 #include "cfilters.h"
 #include "cw-out.h"
+#include "dnscache.h"
 #include "transfer.h"
 #include "sendf.h"
 #include "curl_trc.h"
@@ -303,15 +304,15 @@
      * we should read the EOS. Which may arrive as meta data after
      * the bytes. Not taking it in might lead to RST of streams. */
     if((!is_multiplex && data->req.download_done) || is_eos) {
-      data->req.keepon &= ~KEEP_RECV;
+      CURL_REQ_CLEAR_RECV(data);
     }
     /* if we stopped receiving, leave the loop */
-    if(!(k->keepon & KEEP_RECV))
+    if(!CURL_REQ_WANT_RECV(data))
       break;
 
   } while(maxloops--);
 
-  if(!is_eos && !rate_limited && CURL_WANT_RECV(data) &&
+  if(!is_eos && !rate_limited && CURL_REQ_WANT_RECV(data) &&
      (!rcvd_eagain || data_pending(data, rcvd_eagain))) {
     /* Did not read until EAGAIN/EOS or there is still data pending
      * in buffers. Mark as read-again via simulated SELECT results. */
@@ -319,7 +320,7 @@
     CURL_TRC_M(data, "sendrecv_dl() no EAGAIN/pending data, mark as dirty");
   }
 
-  if(((k->keepon & (KEEP_RECV | KEEP_SEND)) == KEEP_SEND) &&
+  if(!CURL_REQ_WANT_RECV(data) && CURL_REQ_WANT_SEND(data) &&
      (conn->bits.close || is_multiplex)) {
     /* When we have read the entire thing and the close bit is set, the server
        may now close the connection. If there is now any kind of sending going
@@ -340,9 +341,7 @@
  */
 static CURLcode sendrecv_ul(struct Curl_easy *data)
 {
-  /* We should not get here when the sending is already done. It
-   * probably means that someone set `data-req.keepon |= KEEP_SEND`
-   * when it should not. */
+  /* We should not get here when the sending is already done. */
   DEBUGASSERT(!Curl_req_done_sending(data));
 
   if(!Curl_req_done_sending(data))
@@ -366,7 +365,7 @@
 
   /* 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) {
+  if(CURL_REQ_WANT_RECV(data)) {
     result = sendrecv_dl(data, k);
     if(result || data->req.done)
       goto out;
@@ -383,7 +382,7 @@
   if(result)
     goto out;
 
-  if(k->keepon) {
+  if(CURL_REQ_WANT_IO(data)) {
     if(Curl_timeleft_ms(data) < 0) {
       if(k->size != -1) {
         failf(data, "Operation timed out after %" FMT_TIMEDIFF_T
@@ -419,7 +418,7 @@
   }
 
   /* If there is nothing more to send/recv, the request is done */
-  if((k->keepon & (KEEP_RECV | KEEP_SEND)) == 0)
+  if(!CURL_REQ_WANT_IO(data))
     data->req.done = TRUE;
 
   result = Curl_pgrsUpdate(data);
@@ -440,6 +439,40 @@
 }
 
 /*
+ * Restore the user credentials to those set in options.
+ */
+CURLcode Curl_reset_userpwd(struct Curl_easy *data)
+{
+  CURLcode result;
+  if(data->set.str[STRING_USERNAME] || data->set.str[STRING_PASSWORD])
+    data->state.creds_from = CREDS_OPTION;
+  result = Curl_setstropt(&data->state.aptr.user,
+                          data->set.str[STRING_USERNAME]);
+  if(!result)
+    result = Curl_setstropt(&data->state.aptr.passwd,
+                            data->set.str[STRING_PASSWORD]);
+  return result;
+}
+
+/*
+ * Restore the proxy credentials to those set in options.
+ */
+CURLcode Curl_reset_proxypwd(struct Curl_easy *data)
+{
+#ifndef CURL_DISABLE_PROXY
+  CURLcode result = Curl_setstropt(&data->state.aptr.proxyuser,
+                                   data->set.str[STRING_PROXYUSERNAME]);
+  if(!result)
+    result = Curl_setstropt(&data->state.aptr.proxypasswd,
+                            data->set.str[STRING_PROXYPASSWORD]);
+  return result;
+#else
+  (void)data;
+  return CURLE_OK;
+#endif
+}
+
+/*
  * Curl_pretransfer() is called immediately before a transfer starts, and only
  * once for one transfer no matter if it has redirects or do multi-pass
  * authentication etc.
@@ -470,6 +503,8 @@
     uc = curl_url_get(data->set.uh,
                       CURLUPART_URL, &data->set.str[STRING_SET_URL], 0);
     if(uc) {
+      /* clear the pointer to not point to freed memory anymore */
+      Curl_bufref_set(&data->state.url, NULL, 0, NULL);
       failf(data, "No URL set");
       return CURLE_URL_MALFORMAT;
     }
@@ -492,6 +527,7 @@
   data->state.requests = 0;
   data->state.followlocation = 0; /* reset the location-follow counter */
   data->state.this_is_a_follow = FALSE; /* reset this */
+  data->state.http_ignorecustom = FALSE; /* use custom HTTP method */
   data->state.errorbuf = FALSE; /* no error has occurred */
 #ifndef CURL_DISABLE_HTTP
   Curl_http_neg_init(data, &data->state.http_neg);
@@ -499,8 +535,13 @@
   data->state.authproblem = FALSE;
   data->state.authhost.want = data->set.httpauth;
   data->state.authproxy.want = data->set.proxyauth;
-  Curl_safefree(data->info.wouldredirect);
+  curlx_safefree(data->info.wouldredirect);
   Curl_data_priority_clear_state(data);
+  if(data->set.http_auto_referer)
+    Curl_bufref_free(&data->state.referer);
+  if(data->set.str[STRING_SET_REFERER])
+    Curl_bufref_set(&data->state.referer, data->set.str[STRING_SET_REFERER],
+                    0, NULL);
 
   if(data->state.httpreq == HTTPREQ_PUT)
     data->state.infilesize = data->set.filesize;
@@ -563,8 +604,8 @@
       if(wc->state < CURLWC_INIT) {
         if(wc->ftpwc)
           wc->dtor(wc->ftpwc);
-        Curl_safefree(wc->pattern);
-        Curl_safefree(wc->path);
+        curlx_safefree(wc->pattern);
+        curlx_safefree(wc->path);
         Curl_wildcard_init(wc); /* init wildcard structures */
       }
     }
@@ -584,23 +625,10 @@
       return CURLE_OUT_OF_MEMORY;
   }
 
-  if(data->set.str[STRING_USERNAME] ||
-     data->set.str[STRING_PASSWORD])
-    data->state.creds_from = CREDS_OPTION;
   if(!result)
-    result = Curl_setstropt(&data->state.aptr.user,
-                            data->set.str[STRING_USERNAME]);
+    result = Curl_reset_userpwd(data);
   if(!result)
-    result = Curl_setstropt(&data->state.aptr.passwd,
-                            data->set.str[STRING_PASSWORD]);
-#ifndef CURL_DISABLE_PROXY
-  if(!result)
-    result = Curl_setstropt(&data->state.aptr.proxyuser,
-                            data->set.str[STRING_PROXYUSERNAME]);
-  if(!result)
-    result = Curl_setstropt(&data->state.aptr.proxypasswd,
-                            data->set.str[STRING_PROXYPASSWORD]);
-#endif
+    result = Curl_reset_proxypwd(data);
 
   data->req.headerbytecount = 0;
   Curl_headers_cleanup(data);
@@ -710,14 +738,11 @@
 
   /* we want header and/or body, if neither then do not do this! */
   if(conn->scheme->run->write_resp_hd || !data->req.no_body) {
-
     if(conn->recv_idx != -1)
-      k->keepon |= KEEP_RECV;
-
+      CURL_REQ_SET_RECV(data);
     if(conn->send_idx != -1)
-      k->keepon |= KEEP_SEND;
+      CURL_REQ_SET_SEND(data);
   }
-
   CURL_TRC_M(data, "xfer_setup: recv_idx=%d, send_idx=%d",
              conn->recv_idx, conn->send_idx);
 }
@@ -799,7 +824,7 @@
                                  const char *hd0, size_t hdlen, bool is_eos)
 {
   if(data->conn->scheme->run->write_resp_hd) {
-    DEBUGASSERT(!hd0[hdlen]); /* null terminated */
+    DEBUGASSERT(!hd0[hdlen]); /* null-terminated */
     /* protocol handlers offering this function take full responsibility
      * for writing all received download data to the client. */
     return data->conn->scheme->run->write_resp_hd(data, hd0, hdlen, is_eos);
@@ -868,8 +893,8 @@
 
 bool Curl_xfer_is_blocked(struct Curl_easy *data)
 {
-  bool want_send = (data->req.keepon & KEEP_SEND);
-  bool want_recv = (data->req.keepon & KEEP_RECV);
+  bool want_send = CURL_REQ_WANT_SEND(data);
+  bool want_recv = CURL_REQ_WANT_RECV(data);
   if(!want_send)
     return want_recv && Curl_xfer_recv_is_paused(data);
   else if(!want_recv)
@@ -908,3 +933,25 @@
   Curl_pgrsRecvPause(data, enable);
   return result;
 }
+
+bool Curl_xfer_is_secure(struct Curl_easy *data)
+{
+  const struct Curl_scheme *scheme = NULL;
+
+  if(data->conn) {
+    scheme = data->conn->scheme;
+    /* if we are connected, but not use SSL, the transfer is not secure.
+     * This covers an insecure http:// proxy that is not tunneling.
+     * We enforce tunneling for such cases, but better be sure here. */
+    if(Curl_conn_is_connected(data->conn, FIRSTSOCKET) &&
+       !Curl_conn_is_ssl(data->conn, FIRSTSOCKET))
+      return FALSE;
+  }
+  else if(data->info.conn_scheme) { /* was connected once */
+    scheme = Curl_get_scheme(data->info.conn_scheme);
+  }
+  else { /* never connected (yet?) */
+    DEBUGASSERT(0); /* not implemented, would need to parse URL */
+  }
+  return scheme ? (scheme->flags & PROTOPT_SSL) : FALSE;
+}
diff --git a/Utilities/cmcurl/lib/transfer.h b/Utilities/cmcurl/lib/transfer.h
index 05a5f89..b29e70b 100644
--- a/Utilities/cmcurl/lib/transfer.h
+++ b/Utilities/cmcurl/lib/transfer.h
@@ -31,6 +31,8 @@
 
 void Curl_init_CONNECT(struct Curl_easy *data);
 
+CURLcode Curl_reset_userpwd(struct Curl_easy *data);
+CURLcode Curl_reset_proxypwd(struct Curl_easy *data);
 CURLcode Curl_pretransfer(struct Curl_easy *data);
 
 CURLcode Curl_sendrecv(struct Curl_easy *data);
@@ -39,8 +41,8 @@
 
 /**
  * Write the transfer raw response bytes, as received from the connection.
- * Will handle all passed bytes or return an error. By default, this will
- * write the bytes as BODY to the client. Protocols may provide a
+ * Handle all passed bytes or return an error. By default, this writes
+ * the bytes as BODY to the client. Protocols may provide a
  * "write_resp" callback in their handler to add specific treatment. E.g.
  * HTTP parses response headers and passes them differently to the client.
  * @param data     the transfer
@@ -112,7 +114,7 @@
 /**
  * Send data on the socket/connection filter designated
  * for transfer's outgoing data.
- * Will return CURLE_OK on blocking with (*pnwritten == 0).
+ * Return CURLE_OK on blocking with (*pnwritten == 0).
  */
 CURLcode Curl_xfer_send(struct Curl_easy *data,
                         const void *buf, size_t blen, bool eos,
@@ -121,7 +123,7 @@
 /**
  * Receive data on the socket/connection filter designated
  * for transfer's incoming data.
- * Will return CURLE_AGAIN on blocking with (*pnrcvd == 0).
+ * Return CURLE_AGAIN on blocking with (*pnrcvd == 0).
  */
 CURLcode Curl_xfer_recv(struct Curl_easy *data,
                         char *buf, size_t blen,
@@ -143,4 +145,8 @@
 CURLcode Curl_xfer_pause_send(struct Curl_easy *data, bool enable);
 CURLcode Curl_xfer_pause_recv(struct Curl_easy *data, bool enable);
 
+/* TRUE if the transfer is secure (e.g. TLS) from libcurl to the
+ * URL's host. */
+bool Curl_xfer_is_secure(struct Curl_easy *data);
+
 #endif /* HEADER_CURL_TRANSFER_H */
diff --git a/Utilities/cmcurl/lib/uint-bset.c b/Utilities/cmcurl/lib/uint-bset.c
index 5efdbea..5469174 100644
--- a/Utilities/cmcurl/lib/uint-bset.c
+++ b/Utilities/cmcurl/lib/uint-bset.c
@@ -68,7 +68,9 @@
 }
 
 #ifdef UNITTESTS
-UNITTEST uint32_t Curl_uint32_bset_capacity(struct uint32_bset *bset)
+/* @unittest 3211 */
+UNITTEST uint32_t uint32_bset_capacity(struct uint32_bset *bset);
+UNITTEST uint32_t uint32_bset_capacity(struct uint32_bset *bset)
 {
   return bset->nslots * 64;
 }
diff --git a/Utilities/cmcurl/lib/uint-bset.h b/Utilities/cmcurl/lib/uint-bset.h
index 7455c42..ce0450c 100644
--- a/Utilities/cmcurl/lib/uint-bset.h
+++ b/Utilities/cmcurl/lib/uint-bset.h
@@ -56,9 +56,6 @@
 /* Destroy the bitset, freeing all resources. */
 void Curl_uint32_bset_destroy(struct uint32_bset *bset);
 
-/* Get the bitset capacity, e.g. can hold numbers from 0 to capacity - 1. */
-uint32_t Curl_uint32_bset_capacity(struct uint32_bset *bset);
-
 /* Get the cardinality of the bitset, e.g. numbers present in the set. */
 uint32_t Curl_uint32_bset_count(struct uint32_bset *bset);
 
diff --git a/Utilities/cmcurl/lib/uint-hash.c b/Utilities/cmcurl/lib/uint-hash.c
index 2377840..c37448f 100644
--- a/Utilities/cmcurl/lib/uint-hash.c
+++ b/Utilities/cmcurl/lib/uint-hash.c
@@ -181,7 +181,9 @@
   return NULL;
 }
 
-static void uint_hash_clear(struct uint_hash *h)
+/* @unittest 1616 */
+UNITTEST void uint_hash_clear(struct uint_hash *h);
+UNITTEST void uint_hash_clear(struct uint_hash *h)
 {
   if(h && h->table) {
     struct uint_hash_entry *he, **he_anchor;
@@ -198,19 +200,12 @@
   }
 }
 
-#ifdef UNITTESTS
-UNITTEST void Curl_uint32_hash_clear(struct uint_hash *h)
-{
-  uint_hash_clear(h);
-}
-#endif
-
 void Curl_uint32_hash_destroy(struct uint_hash *h)
 {
   DEBUGASSERT(h->init == CURL_UINT32_HASHINIT);
   if(h->table) {
     uint_hash_clear(h);
-    Curl_safefree(h->table);
+    curlx_safefree(h->table);
   }
   DEBUGASSERT(h->size == 0);
   h->slots = 0;
diff --git a/Utilities/cmcurl/lib/uint-hash.h b/Utilities/cmcurl/lib/uint-hash.h
index 56d7104..3cbcf8f 100644
--- a/Utilities/cmcurl/lib/uint-hash.h
+++ b/Utilities/cmcurl/lib/uint-hash.h
@@ -44,8 +44,6 @@
                            uint32_t slots,
                            Curl_uint32_hash_dtor *dtor);
 void Curl_uint32_hash_destroy(struct uint_hash *h);
-void Curl_uint32_hash_clear(struct uint_hash *h);
-
 bool Curl_uint32_hash_set(struct uint_hash *h, uint32_t id, void *value);
 bool Curl_uint32_hash_remove(struct uint_hash *h, uint32_t id);
 void *Curl_uint32_hash_get(struct uint_hash *h, uint32_t id);
diff --git a/Utilities/cmcurl/lib/uint-spbset.c b/Utilities/cmcurl/lib/uint-spbset.c
index 58dcbda..3daa2ee 100644
--- a/Utilities/cmcurl/lib/uint-spbset.c
+++ b/Utilities/cmcurl/lib/uint-spbset.c
@@ -30,9 +30,6 @@
 #define CURL_UINT32_SPBSET_MAGIC  0x70737362
 #endif
 
-/* Clear the bitset, making it empty. */
-UNITTEST void Curl_uint32_spbset_clear(struct uint32_spbset *bset);
-
 void Curl_uint32_spbset_init(struct uint32_spbset *bset)
 {
   memset(bset, 0, sizeof(*bset));
@@ -41,10 +38,26 @@
 #endif
 }
 
+/* Clear the bitset, making it empty.
+
+   @unittest 3213
+ */
+UNITTEST void uint32_spbset_clear(struct uint32_spbset *bset);
+UNITTEST void uint32_spbset_clear(struct uint32_spbset *bset)
+{
+  struct uint32_spbset_chunk *next, *chunk;
+
+  for(chunk = bset->head.next; chunk; chunk = next) {
+    next = chunk->next;
+    curlx_free(chunk);
+  }
+  memset(&bset->head, 0, sizeof(bset->head));
+}
+
 void Curl_uint32_spbset_destroy(struct uint32_spbset *bset)
 {
   DEBUGASSERT(bset->init == CURL_UINT32_SPBSET_MAGIC);
-  Curl_uint32_spbset_clear(bset);
+  uint32_spbset_clear(bset);
 }
 
 uint32_t Curl_uint32_spbset_count(struct uint32_spbset *bset)
@@ -61,19 +74,8 @@
   return n;
 }
 
-UNITTEST void Curl_uint32_spbset_clear(struct uint32_spbset *bset)
-{
-  struct uint32_spbset_chunk *next, *chunk;
-
-  for(chunk = bset->head.next; chunk; chunk = next) {
-    next = chunk->next;
-    curlx_free(chunk);
-  }
-  memset(&bset->head, 0, sizeof(bset->head));
-}
-
-static struct uint32_spbset_chunk *
-uint32_spbset_get_chunk(struct uint32_spbset *bset, uint32_t i, bool grow)
+static struct uint32_spbset_chunk *uint32_spbset_get_chunk(
+  struct uint32_spbset *bset, uint32_t i, bool grow)
 {
   struct uint32_spbset_chunk *chunk, **panchor = NULL;
   uint32_t i_offset = (i & ~CURL_UINT32_SPBSET_CH_MASK);
diff --git a/Utilities/cmcurl/lib/uint-table.c b/Utilities/cmcurl/lib/uint-table.c
index b9c1990..addd2c9 100644
--- a/Utilities/cmcurl/lib/uint-table.c
+++ b/Utilities/cmcurl/lib/uint-table.c
@@ -29,9 +29,6 @@
 #define CURL_UINT32_TBL_MAGIC  0x62757473
 #endif
 
-/* Clear the table, making it empty. */
-UNITTEST void Curl_uint32_tbl_clear(struct uint32_tbl *tbl);
-
 void Curl_uint32_tbl_init(struct uint32_tbl *tbl,
                           Curl_uint32_tbl_entry_dtor *entry_dtor)
 {
@@ -82,15 +79,12 @@
   return CURLE_OK;
 }
 
-void Curl_uint32_tbl_destroy(struct uint32_tbl *tbl)
-{
-  DEBUGASSERT(tbl->init == CURL_UINT32_TBL_MAGIC);
-  Curl_uint32_tbl_clear(tbl);
-  curlx_free(tbl->rows);
-  memset(tbl, 0, sizeof(*tbl));
-}
+/* Clear the table, making it empty.
 
-UNITTEST void Curl_uint32_tbl_clear(struct uint32_tbl *tbl)
+   @unittest 3212
+ */
+UNITTEST void uint32_tbl_clear(struct uint32_tbl *tbl);
+UNITTEST void uint32_tbl_clear(struct uint32_tbl *tbl)
 {
   DEBUGASSERT(tbl->init == CURL_UINT32_TBL_MAGIC);
   uint32_tbl_clear_rows(tbl, 0, tbl->nrows);
@@ -98,6 +92,14 @@
   tbl->last_key_added = UINT32_MAX;
 }
 
+void Curl_uint32_tbl_destroy(struct uint32_tbl *tbl)
+{
+  DEBUGASSERT(tbl->init == CURL_UINT32_TBL_MAGIC);
+  uint32_tbl_clear(tbl);
+  curlx_free(tbl->rows);
+  memset(tbl, 0, sizeof(*tbl));
+}
+
 uint32_t Curl_uint32_tbl_capacity(struct uint32_tbl *tbl)
 {
   return tbl->nrows;
diff --git a/Utilities/cmcurl/lib/url.c b/Utilities/cmcurl/lib/url.c
index ec0457b..5fe6803 100644
--- a/Utilities/cmcurl/lib/url.c
+++ b/Utilities/cmcurl/lib/url.c
@@ -100,7 +100,7 @@
 #include "curlx/strerr.h"
 #include "curlx/strparse.h"
 
-/* And now for the protocols */
+/* Now for the protocols */
 #include "ftp.h"
 #include "dict.h"
 #include "telnet.h"
@@ -112,7 +112,6 @@
 #include "imap.h"
 #include "url.h"
 #include "connect.h"
-#include "curl_rtmp.h"
 #include "gopher.h"
 #include "mqtt.h"
 #include "rtsp.h"
@@ -133,10 +132,6 @@
 # error READBUFFER_SIZE is too small
 #endif
 
-#if !defined(CURL_DISABLE_PROXY) && defined(USE_UNIX_SOCKETS)
-#define UNIX_SOCKET_PREFIX "localhost"
-#endif
-
 /* Reject URLs exceeding this length */
 #define MAX_URL_LEN 0xffff
 
@@ -165,11 +160,11 @@
   enum dupblob j;
 
   for(i = (enum dupstring)0; i < STRING_LAST; i++) {
-    Curl_safefree(data->set.str[i]);
+    curlx_safefree(data->set.str[i]);
   }
 
   for(j = (enum dupblob)0; j < BLOB_LAST; j++) {
-    Curl_safefree(data->set.blobs[j]);
+    curlx_safefree(data->set.blobs[j]);
   }
 
   Curl_bufref_free(&data->state.referer);
@@ -177,7 +172,7 @@
 
 #if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API)
   Curl_mime_cleanpart(data->set.mimepostp);
-  Curl_safefree(data->set.mimepostp);
+  curlx_safefree(data->set.mimepostp);
 #endif
 
 #ifndef CURL_DISABLE_COOKIES
@@ -190,14 +185,14 @@
 static void up_free(struct Curl_easy *data)
 {
   struct urlpieces *up = &data->state.up;
-  Curl_safefree(up->scheme);
-  Curl_safefree(up->hostname);
-  Curl_safefree(up->port);
-  Curl_safefree(up->user);
-  Curl_safefree(up->password);
-  Curl_safefree(up->options);
-  Curl_safefree(up->path);
-  Curl_safefree(up->query);
+  curlx_safefree(up->scheme);
+  curlx_safefree(up->hostname);
+  curlx_safefree(up->port);
+  curlx_safefree(up->user);
+  curlx_safefree(up->password);
+  curlx_safefree(up->options);
+  curlx_safefree(up->path);
+  curlx_safefree(up->query);
   curl_url_cleanup(data->state.uh);
   data->state.uh = NULL;
 }
@@ -244,9 +239,7 @@
     curlx_free(data->state.range);
 
   /* release any resolve information this transfer kept */
-  Curl_async_destroy(data);
-  Curl_resolv_unlink(data, &data->state.dns[0]); /* done with this */
-  Curl_resolv_unlink(data, &data->state.dns[1]);
+  Curl_resolv_destroy_all(data);
 
   data->set.verbose = FALSE; /* no more calls to DEBUGFUNCTION */
   data->magic = 0; /* force a clear AFTER the possibly enforced removal from
@@ -259,7 +252,7 @@
 
   /* Close down all open SSL info and sessions */
   Curl_ssl_close_all(data);
-  Curl_safefree(data->state.first_host);
+  curlx_safefree(data->state.first_host);
   Curl_ssl_free_certinfo(data);
 
   Curl_bufref_free(&data->state.referer);
@@ -280,45 +273,38 @@
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_DIGEST_AUTH)
   Curl_http_auth_cleanup_digest(data);
 #endif
-  Curl_safefree(data->state.most_recent_ftp_entrypath);
-  Curl_safefree(data->info.contenttype);
-  Curl_safefree(data->info.wouldredirect);
+  curlx_safefree(data->state.most_recent_ftp_entrypath);
+  curlx_safefree(data->info.contenttype);
+  curlx_safefree(data->info.wouldredirect);
 
   data_priority_cleanup(data);
 
   /* No longer a dirty share, if it exists */
-  if(data->share) {
-    Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE);
-    data->share->dirty--;
-    Curl_share_unlock(data, CURL_LOCK_DATA_SHARE);
-  }
+  if(Curl_share_easy_unlink(data))
+    DEBUGASSERT(0);
 
   Curl_hash_destroy(&data->meta_hash);
-#ifndef CURL_DISABLE_PROXY
-  Curl_safefree(data->state.aptr.proxyuserpwd);
-#endif
-  Curl_safefree(data->state.aptr.uagent);
-  Curl_safefree(data->state.aptr.userpwd);
-  Curl_safefree(data->state.aptr.accept_encoding);
-  Curl_safefree(data->state.aptr.rangeline);
-  Curl_safefree(data->state.aptr.ref);
-  Curl_safefree(data->state.aptr.host);
+  curlx_safefree(data->state.aptr.uagent);
+  curlx_safefree(data->state.aptr.accept_encoding);
+  curlx_safefree(data->state.aptr.rangeline);
+  curlx_safefree(data->state.aptr.ref);
+  curlx_safefree(data->state.aptr.host);
 #ifndef CURL_DISABLE_COOKIES
-  Curl_safefree(data->state.aptr.cookiehost);
+  curlx_safefree(data->req.cookiehost);
 #endif
 #ifndef CURL_DISABLE_RTSP
-  Curl_safefree(data->state.aptr.rtsp_transport);
+  curlx_safefree(data->state.aptr.rtsp_transport);
 #endif
-  Curl_safefree(data->state.aptr.user);
-  Curl_safefree(data->state.aptr.passwd);
+  curlx_safefree(data->state.aptr.user);
+  curlx_safefree(data->state.aptr.passwd);
 #ifndef CURL_DISABLE_PROXY
-  Curl_safefree(data->state.aptr.proxyuser);
-  Curl_safefree(data->state.aptr.proxypasswd);
+  curlx_safefree(data->state.aptr.proxyuser);
+  curlx_safefree(data->state.aptr.proxypasswd);
 #endif
 
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API)
   Curl_mime_cleanpart(data->state.formp);
-  Curl_safefree(data->state.formp);
+  curlx_safefree(data->state.formp);
 #endif
 
   /* destruct wildcard structures if it is needed */
@@ -400,7 +386,7 @@
 #endif
 
   set->new_file_perms = 0644;    /* Default permissions */
-  set->allowed_protocols = (curl_prot_t) CURLPROTO_64ALL;
+  set->allowed_protocols = (curl_prot_t)CURLPROTO_64ALL;
   set->redir_protocols = CURLPROTO_REDIR;
 
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
@@ -436,6 +422,7 @@
   set->sep_headers = TRUE; /* separated header lists by default */
   set->buffer_size = READBUFFER_SIZE;
   set->upload_buffer_size = UPLOADBUFFER_DEFAULT;
+  set->upload_flags = CURLULFLAG_SEEN;
   set->happy_eyeballs_timeout = CURL_HET_DEFAULT;
   set->upkeep_interval_ms = CURL_UPKEEP_INTERVAL_DEFAULT;
   set->maxconnects = DEFAULT_CONNCACHE_SIZE; /* for easy handles */
@@ -458,7 +445,7 @@
 static void easy_meta_freeentry(void *p)
 {
   (void)p;
-  /* Will always be FALSE. Cannot use a 0 assert here since compilers
+  /* Always FALSE. Cannot use a 0 assert here since compilers
    * are not in agreement if they then want a NORETURN attribute or
    * not. *sigh* */
   DEBUGASSERT(p == NULL);
@@ -531,28 +518,28 @@
 #ifndef CURL_DISABLE_PROXY
   Curl_free_idnconverted_hostname(&conn->http_proxy.host);
   Curl_free_idnconverted_hostname(&conn->socks_proxy.host);
-  Curl_safefree(conn->http_proxy.user);
-  Curl_safefree(conn->socks_proxy.user);
-  Curl_safefree(conn->http_proxy.passwd);
-  Curl_safefree(conn->socks_proxy.passwd);
-  Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */
-  Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */
+  curlx_safefree(conn->http_proxy.user);
+  curlx_safefree(conn->socks_proxy.user);
+  curlx_safefree(conn->http_proxy.passwd);
+  curlx_safefree(conn->socks_proxy.passwd);
+  curlx_safefree(conn->http_proxy.host.rawalloc); /* http proxy name */
+  curlx_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name */
 #endif
-  Curl_safefree(conn->user);
-  Curl_safefree(conn->passwd);
-  Curl_safefree(conn->sasl_authzid);
-  Curl_safefree(conn->options);
-  Curl_safefree(conn->oauth_bearer);
-  Curl_safefree(conn->host.rawalloc); /* hostname buffer */
-  Curl_safefree(conn->conn_to_host.rawalloc); /* hostname buffer */
-  Curl_safefree(conn->secondaryhostname);
-  Curl_safefree(conn->localdev);
+  curlx_safefree(conn->user);
+  curlx_safefree(conn->passwd);
+  curlx_safefree(conn->sasl_authzid);
+  curlx_safefree(conn->options);
+  curlx_safefree(conn->oauth_bearer);
+  curlx_safefree(conn->host.rawalloc); /* hostname buffer */
+  curlx_safefree(conn->conn_to_host.rawalloc); /* hostname buffer */
+  curlx_safefree(conn->secondaryhostname);
+  curlx_safefree(conn->localdev);
   Curl_ssl_conn_config_cleanup(conn);
 
 #ifdef USE_UNIX_SOCKETS
-  Curl_safefree(conn->unix_domain_socket);
+  curlx_safefree(conn->unix_domain_socket);
 #endif
-  Curl_safefree(conn->destination);
+  curlx_safefree(conn->destination);
   Curl_hash_destroy(&conn->meta_hash);
 
   curlx_free(conn); /* free all the connection oriented data */
@@ -650,19 +637,12 @@
       /* avoid check if already too old */
       dead = TRUE;
     }
-    else if(conn->scheme->run->connection_check) {
+    else if(conn->scheme->run->connection_is_dead) {
       /* The protocol has a special method for checking the state of the
          connection. Use it to check if the connection is dead. */
-      unsigned int state;
-
-      /* briefly attach the connection to this transfer for the purpose of
-         checking it */
+      /* briefly attach the connection for the check */
       Curl_attach_connection(data, conn);
-
-      state = conn->scheme->run->connection_check(data, conn,
-                                                     CONNCHECK_ISDEAD);
-      dead = (state & CONNRESULT_DEAD);
-      /* detach the connection again */
+      dead = conn->scheme->run->connection_is_dead(data, conn);
       Curl_detach_connection(data);
     }
     else {
@@ -705,18 +685,7 @@
 
   /* briefly attach for action */
   Curl_attach_connection(data, conn);
-  if(conn->scheme->run->connection_check) {
-    /* Do a protocol-specific keepalive check on the connection. */
-    unsigned int rc;
-    rc = conn->scheme->run->connection_check(data, conn,
-                                                CONNCHECK_KEEPALIVE);
-    if(rc & CONNRESULT_DEAD)
-      result = CURLE_RECV_ERROR;
-  }
-  else {
-    /* Do the generic action on the FIRSTSOCKET filter chain */
-    result = Curl_conn_keep_alive(data, conn, FIRSTSOCKET);
-  }
+  result = Curl_conn_keep_alive(data, conn, FIRSTSOCKET);
   Curl_detach_connection(data);
 
   conn->keepalive = *Curl_pgrs_now(data);
@@ -745,7 +714,7 @@
   BIT(want_proxy_ntlm_http);
   BIT(want_nego_http);
   BIT(want_proxy_nego_http);
-
+  BIT(req_tls); /* require TLS use from a clear-text start */
   BIT(wait_pipe);
   BIT(force_reuse);
   BIT(seen_pending_conn);
@@ -757,7 +726,7 @@
                                      struct url_conn_match *m)
 {
   /* connect-only or to-be-closed connections will not be reused */
-  if(conn->connect_only || conn->bits.close || conn->bits.no_reuse)
+  if(conn->bits.connect_only || conn->bits.close || conn->bits.no_reuse)
     return FALSE;
 
   /* ip_version must match */
@@ -897,6 +866,9 @@
        (get_protocol_family(conn->scheme) != m->needle->scheme->protocol))
       return FALSE;
   }
+  else if(m->req_tls)
+    /* a clear-text STARTTLS protocol with required TLS */
+    return FALSE;
   return TRUE;
 }
 
@@ -1110,11 +1082,17 @@
   if(m->want_ntlm_http) {
     if(Curl_timestrcmp(m->needle->user, conn->user) ||
        Curl_timestrcmp(m->needle->passwd, conn->passwd)) {
-
       /* we prefer a credential match, but this is at least a connection
-         that can be reused and "upgraded" to NTLM */
-      if(conn->http_ntlm_state == NTLMSTATE_NONE)
+         that can be reused and "upgraded" to NTLM if it does
+         not have any auth ongoing. */
+#ifdef USE_SPNEGO
+      if((conn->http_ntlm_state == NTLMSTATE_NONE)
+         && (conn->http_negotiate_state == GSS_AUTHNONE)) {
+#else
+      if(conn->http_ntlm_state == NTLMSTATE_NONE) {
+#endif
         m->found = conn;
+      }
       return FALSE;
     }
   }
@@ -1285,10 +1263,9 @@
   return TRUE;
 }
 
-static bool url_match_result(bool result, void *userdata)
+static bool url_match_result(void *userdata)
 {
   struct url_conn_match *match = userdata;
-  (void)result;
   if(match->found) {
     /* Attach it now while still under lock, so the connection does
      * no longer appear idle and can be reaped. */
@@ -1324,7 +1301,7 @@
                                 bool *waitpipe)
 {
   struct url_conn_match match;
-  bool result;
+  bool success;
 
   DEBUGASSERT(!data->conn);
   memset(&match, 0, sizeof(match));
@@ -1355,16 +1332,17 @@
     (needle->scheme->protocol & PROTO_FAMILY_HTTP);
 #endif
 #endif
+  match.req_tls = data->set.use_ssl >= CURLUSESSL_CONTROL;
 
   /* Find a connection in the pool that matches what "data + needle"
    * requires. If a suitable candidate is found, it is attached to "data". */
-  result = Curl_cpool_find(data, needle->destination,
-                           url_match_conn, url_match_result, &match);
+  success = Curl_cpool_find(data, needle->destination,
+                            url_match_conn, url_match_result, &match);
 
   /* wait_pipe is TRUE if we encounter a bundle that is undecided. There
    * is no matching connection then, yet. */
   *waitpipe = (bool)match.wait_pipe;
-  return result;
+  return success;
 }
 
 /*
@@ -1384,7 +1362,7 @@
   conn->send_idx = 0; /* default for sending transfer data */
   conn->connection_id = -1;    /* no ID */
   conn->attached_xfers = 0;
-  conn->remote_port = -1; /* unknown at this point */
+  conn->remote_port = 0; /* unknown at this point */
 
   /* Store creation time to help future close decision making */
   conn->created = *Curl_pgrs_now(data);
@@ -1420,7 +1398,7 @@
   conn->bits.ftp_use_eprt = data->set.ftp_use_eprt;
 #endif
   conn->ip_version = data->set.ipver;
-  conn->connect_only = (bool)data->set.connect_only;
+  conn->bits.connect_only = (bool)data->set.connect_only;
   conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */
 
   /* Store the local bind parameters that will be used for this connection */
@@ -1451,80 +1429,6 @@
   return NULL;
 }
 
-const struct Curl_scheme *Curl_get_scheme(const char *scheme)
-{
-  return Curl_getn_scheme(scheme, strlen(scheme));
-}
-
-/* Returns a struct scheme pointer if the name is a known scheme. Check the
-   ->run struct field for non-NULL to figure out if an implementation is
-   present. */
-const struct Curl_scheme *Curl_getn_scheme(const char *scheme, size_t len)
-{
-  /* table generated by schemetable.c:
-     1. gcc schemetable.c && ./a.out
-     2. check how small the table gets
-     3. tweak the hash algorithm, then rerun from 1
-     4. when the table is good enough
-     5. copy the table into this source code
-     6. make sure this function uses the same hash function that worked for
-     schemetable.c
-     */
-  static const struct Curl_scheme * const all_schemes[67] = {
-    &Curl_scheme_file,
-    &Curl_scheme_mqtts, NULL,
-    &Curl_scheme_gophers, NULL,
-    &Curl_scheme_rtmpe,
-    &Curl_scheme_smtp,
-    &Curl_scheme_sftp,
-    &Curl_scheme_smb,
-    &Curl_scheme_smtps,
-    &Curl_scheme_telnet,
-    &Curl_scheme_gopher,
-    &Curl_scheme_tftp, NULL, NULL, NULL,
-    &Curl_scheme_ftps,
-    &Curl_scheme_http,
-    &Curl_scheme_imap,
-    &Curl_scheme_rtmps,
-    &Curl_scheme_rtmpt, NULL, NULL, NULL,
-    &Curl_scheme_ldaps,
-    &Curl_scheme_wss,
-    &Curl_scheme_https, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-    &Curl_scheme_rtsp,
-    &Curl_scheme_smbs,
-    &Curl_scheme_scp, NULL, NULL, NULL,
-    &Curl_scheme_pop3, NULL, NULL,
-    &Curl_scheme_rtmp, NULL, NULL, NULL,
-    &Curl_scheme_rtmpte, NULL, NULL, NULL,
-    &Curl_scheme_dict, NULL, NULL, NULL,
-    &Curl_scheme_mqtt,
-    &Curl_scheme_pop3s,
-    &Curl_scheme_imaps, NULL,
-    &Curl_scheme_ws, NULL,
-    &Curl_scheme_rtmpts,
-    &Curl_scheme_ldap, NULL, NULL,
-    &Curl_scheme_ftp,
-  };
-
-  if(len && (len <= 7)) {
-    const char *s = scheme;
-    size_t l = len;
-    const struct Curl_scheme *h;
-    unsigned int c = 978;
-    while(l) {
-      c <<= 5;
-      c += (unsigned int)Curl_raw_tolower(*s);
-      s++;
-      l--;
-    }
-
-    h = all_schemes[c % 67];
-    if(h && curl_strnequal(scheme, h->name, len) && !h->name[len])
-      return h;
-  }
-  return NULL;
-}
-
 static CURLcode findprotocol(struct Curl_easy *data,
                              struct connectdata *conn,
                              const char *protostr)
@@ -1627,6 +1531,7 @@
   CURLU *uh;
   CURLUcode uc;
   char *hostname;
+  size_t hlen;
   bool use_set_uh = (data->set.uh && !data->state.this_is_a_follow);
 
   up_free(data); /* cleanup previous leftovers first */
@@ -1685,17 +1590,16 @@
     failf(data, "Too long hostname (maximum is %d)", MAX_URL_LEN);
     return CURLE_URL_MALFORMAT;
   }
+
   hostname = data->state.up.hostname;
+  hlen = hostname ? strlen(hostname) : 0;
 
   if(hostname && hostname[0] == '[') {
     /* This looks like an IPv6 address literal. See if there is an address
        scope. */
-    size_t hlen;
-    conn->bits.ipv6_ip = TRUE;
-    /* cut off the brackets! */
+    /* cut off the brackets after copying this! */
     hostname++;
-    hlen = strlen(hostname);
-    hostname[hlen - 1] = 0;
+    hlen -= 2;
 
     zonefrom_url(uh, data, conn);
   }
@@ -1704,6 +1608,7 @@
   conn->host.rawalloc = curlx_strdup(hostname ? hostname : "");
   if(!conn->host.rawalloc)
     return CURLE_OUT_OF_MEMORY;
+  conn->host.rawalloc[hlen] = 0; /* cut off for ipv6 case */
   conn->host.name = conn->host.rawalloc;
 
   /*************************************************************
@@ -1719,7 +1624,7 @@
     /* This MUST use the IDN decoded name */
     if(Curl_hsts(data->hsts, conn->host.name, strlen(conn->host.name), TRUE)) {
       char *url;
-      Curl_safefree(data->state.up.scheme);
+      curlx_safefree(data->state.up.scheme);
       uc = curl_url_set(uh, CURLUPART_SCHEME, "https", 0);
       if(uc)
         return Curl_uc_to_curlcode(uc);
@@ -1880,7 +1785,7 @@
                                            struct connectdata *conn)
 {
   const char *hostname;
-  int port;
+  uint16_t port;
   CURLcode result;
 
   DEBUGF(infof(data, "setup connection, bits.close=%d", conn->bits.close));
@@ -1900,19 +1805,21 @@
   else
 #endif
   {
-    port = conn->remote_port;
-    if(conn->bits.conn_to_host)
-      hostname = conn->conn_to_host.name;
-    else
-      hostname = conn->host.name;
+    port = conn->bits.conn_to_port ?
+      conn->conn_to_port : conn->remote_port;
+    hostname = conn->bits.conn_to_host ?
+      conn->conn_to_host.name : conn->host.name;
   }
 
 #ifdef USE_IPV6
-  conn->destination = curl_maprintf("%u/%d/%s", conn->scope_id, port,
-                                    hostname);
-#else
-  conn->destination = curl_maprintf("%d/%s", port, hostname);
+  /* IPv6 addresses with a scope_id (0 is default == global) have a
+   * printable representation with a '%<scope_id>' suffix. */
+  if(conn->scope_id)
+    conn->destination = curl_maprintf("[%s:%u]%%%u", hostname, port,
+                                      conn->scope_id);
+  else
 #endif
+  conn->destination = curl_maprintf("%s:%u", hostname, port);
   if(!conn->destination)
     return CURLE_OUT_OF_MEMORY;
 
@@ -2082,15 +1989,13 @@
     goto error;
   }
 
-#ifdef USE_SSL
-  if(!Curl_ssl_supports(data, SSLSUPP_HTTPS_PROXY))
-#endif
-    if(IS_HTTPS_PROXY(proxytype)) {
-      failf(data, "Unsupported proxy \'%s\', libcurl is built without the "
-            "HTTPS-proxy support.", proxy);
-      result = CURLE_NOT_BUILT_IN;
-      goto error;
-    }
+  if(IS_HTTPS_PROXY(proxytype) &&
+     !Curl_ssl_supports(data, SSLSUPP_HTTPS_PROXY)) {
+    failf(data, "Unsupported proxy \'%s\', libcurl is built without the "
+          "HTTPS-proxy support.", proxy);
+    result = CURLE_NOT_BUILT_IN;
+    goto error;
+  }
 
   sockstype =
     proxytype == CURLPROXY_SOCKS5_HOSTNAME ||
@@ -2101,7 +2006,7 @@
   proxyinfo = sockstype ? &conn->socks_proxy : &conn->http_proxy;
   proxyinfo->proxytype = (unsigned char)proxytype;
 
-  /* Is there a username and password given in this proxy url? */
+  /* Is there a username and password given in this proxy URL? */
   uc = curl_url_get(uhp, CURLUPART_USER, &proxyuser, CURLU_URLDECODE);
   if(uc && (uc != CURLUE_NO_USER)) {
     result = Curl_uc_to_curlcode(uc);
@@ -2120,7 +2025,7 @@
     proxyuser = NULL;
     if(result)
       goto error;
-    Curl_safefree(proxyinfo->passwd);
+    curlx_safefree(proxyinfo->passwd);
     if(!proxypasswd) {
       proxypasswd = curlx_strdup("");
       if(!proxypasswd) {
@@ -2239,7 +2144,7 @@
     if(conn->http_proxy.passwd)
       result = CURLE_OK;
     else
-      Curl_safefree(conn->http_proxy.user);
+      curlx_safefree(conn->http_proxy.user);
   }
   return result;
 }
@@ -2300,15 +2205,15 @@
 
   if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ?
                         data->set.str[STRING_NOPROXY] : no_proxy)) {
-    Curl_safefree(proxy);
-    Curl_safefree(socksproxy);
+    curlx_safefree(proxy);
+    curlx_safefree(socksproxy);
   }
 #ifndef CURL_DISABLE_HTTP
   else if(!proxy && !socksproxy)
     /* if the host is not in the noproxy list, detect proxy. */
     proxy = detect_proxy(data, conn);
 #endif /* CURL_DISABLE_HTTP */
-  Curl_safefree(no_proxy);
+  curlx_safefree(no_proxy);
 
 #ifdef USE_UNIX_SOCKETS
   /* For the time being do not mix proxy and Unix domain sockets. See #1274 */
@@ -2337,18 +2242,18 @@
    * connection that may exist registered to the same proxy host.
    ***********************************************************************/
   if(proxy || socksproxy) {
-    long ptype = conn->http_proxy.proxytype;
     if(proxy) {
-      result = parse_proxy(data, conn, proxy, ptype);
-      Curl_safefree(proxy); /* parse_proxy copies the proxy string */
+      result = parse_proxy(data, conn, proxy, conn->http_proxy.proxytype);
+      curlx_safefree(proxy); /* parse_proxy copies the proxy string */
       if(result)
         goto out;
     }
 
     if(socksproxy) {
-      result = parse_proxy(data, conn, socksproxy, ptype);
+      result = parse_proxy(data, conn, socksproxy,
+                           conn->socks_proxy.proxytype);
       /* parse_proxy copies the socks proxy string */
-      Curl_safefree(socksproxy);
+      curlx_safefree(socksproxy);
       if(result)
         goto out;
     }
@@ -2573,8 +2478,8 @@
 
 #ifndef CURL_DISABLE_NETRC
   if(data->set.use_netrc == CURL_NETRC_REQUIRED) {
-    Curl_safefree(*userp);
-    Curl_safefree(*passwdp);
+    curlx_safefree(*userp);
+    curlx_safefree(*passwdp);
   }
   conn->bits.netrc = FALSE;
   if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) {
@@ -2835,19 +2740,15 @@
     ptr++;
   }
   else {
-    /* check whether the URL's hostname matches */
-    size_t hostname_to_match_len;
-    char *hostname_to_match = curl_maprintf("%s%s%s",
-                                            conn->bits.ipv6_ip ? "[" : "",
-                                            conn->host.name,
-                                            conn->bits.ipv6_ip ? "]" : "");
-    if(!hostname_to_match)
-      return CURLE_OUT_OF_MEMORY;
-    hostname_to_match_len = strlen(hostname_to_match);
-    host_match = curl_strnequal(ptr, hostname_to_match,
-                                hostname_to_match_len);
-    curlx_free(hostname_to_match);
-    ptr += hostname_to_match_len;
+    /* check whether the URL's hostname matches. Use the URL hostname
+     * when it was an IPv6 address. Otherwise use the connection's hostname
+     * that has IDN conversion. */
+    char *hostname_to_match =
+      (data->state.up.hostname && data->state.up.hostname[0] == '[') ?
+      data->state.up.hostname : conn->host.name;
+    size_t hlen = strlen(hostname_to_match);
+    host_match = curl_strnequal(ptr, hostname_to_match, hlen);
+    ptr += hlen;
 
     host_match = host_match && *ptr == ':';
     ptr++;
@@ -2908,13 +2809,13 @@
     else {
       /* no "connect to host" */
       conn->bits.conn_to_host = FALSE;
-      Curl_safefree(host);
+      curlx_safefree(host);
     }
 
     if(port >= 0) {
-      conn->conn_to_port = port;
+      conn->conn_to_port = (uint16_t)port;
       conn->bits.conn_to_port = TRUE;
-      infof(data, "Connecting to port: %d", port);
+      infof(data, "Connecting to port: %u", conn->conn_to_port);
     }
     else {
       /* no "connect to port" */
@@ -3016,7 +2917,7 @@
       conn->conn_to_port = as->dst.port;
       conn->bits.conn_to_port = TRUE;
       conn->bits.altused = TRUE;
-      infof(data, "Alt-svc connecting from [%s]%s:%d to [%s]%s:%d",
+      infof(data, "Alt-svc connecting from [%s]%s:%u to [%s]%s:%u",
             Curl_alpnid2str(srcalpnid), host, conn->remote_port,
             Curl_alpnid2str(as->dst.alpnid), hostd, as->dst.port);
       if(srcalpnid != as->dst.alpnid) {
@@ -3044,119 +2945,9 @@
   return result;
 }
 
-#ifdef USE_UNIX_SOCKETS
-static CURLcode resolve_unix(struct Curl_easy *data,
-                             struct connectdata *conn,
-                             const char *unix_path,
-                             struct Curl_dns_entry **pdns)
-{
-  struct Curl_dns_entry *hostaddr;
-  bool longpath = FALSE;
-
-  DEBUGASSERT(unix_path);
-  *pdns = NULL;
-
-  /* Unix domain sockets are local. The host gets ignored, use the specified
-   * domain socket address. Do not cache "DNS entries". There is no DNS
-   * involved and we already have the file system path available. */
-  hostaddr = curlx_calloc(1, sizeof(struct Curl_dns_entry));
-  if(!hostaddr)
-    return CURLE_OUT_OF_MEMORY;
-
-  hostaddr->addr = Curl_unix2addr(unix_path, &longpath,
-                                  (bool)conn->bits.abstract_unix_socket);
-  if(!hostaddr->addr) {
-    if(longpath)
-      /* Long paths are not supported for now */
-      failf(data, "Unix socket path too long: '%s'", unix_path);
-    curlx_free(hostaddr);
-    return longpath ? CURLE_COULDNT_RESOLVE_HOST : CURLE_OUT_OF_MEMORY;
-  }
-
-  hostaddr->refcount = 1; /* connection is the only one holding this */
-  *pdns = hostaddr;
-  return CURLE_OK;
-}
-#endif
-
-/*************************************************************
- * Resolve the address of the server or proxy
- *************************************************************/
-static CURLcode resolve_server(struct Curl_easy *data,
-                               struct connectdata *conn,
-                               bool *async,
-                               struct Curl_dns_entry **pdns)
-{
-  struct hostname *ehost;
-  int eport;
-  timediff_t timeout_ms = Curl_timeleft_ms(data);
-  const char *peertype = "host";
-  CURLcode result;
-
-  *pdns = NULL;
-
-#ifdef USE_UNIX_SOCKETS
-  {
-    const char *unix_path = conn->unix_domain_socket;
-
-#ifndef CURL_DISABLE_PROXY
-    if(!unix_path && CONN_IS_PROXIED(conn) && conn->socks_proxy.host.name &&
-       !strncmp(UNIX_SOCKET_PREFIX "/",
-                conn->socks_proxy.host.name, sizeof(UNIX_SOCKET_PREFIX)))
-      unix_path = conn->socks_proxy.host.name + sizeof(UNIX_SOCKET_PREFIX) - 1;
-#endif
-
-    if(unix_path) {
-      /* This only works if previous transport is TRNSPRT_TCP. Check it? */
-      conn->transport_wanted = TRNSPRT_UNIX;
-      return resolve_unix(data, conn, unix_path, pdns);
-    }
-  }
-#endif
-
-#ifndef CURL_DISABLE_PROXY
-  if(CONN_IS_PROXIED(conn)) {
-    ehost = conn->bits.socksproxy ? &conn->socks_proxy.host :
-      &conn->http_proxy.host;
-    eport = conn->bits.socksproxy ? conn->socks_proxy.port :
-      conn->http_proxy.port;
-    peertype = "proxy";
-  }
-  else
-#endif
-  {
-    ehost = conn->bits.conn_to_host ? &conn->conn_to_host : &conn->host;
-    /* If not connecting via a proxy, extract the port from the URL, if it is
-     * there, thus overriding any defaults that might have been set above. */
-    eport = conn->bits.conn_to_port ? conn->conn_to_port : conn->remote_port;
-  }
-
-  result = Curl_resolv_timeout(data, ehost->name,
-                               eport, conn->ip_version,
-                               pdns, timeout_ms);
-  DEBUGASSERT(!result || !*pdns);
-  if(result == CURLE_AGAIN) {
-    *async = TRUE;
-    return CURLE_OK;
-  }
-  else if(result == CURLE_OPERATION_TIMEDOUT) {
-    failf(data, "Failed to resolve %s '%s' with timeout after %"
-          FMT_TIMEDIFF_T " ms", peertype, ehost->dispname,
-          curlx_ptimediff_ms(Curl_pgrs_now(data),
-                             &data->progress.t_startsingle));
-    return CURLE_OPERATION_TIMEDOUT;
-  }
-  else if(result) {
-    failf(data, "Could not resolve %s: %s", peertype, ehost->dispname);
-    return result;
-  }
-  DEBUGASSERT(*pdns);
-  return CURLE_OK;
-}
-
 static void url_move_hostname(struct hostname *dest, struct hostname *src)
 {
-  Curl_safefree(dest->rawalloc);
+  curlx_safefree(dest->rawalloc);
   Curl_free_idnconverted_hostname(dest);
   *dest = *src;
   memset(src, 0, sizeof(*src));
@@ -3222,7 +3013,7 @@
 static void conn_meta_freeentry(void *p)
 {
   (void)p;
-  /* Will always be FALSE. Cannot use a 0 assert here since compilers
+  /* Always FALSE. Cannot use a 0 assert here since compilers
    * are not in agreement if they then want a NORETURN attribute or
    * not. *sigh* */
   DEBUGASSERT(p == NULL);
@@ -3399,6 +3190,10 @@
     needle->recv[SECONDARYSOCKET] = Curl_cf_recv;
     needle->send[SECONDARYSOCKET] = Curl_cf_send;
     needle->bits.tcp_fastopen = data->set.tcp_fastopen;
+#ifdef USE_UNIX_SOCKETS
+    if(Curl_conn_get_unix_path(needle))
+      needle->transport_wanted = TRNSPRT_UNIX;
+#endif
   }
 
 out:
@@ -3621,41 +3416,12 @@
   return result;
 }
 
-/* Curl_setup_conn() is called after the name resolve initiated in
- * create_conn() is all done.
- *
- * Curl_setup_conn() also handles reused connections
- */
-CURLcode Curl_setup_conn(struct Curl_easy *data,
-                         struct Curl_dns_entry *dns,
-                         bool *protocol_done)
-{
-  CURLcode result = CURLE_OK;
-  struct connectdata *conn = data->conn;
-
-  DEBUGASSERT(dns);
-  Curl_pgrsTime(data, TIMER_NAMELOOKUP);
-
-  if(!conn->bits.reuse)
-    result = Curl_conn_setup(data, conn, FIRSTSOCKET, dns,
-                             CURL_CF_SSL_DEFAULT);
-  if(!result)
-    result = Curl_headers_init(data);
-
-  /* not sure we need this flag to be passed around any more */
-  *protocol_done = FALSE;
-  return result;
-}
-
-CURLcode Curl_connect(struct Curl_easy *data,
-                      bool *asyncp,
-                      bool *protocol_done)
+CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected)
 {
   CURLcode result;
   struct connectdata *conn;
 
-  *asyncp = FALSE; /* assume synchronous resolves by default */
-  *protocol_done = FALSE;
+  *pconnected = FALSE;
 
   /* Set the request to virgin state based on transfer settings */
   Curl_req_hard_reset(&data->req, data);
@@ -3664,40 +3430,31 @@
   result = url_find_or_create_conn(data);
   conn = data->conn;
 
-  if(result == CURLE_NO_CONNECTION_AVAILABLE) {
-    DEBUGASSERT(!conn);
-    return result;
+  if(result)
+    goto out;
+
+  DEBUGASSERT(conn);
+  Curl_pgrsTime(data, TIMER_POSTQUEUE);
+  if(conn->bits.reuse) {
+    if(conn->attached_xfers > 1)
+      /* multiplexed */
+      *pconnected = TRUE;
+  }
+  else if(conn->scheme->flags & PROTOPT_NONETWORK) {
+    Curl_pgrsTime(data, TIMER_NAMELOOKUP);
+    *pconnected = TRUE;
+  }
+  else {
+    result = Curl_conn_setup(data, conn, FIRSTSOCKET, NULL,
+                             CURL_CF_SSL_DEFAULT);
+    if(!result)
+      result = Curl_headers_init(data);
+    CURL_TRC_M(data, "Curl_conn_setup() -> %d", result);
   }
 
-  if(!result) {
-    DEBUGASSERT(conn);
-    Curl_pgrsTime(data, TIMER_POSTQUEUE);
-    if(conn->bits.reuse) {
-      if(conn->attached_xfers > 1)
-        /* multiplexed */
-        *protocol_done = TRUE;
-    }
-    else if(conn->scheme->flags & PROTOPT_NONETWORK) {
-      *asyncp = FALSE;
-      Curl_pgrsTime(data, TIMER_NAMELOOKUP);
-      *protocol_done = TRUE;
-    }
-    else {
-      /*************************************************************
-       * Resolve the address of the server or proxy
-       *************************************************************/
-      struct Curl_dns_entry *dns;
-      result = resolve_server(data, conn, asyncp, &dns);
-      if(!result) {
-        *asyncp = !dns;
-        if(dns)
-          /* DNS resolution is done: that is either because this is a reused
-             connection, in which case DNS was unnecessary, or because DNS
-             really did finish already (synch resolver/fast async resolve) */
-          result = Curl_setup_conn(data, dns, protocol_done);
-      }
-    }
-  }
+out:
+  if(result == CURLE_NO_CONNECTION_AVAILABLE)
+    DEBUGASSERT(!conn);
 
   if(result && conn) {
     /* We are not allowed to return failure with memory left allocated in the
@@ -3734,6 +3491,7 @@
 
   data->state.done = FALSE; /* *_done() is not called yet */
 
+  data->req.no_body = data->set.opt_no_body;
   if(data->req.no_body)
     /* in HTTP lingo, no body means using the HEAD request... */
     data->state.httpreq = HTTPREQ_HEAD;
@@ -3865,11 +3623,6 @@
   return Curl_hash_pick(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1);
 }
 
-CURLcode Curl_1st_err(CURLcode r1, CURLcode r2)
-{
-  return r1 ? r1 : r2;
-}
-
 CURLcode Curl_1st_fatal(CURLcode r1, CURLcode r2)
 {
   if(r1 && (r1 != CURLE_AGAIN))
diff --git a/Utilities/cmcurl/lib/url.h b/Utilities/cmcurl/lib/url.h
index c4cd762..66baf70 100644
--- a/Utilities/cmcurl/lib/url.h
+++ b/Utilities/cmcurl/lib/url.h
@@ -26,7 +26,7 @@
 #include "curl_setup.h"
 
 /*
- * Prototypes for library-wide functions provided by url.c
+ * Prototypes for library-wide functions
  */
 
 CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn);
@@ -36,12 +36,7 @@
 void Curl_freeset(struct Curl_easy *data);
 CURLcode Curl_uc_to_curlcode(CURLUcode uc);
 CURLcode Curl_close(struct Curl_easy **datap); /* opposite of Curl_open() */
-CURLcode Curl_connect(struct Curl_easy *data,
-                      bool *asyncp,
-                      bool *protocol_done);
-CURLcode Curl_setup_conn(struct Curl_easy *data,
-                         struct Curl_dns_entry *dns,
-                         bool *protocol_done);
+CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected);
 void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn);
 CURLcode Curl_parse_login_details(const char *login, const size_t len,
                                   char **userp, char **passwdp,
@@ -69,13 +64,6 @@
 void Curl_conn_meta_remove(struct connectdata *conn, const char *key);
 void *Curl_conn_meta_get(struct connectdata *conn, const char *key);
 
-/* Get protocol handler for a URI scheme
- * @param scheme URI scheme, case-insensitive
- * @return NULL of handler not found
- */
-const struct Curl_scheme *Curl_get_scheme(const char *scheme);
-const struct Curl_scheme *Curl_getn_scheme(const char *scheme, size_t len);
-
 #define CURL_DEFAULT_PROXY_PORT 1080 /* default proxy port unless specified */
 #define CURL_DEFAULT_HTTPS_PROXY_PORT 443 /* default https proxy port unless
                                              specified */
@@ -93,15 +81,8 @@
                           struct connectdata *conn);
 
 /**
- * Always eval all arguments, return the first result != CURLE_OK.
- * A non-short-circuit evaluation.
- */
-CURLcode Curl_1st_err(CURLcode r1, CURLcode r2);
-
-/**
  * Always eval all arguments, return the first
- * result != (CURLE_OK|CURLE_AGAIN) or `r1`.
- * A non-short-circuit evaluation.
+ * result != (CURLE_OK | CURLE_AGAIN) or `r1`.
  */
 CURLcode Curl_1st_fatal(CURLcode r1, CURLcode r2);
 
diff --git a/Utilities/cmcurl/lib/urlapi-int.h b/Utilities/cmcurl/lib/urlapi-int.h
index 29d4fe5..4d8f2c1 100644
--- a/Utilities/cmcurl/lib/urlapi-int.h
+++ b/Utilities/cmcurl/lib/urlapi-int.h
@@ -24,6 +24,34 @@
  *
  ***************************************************************************/
 #include "curl_setup.h"
+#include <curl/urlapi.h>
+
+/* Internal representation of CURLU. Point to URL-encoded strings. */
+struct Curl_URL {
+  char *scheme;
+  char *user;
+  char *password;
+  char *options; /* IMAP only? */
+  char *host;
+  char *zoneid; /* for numerical IPv6 addresses */
+  char *port;
+  char *path;
+  char *query;
+  char *fragment;
+  unsigned short portnum; /* the numerical version (if 'port' is set) */
+  BIT(query_present);    /* to support blank */
+  BIT(fragment_present); /* to support blank */
+  BIT(guessed_scheme);   /* when a URL without scheme is parsed */
+};
+
+#define HOST_ERROR   (-1) /* out of memory */
+#define HOST_NAME    1
+#define HOST_IPV4    2
+#define HOST_IPV6    3
+
+#define QUERY_NO      2
+#define QUERY_NOT_YET 3 /* allow to change to query */
+#define QUERY_YES     4
 
 size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen,
                             bool guess_scheme);
@@ -32,12 +60,9 @@
 
 CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace);
 
-#ifdef UNITTESTS
-UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host,
-                                   bool has_scheme);
-#endif
-
-#define U_CURLU_URLDECODE (unsigned int)CURLU_URLDECODE
+#define U_CURLU_URLDECODE  (unsigned int)CURLU_URLDECODE
 #define U_CURLU_PATH_AS_IS (unsigned int)CURLU_PATH_AS_IS
 
+bool Curl_url_same_origin(CURLU *base, CURLU *href);
+
 #endif /* HEADER_CURL_URLAPI_INT_H */
diff --git a/Utilities/cmcurl/lib/urlapi.c b/Utilities/cmcurl/lib/urlapi.c
index a4b82f3..103e0a6 100644
--- a/Utilities/cmcurl/lib/urlapi.c
+++ b/Utilities/cmcurl/lib/urlapi.c
@@ -63,24 +63,6 @@
 #define AF_INET6 (AF_INET + 1)
 #endif
 
-/* Internal representation of CURLU. Point to URL-encoded strings. */
-struct Curl_URL {
-  char *scheme;
-  char *user;
-  char *password;
-  char *options; /* IMAP only? */
-  char *host;
-  char *zoneid; /* for numerical IPv6 addresses */
-  char *port;
-  char *path;
-  char *query;
-  char *fragment;
-  unsigned short portnum; /* the numerical version (if 'port' is set) */
-  BIT(query_present);    /* to support blank */
-  BIT(fragment_present); /* to support blank */
-  BIT(guessed_scheme);   /* when a URL without scheme is parsed */
-};
-
 #define DEFAULT_SCHEME "https"
 
 static void free_urlhandle(struct Curl_URL *u)
@@ -124,19 +106,34 @@
 /* urlencode_str() writes data into an output dynbuf and URL-encodes the
  * spaces in the source URL accordingly.
  *
+ * This function re-encodes the string, meaning that it leaves already encoded
+ * bytes as-is and works by encoding only what *has* to be encoded - unless it
+ * has to uppercase the hex to normalize.
+ *
+ * Illegal percent-encoding sequences are left as-is.
+ *
  * URL encoding should be skipped for hostnames, otherwise IDN resolution
  * will fail.
+ *
+ * 'query' tells if it is a query part or not, or if it is allowed to
+ * "transition" into a query part with a question mark.
+ *
+ * @unittest 1675
  */
-static CURLUcode urlencode_str(struct dynbuf *o, const char *url,
-                               size_t len, bool relative,
-                               bool query)
+UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url,
+                                 size_t len, bool relative,
+                                 unsigned int query);
+UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url,
+                                 size_t len, bool relative,
+                                 unsigned int query)
 {
   /* we must add this with whitespace-replacing */
-  bool left = !query;
   const unsigned char *iptr;
   const unsigned char *host_sep = (const unsigned char *)url;
   CURLcode result = CURLE_OK;
 
+  DEBUGASSERT((query >= QUERY_NO) && (query <= QUERY_YES));
+
   if(!relative) {
     size_t n;
     host_sep = (const unsigned char *)find_host_sep(url);
@@ -149,7 +146,7 @@
 
   for(iptr = host_sep; len && !result; iptr++, len--) {
     if(*iptr == ' ') {
-      if(left)
+      if(query != QUERY_YES)
         result = curlx_dyn_addn(o, "%20", 3);
       else
         result = curlx_dyn_addn(o, "+", 1);
@@ -159,10 +156,22 @@
       Curl_hexbyte(&out[1], *iptr);
       result = curlx_dyn_addn(o, out, 3);
     }
+    else if(*iptr == '%' && (len >= 3) &&
+            ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
+            (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) {
+      /* uppercase it */
+      unsigned char hex = (unsigned char)((curlx_hexval(iptr[1]) << 4) |
+                                          curlx_hexval(iptr[2]));
+      unsigned char out[3] = { '%' };
+      Curl_hexbyte(&out[1], hex);
+      result = curlx_dyn_addn(o, out, 3);
+      iptr += 2;
+      len -= 2;
+    }
     else {
       result = curlx_dyn_addn(o, iptr, 1);
-      if(*iptr == '?')
-        left = FALSE;
+      if(*iptr == '?' && (query == QUERY_NOT_YET))
+        query = QUERY_YES;
     }
   }
 
@@ -251,8 +260,8 @@
                                       unsigned int flags,
                                       size_t *offset) /* to the hostname */
 {
-  CURLUcode result = CURLUE_OK;
-  CURLcode ccode;
+  CURLUcode ures = CURLUE_OK;
+  CURLcode result;
   char *userp = NULL;
   char *passwdp = NULL;
   char *optionsp = NULL;
@@ -285,21 +294,21 @@
 
   /* We could use the login information in the URL so extract it. Only parse
      options if the handler says we should. Note that 'h' might be NULL! */
-  ccode = Curl_parse_login_details(login, ptr - login - 1,
-                                   &userp, &passwdp,
-                                   (h && (h->flags & PROTOPT_URLOPTIONS)) ?
-                                   &optionsp : NULL);
-  if(ccode) {
+  result = Curl_parse_login_details(login, ptr - login - 1,
+                                    &userp, &passwdp,
+                                    (h && (h->flags & PROTOPT_URLOPTIONS)) ?
+                                    &optionsp : NULL);
+  if(result) {
     /* the only possible error from Curl_parse_login_details is out of
        memory: */
-    result = CURLUE_OUT_OF_MEMORY;
+    ures = CURLUE_OUT_OF_MEMORY;
     goto out;
   }
 
   if(userp) {
     if(flags & CURLU_DISALLOW_USER) {
       /* Option DISALLOW_USER is set and URL contains username. */
-      result = CURLUE_USER_NOT_ALLOWED;
+      ures = CURLUE_USER_NOT_ALLOWED;
       goto out;
     }
     curlx_free(u->user);
@@ -329,11 +338,14 @@
   u->password = NULL;
   u->options = NULL;
 
-  return result;
+  return ures;
 }
 
-UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host,
-                                   bool has_scheme)
+/* @unittest 1653 */
+UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host,
+                              bool has_scheme);
+UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host,
+                              bool has_scheme)
 {
   const char *portptr;
   const char *hostname = curlx_dyn_ptr(host);
@@ -386,9 +398,15 @@
   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 */
+/* This function assumes 'hostname' now starts with [. It trims 'hostname' in
+ * place and it sets u->zoneid if present.
+ *
+ * @unittest 1675
+ */
+UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname,
+                              size_t hlen);
+UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname,
+                              size_t hlen) /* length of hostname */
 {
   size_t len;
   DEBUGASSERT(*hostname == '[');
@@ -472,15 +490,12 @@
  * integers.
  *
  * Returns the host type.
+ *
+ * @unittest 1675
  */
 
-#define HOST_ERROR   (-1) /* out of memory */
-
-#define HOST_NAME    1
-#define HOST_IPV4    2
-#define HOST_IPV6    3
-
-static int ipv4_normalize(struct dynbuf *host)
+UNITTEST int ipv4_normalize(struct dynbuf *host);
+UNITTEST int ipv4_normalize(struct dynbuf *host)
 {
   bool done = FALSE;
   int n = 0;
@@ -624,7 +639,7 @@
     goto out;
   }
 
-  uc = Curl_parse_port(u, host, has_scheme);
+  uc = parse_port(u, host, has_scheme);
   if(uc)
     goto out;
 
@@ -657,21 +672,21 @@
 /* used for HTTP/2 server push */
 CURLUcode Curl_url_set_authority(CURLU *u, const char *authority)
 {
-  CURLUcode result;
+  CURLUcode ures;
   struct dynbuf host;
 
   DEBUGASSERT(authority);
   curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
 
-  result = parse_authority(u, authority, strlen(authority),
-                           CURLU_DISALLOW_USER, &host, !!u->scheme);
-  if(result)
+  ures = parse_authority(u, authority, strlen(authority),
+                         CURLU_DISALLOW_USER, &host, !!u->scheme);
+  if(ures)
     curlx_dyn_free(&host);
   else {
     curlx_free(u->host);
     u->host = curlx_dyn_ptr(&host);
   }
-  return result;
+  return ures;
 }
 
 /*
@@ -700,7 +715,6 @@
 
 /*
  * dedotdotify()
- * @unittest: 1395
  *
  * This function gets a null-terminated path with dot and dotdot sequences
  * passed in and strips them off according to the rules in RFC 3986 section
@@ -711,6 +725,8 @@
  * RETURNS
  *
  * Zero for success and 'out' set to an allocated dedotdotified string.
+ *
+ * @unittest 1395
  */
 UNITTEST int dedotdotify(const char *input, size_t clen, char **outp);
 UNITTEST int dedotdotify(const char *input, size_t clen, char **outp)
@@ -718,38 +734,36 @@
   struct dynbuf out;
   CURLcode result = CURLE_OK;
 
+  /* variables for leading dot checks */
+  const char *dinput = input;
+  size_t dlen = clen;
+
   *outp = NULL;
-  /* the path always starts with a slash, and a slash has not dot */
+  /* a single byte path cannot be cleaned up */
   if(clen < 2)
     return 0;
 
   curlx_dyn_init(&out, clen + 1);
 
-  /*  A. If the input buffer begins with a prefix of "../" or "./", then
-      remove that prefix from the input buffer; otherwise, */
-  if(is_dot(&input, &clen)) {
-    const char *p = input;
-    size_t blen = clen;
-
-    if(!clen)
-      /* . [end] */
-      goto end;
-    else if(ISSLASH(*p)) {
+  /* if the input buffer begins with a prefix of "../" or "./", then remove
+     that prefix from the input buffer; otherwise, */
+  if(is_dot(&dinput, &dlen)) {
+    if(ISSLASH(*dinput)) {
       /* one dot followed by a slash */
-      input = p + 1;
-      clen--;
+      input = dinput + 1;
+      clen = dlen - 1;
     }
 
-    /*  D. if the input buffer consists only of "." or "..", then remove
-        that from the input buffer; otherwise, */
-    else if(is_dot(&p, &blen)) {
-      if(!blen)
+    /* if the input buffer consists only of "." or "..", then remove
+       that from the input buffer; otherwise, */
+    else if(is_dot(&dinput, &dlen)) {
+      if(!dlen)
         /* .. [end] */
         goto end;
-      else if(ISSLASH(*p)) {
+      else if(ISSLASH(*dinput)) {
         /* ../ */
-        input = p + 1;
-        clen = blen - 1;
+        input = dinput + 1;
+        clen = dlen - 1;
       }
     }
   }
@@ -758,9 +772,9 @@
     if(ISSLASH(*input)) {
       const char *p = &input[1];
       size_t blen = clen - 1;
-      /*  B. if the input buffer begins with a prefix of "/./" or "/.", where
-          "."  is a complete path segment, then replace that prefix with "/" in
-          the input buffer; otherwise, */
+      /* if the input buffer begins with a prefix of "/./" or "/.", where "."
+         is a complete path segment, then replace that prefix with "/" in the
+         input buffer; otherwise, */
       if(is_dot(&p, &blen)) {
         if(!blen) { /* /. */
           result = curlx_dyn_addn(&out, "/", 1);
@@ -772,10 +786,10 @@
           continue;
         }
 
-        /*  C. if the input buffer begins with a prefix of "/../" or "/..",
-            where ".." is a complete path segment, then replace that prefix
-            with "/" in the input buffer and remove the last segment and its
-            preceding "/" (if any) from the output buffer; otherwise, */
+        /* if the input buffer begins with a prefix of "/../" or "/..", where
+           ".." is a complete path segment, then replace that prefix with "/"
+           in the input buffer and remove the last segment and its preceding
+           "/" (if any) from the output buffer; otherwise, */
         else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) {
           /* remove the last segment from the output buffer */
           size_t len = curlx_dyn_len(&out);
@@ -798,10 +812,10 @@
       }
     }
 
-    /*  E. move the first path segment in the input buffer to the end of
-        the output buffer, including the initial "/" character (if any) and
-        any subsequent characters up to, but not including, the next "/"
-        character or the end of the input buffer. */
+    /* move the first path segment in the input buffer to the end of the
+       output buffer, including the initial "/" character (if any) and any
+       subsequent characters up to, but not including, the next "/" character
+       or the end of the input buffer. */
 
     result = curlx_dyn_addn(&out, input, 1);
     input++;
@@ -820,13 +834,19 @@
   return result ? 1 : 0; /* success */
 }
 
-static CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
-                            struct dynbuf *host, const char **pathp,
-                            size_t *pathlenp)
+/*
+ * @unittest 1675
+ */
+UNITTEST CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
+                              const char **pathp, size_t *pathlenp);
+UNITTEST CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
+                              const char **pathp, size_t *pathlenp)
 {
   const char *path;
   size_t pathlen;
-  bool uncpath = FALSE;
+
+  *pathp = NULL;
+  *pathlenp = 0;
   if(urllen <= 6)
     /* file:/ is not enough to actually be a complete file: URL */
     return CURLUE_BAD_FILE_URL;
@@ -835,10 +855,6 @@
   path = &url[5];
   pathlen = urllen - 5;
 
-  u->scheme = curlx_strdup("file");
-  if(!u->scheme)
-    return CURLUE_OUT_OF_MEMORY;
-
   /* Extra handling URLs with an authority component (i.e. that start with
    * "file://")
    *
@@ -858,9 +874,6 @@
      *
      *  o the hostname is a FQDN that resolves to this machine, or
      *
-     *  o it is an UNC String transformed to an URI (Windows only, RFC 8089
-     *    Appendix E.3).
-     *
      * For brevity, we only consider URLs with empty, "localhost", or
      * "127.0.0.1" hostnames as local, otherwise as an UNC String.
      *
@@ -875,42 +888,16 @@
          checkprefix("127.0.0.1/", ptr)) {
         ptr += 9; /* now points to the slash after the host */
       }
-      else {
-#ifdef _WIN32
-        size_t len;
-
-        /* the hostname, NetBIOS computer name, can not contain disallowed
-           chars, and the delimiting slash character must be appended to the
-           hostname */
-        path = strpbrk(ptr, "/\\:*?\"<>|");
-        if(!path || *path != '/')
-          return CURLUE_BAD_FILE_URL;
-
-        len = path - ptr;
-        if(len) {
-          CURLcode code = curlx_dyn_addn(host, ptr, len);
-          if(code)
-            return cc2cu(code);
-          uncpath = TRUE;
-        }
-
-        ptr -= 2; /* now points to the // before the host in UNC */
-#else
+      else
         /* Invalid file://hostname/, expected localhost or 127.0.0.1 or
            none */
         return CURLUE_BAD_FILE_URL;
-#endif
-      }
     }
 
     path = ptr;
     pathlen = urllen - (ptr - url);
   }
 
-  if(!uncpath)
-    /* no host for file: URLs by default */
-    curlx_dyn_reset(host);
-
 #if !defined(_WIN32) && !defined(MSDOS) && !defined(__CYGWIN__)
   /* Do not allow Windows drive letters when not in Windows.
    * This catches both "file:/c:" and "file:c:" */
@@ -927,6 +914,10 @@
     pathlen--;
   }
 #endif
+  u->scheme = curlx_strdup("file");
+  if(!u->scheme)
+    return CURLUE_OUT_OF_MEMORY;
+
   *pathp = path;
   *pathlenp = pathlen;
   return CURLUE_OK;
@@ -1012,16 +1003,16 @@
 static CURLUcode handle_fragment(CURLU *u, const char *fragment,
                                  size_t fraglen, unsigned int flags)
 {
-  CURLUcode result;
+  CURLUcode ures;
   u->fragment_present = TRUE;
   if(fraglen > 1) {
     /* skip the leading '#' in the copy but include the terminating null */
     if(flags & CURLU_URLENCODE) {
       struct dynbuf enc;
       curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
-      result = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, FALSE);
-      if(result)
-        return result;
+      ures = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, QUERY_NO);
+      if(ures)
+        return ures;
       u->fragment = curlx_dyn_ptr(&enc);
     }
     else {
@@ -1040,12 +1031,12 @@
   if(qlen > 1) {
     if(flags & CURLU_URLENCODE) {
       struct dynbuf enc;
-      CURLUcode result;
+      CURLUcode ures;
       curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
       /* skip the leading question mark */
-      result = urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE);
-      if(result)
-        return result;
+      ures = urlencode_str(&enc, query + 1, qlen - 1, TRUE, QUERY_YES);
+      if(ures)
+        return ures;
       u->query = curlx_dyn_ptr(&enc);
     }
     else {
@@ -1064,24 +1055,22 @@
 }
 
 static CURLUcode handle_path(CURLU *u, const char *path,
-                             size_t pathlen, unsigned int flags)
+                             size_t pathlen, unsigned int flags,
+                             bool is_file)
 {
-  CURLUcode result;
+  CURLUcode ures;
   if(pathlen && (flags & CURLU_URLENCODE)) {
     struct dynbuf enc;
     curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
-    result = urlencode_str(&enc, path, pathlen, TRUE, FALSE);
-    if(result)
-      return result;
+    ures = urlencode_str(&enc, path, pathlen, TRUE, QUERY_NO);
+    if(ures)
+      return ures;
     pathlen = curlx_dyn_len(&enc);
     path = u->path = curlx_dyn_ptr(&enc);
   }
 
-  if(pathlen <= 1) {
-    /* there is no path left or the slash, unset */
-    path = NULL;
-  }
-  else {
+  if(pathlen >= (size_t)(1 + !is_file)) {
+    /* paths for file:// scheme can be one byte, others need to be two */
     if(!u->path) {
       u->path = curlx_memdup0(path, pathlen);
       if(!u->path)
@@ -1114,15 +1103,16 @@
   char schemebuf[MAX_SCHEME_LEN + 1];
   size_t schemelen = 0;
   size_t urllen;
-  CURLUcode result = CURLUE_OK;
+  CURLUcode ures = CURLUE_OK;
   struct dynbuf host;
+  bool is_file = FALSE;
 
   DEBUGASSERT(url);
 
   curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
 
-  result = Curl_junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE));
-  if(result)
+  ures = Curl_junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE));
+  if(ures)
     goto fail;
 
   schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf),
@@ -1130,13 +1120,15 @@
                                             CURLU_DEFAULT_SCHEME));
 
   /* handle the file: scheme */
-  if(schemelen && !strcmp(schemebuf, "file"))
-    result = parse_file(url, urllen, u, &host, &path, &pathlen);
+  if(schemelen && !strcmp(schemebuf, "file")) {
+    is_file = TRUE;
+    ures = parse_file(url, urllen, u, &path, &pathlen);
+  }
   else {
     const char *hostp = NULL;
     size_t hostlen;
-    result = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp);
-    if(result)
+    ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp);
+    if(ures)
       goto fail;
 
     /* find the end of the hostname + port number */
@@ -1146,49 +1138,49 @@
     /* this pathlen also contains the query and the fragment */
     pathlen = urllen - (path - url);
     if(hostlen) {
-      result = parse_authority(u, hostp, hostlen, flags, &host,
-                               u->scheme != NULL);
-      if(!result && (flags & CURLU_GUESS_SCHEME) && !u->scheme)
-        result = guess_scheme(u, &host);
+      ures = parse_authority(u, hostp, hostlen, flags, &host,
+                             u->scheme != NULL);
+      if(!ures && (flags & CURLU_GUESS_SCHEME) && !u->scheme)
+        ures = guess_scheme(u, &host);
     }
     else if(flags & CURLU_NO_AUTHORITY) {
       /* allowed to be empty. */
       if(curlx_dyn_add(&host, ""))
-        result = CURLUE_OUT_OF_MEMORY;
+        ures = CURLUE_OUT_OF_MEMORY;
     }
     else
-      result = CURLUE_NO_HOST;
+      ures = CURLUE_NO_HOST;
   }
-  if(!result) {
+  if(!ures) {
     /* The path might at this point contain a fragment and/or a query to
        handle */
     const char *fragment = strchr(path, '#');
     if(fragment) {
       size_t fraglen = pathlen - (fragment - path);
-      result = handle_fragment(u, fragment, fraglen, flags);
+      ures = handle_fragment(u, fragment, fraglen, flags);
       /* after this, pathlen still contains the query */
       pathlen -= fraglen;
     }
   }
-  if(!result) {
+  if(!ures) {
     const char *query = memchr(path, '?', pathlen);
     if(query) {
       size_t qlen = pathlen - (query - path);
-      result = handle_query(u, query, qlen, flags);
+      ures = handle_query(u, query, qlen, flags);
       pathlen -= qlen;
     }
   }
-  if(!result)
+  if(!ures)
     /* the fragment and query parts are trimmed off from the path */
-    result = handle_path(u, path, pathlen, flags);
-  if(!result) {
+    ures = handle_path(u, path, pathlen, flags, is_file);
+  if(!ures) {
     u->host = curlx_dyn_ptr(&host);
     return CURLUE_OK;
   }
 fail:
   curlx_dyn_free(&host);
   free_urlhandle(u);
-  return result;
+  return ures;
 }
 
 /*
@@ -1197,15 +1189,15 @@
 static CURLUcode parseurl_and_replace(const char *url, CURLU *u,
                                       unsigned int flags)
 {
-  CURLUcode result;
+  CURLUcode ures;
   CURLU tmpurl;
   memset(&tmpurl, 0, sizeof(tmpurl));
-  result = parseurl(url, &tmpurl, flags);
-  if(!result) {
+  ures = parseurl(url, &tmpurl, flags);
+  if(!ures) {
     free_urlhandle(u);
     *u = tmpurl;
   }
-  return result;
+  return ures;
 }
 
 /*
@@ -1272,7 +1264,8 @@
   curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH);
 
   if(!curlx_dyn_addn(&urlbuf, base, prelen) &&
-     !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed, FALSE)) {
+     !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed,
+                    QUERY_NOT_YET)) {
     uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u,
                               flags & ~U_CURLU_PATH_AS_IS);
   }
@@ -1380,11 +1373,12 @@
   if(urldecode) {
     char *decoded;
     size_t dlen;
-    /* this unconditional rejection of control bytes is documented
-       API behavior */
-    CURLcode res = Curl_urldecode(part, partlen, &decoded, &dlen, REJECT_CTRL);
+    /* this unconditional rejection of control bytes is documented API
+       behavior */
+    CURLcode result = Curl_urldecode(part, partlen, &decoded, &dlen,
+                                     REJECT_CTRL);
     curlx_free(part);
-    if(res)
+    if(result)
       return CURLUE_URLDECODE;
     part = decoded;
     partlen = dlen;
@@ -1392,7 +1386,8 @@
   if(urlencode) {
     struct dynbuf enc;
     curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
-    uc = urlencode_str(&enc, part, partlen, TRUE, what == CURLUPART_QUERY);
+    uc = urlencode_str(&enc, part, partlen, TRUE, what == CURLUPART_QUERY ?
+                       QUERY_YES : QUERY_NO);
     curlx_free(part);
     if(uc)
       return uc;
@@ -1425,29 +1420,25 @@
 static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
 {
   char *url;
-  const char *scheme;
-  char *options = u->options;
-  char *port = u->port;
   char *allochost = NULL;
-  bool show_fragment =
-    u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY);
-  bool show_query = (u->query && u->query[0]) ||
-    (u->query_present && flags & CURLU_GET_EMPTY);
-  bool punycode = (flags & CURLU_PUNYCODE) ? 1 : 0;
-  bool depunyfy = (flags & CURLU_PUNY2IDN) ? 1 : 0;
-  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
+  const char *fragmentsep =
+    (u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY)) ?
+    "#" : "";
+  const char *querysep = ((u->query && u->query[0]) ||
+                          (u->query_present && flags & CURLU_GET_EMPTY)) ?
+    "?" : "";
   char portbuf[7];
   if(u->scheme && curl_strequal("file", u->scheme)) {
     url = curl_maprintf("file://%s%s%s%s%s",
-                        u->path,
-                        show_query ? "?" : "",
-                        u->query ? u->query : "",
-                        show_fragment ? "#" : "",
-                        u->fragment ? u->fragment : "");
+                        u->path, querysep, u->query ? u->query : "",
+                        fragmentsep, u->fragment ? u->fragment : "");
   }
   else if(!u->host)
     return CURLUE_NO_HOST;
   else {
+    const char *scheme;
+    char *options = u->options;
+    char *port = u->port;
     const struct Curl_scheme *h = NULL;
     char schemebuf[MAX_SCHEME_LEN + 5];
     if(u->scheme)
@@ -1489,19 +1480,19 @@
         allochost = curlx_dyn_ptr(&enc);
       }
     }
-    else if(urlencode) {
+    else if(flags & CURLU_URLENCODE) {
       allochost = curl_easy_escape(NULL, u->host, 0);
       if(!allochost)
         return CURLUE_OUT_OF_MEMORY;
     }
-    else if(punycode) {
+    else if(flags & CURLU_PUNYCODE) {
       if(!Curl_is_ASCII_name(u->host)) {
         CURLUcode ret = host_decode(u->host, &allochost);
         if(ret)
           return ret;
       }
     }
-    else if(depunyfy) {
+    else if(flags & CURLU_PUNY2IDN) {
       if(Curl_is_ASCII_name(u->host)) {
         CURLUcode ret = host_encode(u->host, &allochost);
         if(ret)
@@ -1526,9 +1517,9 @@
                         port ? ":" : "",
                         port ? port : "",
                         u->path ? u->path : "/",
-                        show_query ? "?" : "",
+                        querysep,
                         u->query ? u->query : "",
-                        show_fragment ? "#" : "",
+                        fragmentsep,
                         u->fragment ? u->fragment : "");
     curlx_free(allochost);
   }
@@ -1649,6 +1640,7 @@
     const char *s = scheme;
     if(ISALPHA(*s)) {
       /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */
+      s++;
       while(--plen) {
         if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.'))
           s++; /* fine */
@@ -1737,37 +1729,37 @@
     memset(u, 0, sizeof(struct Curl_URL));
     break;
   case CURLUPART_SCHEME:
-    Curl_safefree(u->scheme);
+    curlx_safefree(u->scheme);
     u->guessed_scheme = FALSE;
     break;
   case CURLUPART_USER:
-    Curl_safefree(u->user);
+    curlx_safefree(u->user);
     break;
   case CURLUPART_PASSWORD:
-    Curl_safefree(u->password);
+    curlx_safefree(u->password);
     break;
   case CURLUPART_OPTIONS:
-    Curl_safefree(u->options);
+    curlx_safefree(u->options);
     break;
   case CURLUPART_HOST:
-    Curl_safefree(u->host);
+    curlx_safefree(u->host);
     break;
   case CURLUPART_ZONEID:
-    Curl_safefree(u->zoneid);
+    curlx_safefree(u->zoneid);
     break;
   case CURLUPART_PORT:
     u->portnum = 0;
-    Curl_safefree(u->port);
+    curlx_safefree(u->port);
     break;
   case CURLUPART_PATH:
-    Curl_safefree(u->path);
+    curlx_safefree(u->path);
     break;
   case CURLUPART_QUERY:
-    Curl_safefree(u->query);
+    curlx_safefree(u->query);
     u->query_present = FALSE;
     break;
   case CURLUPART_FRAGMENT:
-    Curl_safefree(u->fragment);
+    curlx_safefree(u->fragment);
     u->fragment_present = FALSE;
     break;
   default:
@@ -1845,7 +1837,7 @@
     break;
   case CURLUPART_HOST:
     storep = &u->host;
-    Curl_safefree(u->zoneid);
+    curlx_safefree(u->zoneid);
     break;
   case CURLUPART_ZONEID:
     storep = &u->zoneid;
@@ -1996,3 +1988,36 @@
   }
   return CURLUE_OK;
 }
+
+bool Curl_url_same_origin(CURLU *base, CURLU *href)
+{
+  const struct Curl_scheme *s = NULL;
+
+  /* base must be an absolute URL */
+  if(!base->scheme || !base->host)
+    return FALSE;
+  if(href->scheme && !curl_strequal(base->scheme, href->scheme))
+    return FALSE;
+  if(href->host) {
+    if(!curl_strequal(base->host, href->host))
+      return FALSE;
+    if(!curl_strequal(base->port, href->port)) {
+      /* This may still match if only one has an explicit port
+       * and it is the default for the scheme. */
+      if(base->port && href->port)
+        return FALSE;
+
+      s = Curl_get_scheme(base->scheme);
+      if(!s) /* Cannot match default port for unknown scheme */
+        return FALSE;
+
+      /* The port which is set must be the default one */
+      if((base->port && (base->portnum != s->defport)) ||
+         (href->port && (href->portnum != s->defport)))
+        return FALSE;
+    }
+  }
+  else if(href->port) /* no host in href, then there must be no port */
+    return FALSE;
+  return TRUE;
+}
diff --git a/Utilities/cmcurl/lib/urldata.h b/Utilities/cmcurl/lib/urldata.h
index 5ae1480..f35bfee 100644
--- a/Utilities/cmcurl/lib/urldata.h
+++ b/Utilities/cmcurl/lib/urldata.h
@@ -26,70 +26,7 @@
 /* This file is for lib internal stuff */
 #include "curl_setup.h"
 
-#define PORT_FTP    21
-#define PORT_FTPS   990
-#define PORT_TELNET 23
-#define PORT_HTTP   80
-#define PORT_HTTPS  443
-#define PORT_DICT   2628
-#define PORT_LDAP   389
-#define PORT_LDAPS  636
-#define PORT_TFTP   69
-#define PORT_SSH    22
-#define PORT_IMAP   143
-#define PORT_IMAPS  993
-#define PORT_POP3   110
-#define PORT_POP3S  995
-#define PORT_SMB    445
-#define PORT_SMBS   445
-#define PORT_SMTP   25
-#define PORT_SMTPS  465 /* sometimes called SSMTP */
-#define PORT_RTSP   554
-#define PORT_RTMP   1935
-#define PORT_RTMPT  PORT_HTTP
-#define PORT_RTMPS  PORT_HTTPS
-#define PORT_GOPHER 70
-#define PORT_MQTT   1883
-#define PORT_MQTTS  8883
-
-#ifdef USE_ECH
-/* CURLECH_ bits for the tls_ech option */
-#define CURLECH_DISABLE    (1 << 0)
-#define CURLECH_GREASE     (1 << 1)
-#define CURLECH_ENABLE     (1 << 2)
-#define CURLECH_HARD       (1 << 3)
-#define CURLECH_CLA_CFG    (1 << 4)
-#endif
-
-#ifndef CURL_DISABLE_WEBSOCKETS
-/* CURLPROTO_GOPHERS (29) is the highest publicly used protocol bit number,
- * the rest are internal information. If we use higher bits we only do this on
- * platforms that have a >= 64-bit type and then we use such a type for the
- * protocol fields in the protocol handler.
- */
-#define CURLPROTO_WS     (1L << 30)
-#define CURLPROTO_WSS    ((curl_prot_t)1 << 31)
-#else
-#define CURLPROTO_WS     0L
-#define CURLPROTO_WSS    0L
-#endif
-
-#define CURLPROTO_MQTTS  (1LL << 32)
-
-#define CURLPROTO_64ALL ((uint64_t)0xffffffffffffffff)
-
-/* the default protocols accepting a redirect to */
-#define CURLPROTO_REDIR (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | \
-                         CURLPROTO_FTPS)
-
-typedef curl_off_t curl_prot_t;
-
-/* This mask is for all the old protocols that are provided and defined in the
-   public header and shall exclude protocols added since which are not exposed
-   in the API */
-#define CURLPROTO_MASK   0x3ffffff
-
-#define CURL_DEFAULT_USER "anonymous"
+#define CURL_DEFAULT_USER     "anonymous"
 #define CURL_DEFAULT_PASSWORD "ftp@example.com"
 
 #if !defined(_WIN32) && !defined(MSDOS)
@@ -98,31 +35,11 @@
 #define CURL_PREFER_LF_LINEENDS
 #endif
 
-/* Convenience defines for checking protocols or their SSL based version. Each
-   protocol handler should only ever have a single CURLPROTO_ in its protocol
-   field. */
-#define PROTO_FAMILY_HTTP (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_WS | \
-                           CURLPROTO_WSS)
-#define PROTO_FAMILY_FTP  (CURLPROTO_FTP | CURLPROTO_FTPS)
-#define PROTO_FAMILY_POP3 (CURLPROTO_POP3 | CURLPROTO_POP3S)
-#define PROTO_FAMILY_SMB  (CURLPROTO_SMB | CURLPROTO_SMBS)
-#define PROTO_FAMILY_SMTP (CURLPROTO_SMTP | CURLPROTO_SMTPS)
-#define PROTO_FAMILY_SSH  (CURLPROTO_SCP | CURLPROTO_SFTP)
-
-#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH) || \
-  !defined(CURL_DISABLE_POP3)
-/* these protocols support CURLOPT_DIRLISTONLY */
-#define CURL_LIST_ONLY_PROTOCOL 1
-#endif
-
 #define DEFAULT_CONNCACHE_SIZE 5
 
 /* length of longest IPv6 address string including the trailing null */
 #define MAX_IPADR_LEN sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")
 
-/* Default FTP/IMAP etc response timeout in milliseconds */
-#define RESP_TIMEOUT (60 * 1000)
-
 /* Max string input length is a precaution against abuse and to detect junk
    input easier and better. */
 #define CURL_MAX_INPUT_LENGTH 8000000
@@ -167,6 +84,7 @@
                              size_t *pnread);          /* how much received */
 
 #include "mime.h"
+#include "protocol.h"
 #include "ftp.h"
 #include "http.h"
 #include "smb.h"
@@ -322,16 +240,11 @@
   GSS_AUTHSUCC
 } curlnegotiate;
 
-#ifdef CURL_DISABLE_PROXY
-#define CONN_IS_PROXIED(x) 0
-#else
-#define CONN_IS_PROXIED(x) (x)->bits.proxy
-#endif
-
 /*
  * Boolean values that concerns this connection.
  */
 struct ConnectBits {
+  BIT(connect_only);
 #ifndef CURL_DISABLE_PROXY
   BIT(httpproxy);  /* if set, this transfer is done through an HTTP proxy */
   BIT(socksproxy); /* if set, this transfer is done through a socks proxy */
@@ -350,8 +263,6 @@
                         that overrides the host in the URL */
   BIT(conn_to_port); /* if set, this connection has a "connect to port"
                         that overrides the port in the URL (remote port) */
-  BIT(ipv6_ip); /* we communicate with a remote site specified with pure IPv6
-                   IP address */
   BIT(ipv6);    /* we communicate with a site using an IPv6 address */
   BIT(do_more); /* this is set TRUE if the ->curl_do_more() function is
                    supposed to be called, after ->curl_do() */
@@ -378,9 +289,6 @@
   BIT(multiplex); /* connection is multiplexed */
   BIT(tcp_fastopen); /* use TCP Fast Open */
   BIT(tls_enable_alpn); /* TLS ALPN extension? */
-#ifndef CURL_DISABLE_DOH
-  BIT(doh);
-#endif
 #ifdef USE_UNIX_SOCKETS
   BIT(abstract_unix_socket);
 #endif
@@ -393,6 +301,7 @@
   BIT(shutdown_handler); /* connection shutdown: handler shut down */
   BIT(shutdown_filters); /* connection shutdown: filters shut down */
   BIT(in_cpool);     /* connection is kept in a connection pool */
+  BIT(dns_resolved); /* DNS records for connection were resolved */
 };
 
 struct hostname {
@@ -402,165 +311,9 @@
   const char *dispname; /* name to display, as 'name' might be encoded */
 };
 
-/*
- * Flags on the keepon member of the Curl_transfer_keeper
- */
-
-#define KEEP_NONE       0
-#define KEEP_RECV       (1 << 0) /* there is or may be data to read */
-#define KEEP_SEND       (1 << 1) /* there is or may be data to write */
-
-/* transfer wants to send */
-#define CURL_WANT_SEND(data) ((data)->req.keepon & KEEP_SEND)
-/* transfer wants to receive */
-#define CURL_WANT_RECV(data) ((data)->req.keepon & KEEP_RECV)
-
 #define FIRSTSOCKET     0
 #define SECONDARYSOCKET 1
 
-/*
- * Specific protocol handler.
- */
-
-struct Curl_protocol {
-  /* Complement to setup_connection_internals(). This is done before the
-     transfer "owns" the connection. */
-  CURLcode (*setup_connection)(struct Curl_easy *data,
-                               struct connectdata *conn);
-
-  /* These two functions MUST be set to be protocol dependent */
-  CURLcode (*do_it)(struct Curl_easy *data, bool *done);
-  CURLcode (*done)(struct Curl_easy *, CURLcode, bool);
-
-  /* If the curl_do() function is better made in two halves, this
-   * curl_do_more() function will be called afterwards, if set. For example
-   * for doing the FTP stuff after the PASV/PORT command.
-   */
-  CURLcode (*do_more)(struct Curl_easy *, int *);
-
-  /* This function *MAY* be set to a protocol-dependent function that is run
-   * after the connect() and everything is done, as a step in the connection.
-   * The 'done' pointer points to a bool that should be set to TRUE if the
-   * function completes before return. If it does not complete, the caller
-   * should call the ->connecting() function until it is.
-   */
-  CURLcode (*connect_it)(struct Curl_easy *data, bool *done);
-
-  /* See above. */
-  CURLcode (*connecting)(struct Curl_easy *data, bool *done);
-  CURLcode (*doing)(struct Curl_easy *data, bool *done);
-
-  /* Called from the multi interface during the PROTOCONNECT phase, and it
-     should then return a proper fd set */
-  CURLcode (*proto_pollset)(struct Curl_easy *data,
-                            struct easy_pollset *ps);
-
-  /* Called from the multi interface during the DOING phase, and it should
-     then return a proper fd set */
-  CURLcode (*doing_pollset)(struct Curl_easy *data,
-                            struct easy_pollset *ps);
-
-  /* Called from the multi interface during the DO_MORE phase, and it should
-     then return a proper fd set */
-  CURLcode (*domore_pollset)(struct Curl_easy *data,
-                            struct easy_pollset *ps);
-
-  /* Called from the multi interface during the DO_DONE, PERFORM and
-     WAITPERFORM phases, and it should then return a proper fd set. Not setting
-     this will make libcurl use the generic default one. */
-  CURLcode (*perform_pollset)(struct Curl_easy *data,
-                              struct easy_pollset *ps);
-
-  /* This function *MAY* be set to a protocol-dependent function that is run
-   * by the curl_disconnect(), as a step in the disconnection. If the handler
-   * is called because the connection has been considered dead,
-   * dead_connection is set to TRUE. The connection is (again) associated with
-   * the transfer here.
-   */
-  CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *,
-                         bool dead_connection);
-
-  /* If used, this function gets called from transfer.c to
-     allow the protocol to do extra handling in writing response to
-     the client. */
-  CURLcode (*write_resp)(struct Curl_easy *data, const char *buf, size_t blen,
-                         bool is_eos);
-
-  /* If used, this function gets called from transfer.c to
-     allow the protocol to do extra handling in writing a single response
-     header line to the client. */
-  CURLcode (*write_resp_hd)(struct Curl_easy *data,
-                            const char *hd, size_t hdlen, bool is_eos);
-
-  /* This function can perform various checks on the connection. See
-     CONNCHECK_* for more information about the checks that can be performed,
-     and CONNRESULT_* for the results that can be returned. */
-  uint32_t (*connection_check)(struct Curl_easy *data,
-                               struct connectdata *conn,
-                               uint32_t checks_to_perform);
-
-  /* attach() attaches this transfer to this connection */
-  void (*attach)(struct Curl_easy *data, struct connectdata *conn);
-
-  /* return CURLE_OK if a redirect to `newurl` should be followed,
-     CURLE_TOO_MANY_REDIRECTS otherwise. May alter `data` to change
-     the way the follow request is performed. */
-  CURLcode (*follow)(struct Curl_easy *data, const char *newurl,
-                     followtype type);
-};
-
-struct Curl_scheme {
-  const char *name;       /* URL scheme name in lowercase */
-  const struct Curl_protocol *run; /* implementation */
-  curl_prot_t protocol;   /* See CURLPROTO_* - this needs to be the single
-                             specific protocol bit */
-  curl_prot_t family;     /* single bit for protocol family; the non-TLS name
-                             of the protocol this is */
-  uint32_t flags;         /* Extra particular characteristics, see PROTOPT_* */
-  uint16_t defport;       /* Default port. */
-};
-
-#define PROTOPT_NONE 0             /* nothing extra */
-#define PROTOPT_SSL (1 << 0)       /* uses SSL */
-#define PROTOPT_DUAL (1 << 1)      /* this protocol uses two connections */
-#define PROTOPT_CLOSEACTION (1 << 2) /* need action before socket close */
-/* some protocols will have to call the underlying functions without regard to
-   what exact state the socket signals. IE even if the socket says "readable",
-   the send function might need to be called while uploading, or vice versa.
-*/
-#define PROTOPT_DIRLOCK (1 << 3)
-#define PROTOPT_NONETWORK (1 << 4) /* protocol does not use the network! */
-#define PROTOPT_NEEDSPWD (1 << 5)  /* needs a password, and if none is set it
-                                      gets a default */
-#define PROTOPT_NOURLQUERY (1 << 6)  /* protocol cannot handle
-                                        URL query strings (?foo=bar) ! */
-#define PROTOPT_CREDSPERREQUEST (1 << 7) /* requires login credentials per
-                                            request instead of per
-                                            connection */
-#define PROTOPT_ALPN (1 << 8) /* set ALPN for this */
-/* (1 << 9) was PROTOPT_STREAM, now free */
-#define PROTOPT_URLOPTIONS (1 << 10) /* allow options part in the userinfo
-                                        field of the URL */
-#define PROTOPT_PROXY_AS_HTTP (1 << 11) /* allow this non-HTTP scheme over a
-                                           HTTP proxy as HTTP proxies may know
-                                           this protocol and act as
-                                           a gateway */
-#define PROTOPT_WILDCARD (1 << 12)  /* protocol supports wildcard matching */
-#define PROTOPT_USERPWDCTRL (1 << 13) /* Allow "control bytes" (< 32 ASCII) in
-                                         username and password */
-#define PROTOPT_NOTCPPROXY (1 << 14)  /* this protocol cannot proxy over TCP */
-#define PROTOPT_SSL_REUSE (1 << 15)   /* this protocol may reuse an existing
-                                         SSL connection in the same family
-                                         without having PROTOPT_SSL. */
-#define PROTOPT_CONN_REUSE (1 << 16)  /* this protocol can reuse connections */
-
-#define CONNCHECK_NONE 0                 /* No checks */
-#define CONNCHECK_ISDEAD (1 << 0)        /* Check if the connection is dead. */
-#define CONNCHECK_KEEPALIVE (1 << 1)     /* Perform any keepalive function. */
-
-#define CONNRESULT_NONE 0                /* No extra information. */
-#define CONNRESULT_DEAD (1 << 0)         /* The connection is dead. */
-
 #define TRNSPRT_NONE 0
 #define TRNSPRT_TCP  3
 #define TRNSPRT_UDP  4
@@ -630,7 +383,7 @@
   char *sasl_authzid;     /* authorization identity string, allocated */
   char *oauth_bearer; /* OAUTH2 bearer, allocated */
   struct curltime created; /* creation time */
-  struct curltime lastused; /* when returned to the connection poolas idle */
+  struct curltime lastused; /* when returned to the connection pool as idle */
 
   /* A connection can have one or two sockets and connection filters.
    * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */
@@ -695,33 +448,31 @@
      that subsequent bound-requested connections are not accidentally reusing
      wrong connections. */
   char *localdev;
-  uint16_t localportrange;
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
   int socks5_gssapi_enctype;
 #endif
-  /* The field below gets set in connect.c:connecthost() */
-  int remote_port; /* the remote port, not the proxy port! */
-  int conn_to_port; /* the remote port to connect to. valid only if
-                       bits.conn_to_port is set */
-
   uint32_t attached_xfers; /* # of attached easy handles */
 
 #ifdef USE_IPV6
   uint32_t scope_id;  /* Scope id for IPv6 */
 #endif
+  /* The field below gets set in connect.c:connecthost() */
+  uint16_t remote_port; /* the remote port, not the proxy port! */
+  uint16_t conn_to_port; /* the remote port to connect to. valid only if
+                            bits.conn_to_port is set */
+  uint16_t localportrange;
   uint16_t localport;
   uint16_t secondary_port; /* secondary socket remote port to connect to
                                     (ftp) */
-  uint8_t transport_wanted; /* one of the TRNSPRT_* defines. Not
-   necessarily the transport the connection ends using due to Alt-Svc
-   and happy eyeballing. Use `Curl_conn_get_transport() for actual value
-   once the connection is set up. */
+  uint8_t transport_wanted; /* one of the TRNSPRT_* defines. Not necessarily
+   the transport the connection ends using due to Alt-Svc and happy
+   eyeballing. Use Curl_conn_get_transport() for actual value once the
+   connection is set up. */
   uint8_t ip_version; /* copied from the Curl_easy at creation time */
   /* HTTP version last responded with by the server or negotiated via ALPN.
    * 0 at start, then one of 09, 10, 11, etc. */
   uint8_t httpversion_seen;
   uint8_t gssapi_delegation; /* inherited from set.gssapi_delegation */
-  BIT(connect_only);
 };
 
 #ifndef CURL_DISABLE_PROXY
@@ -792,6 +543,7 @@
                       force redraw at next call */
   struct pgrs_dir ul;
   struct pgrs_dir dl;
+  curl_off_t deliver; /* amount of data delivered to application */
 
   curl_off_t current_speed; /* uses the currently fastest transfer */
   curl_off_t earlydata_sent;
@@ -817,7 +569,7 @@
 
   curl_off_t speed_amount[CURL_SPEED_RECORDS];
   struct curltime speed_time[CURL_SPEED_RECORDS];
-  uint8_t speeder_c;
+  uint32_t speeder_c;
   BIT(hide);
   BIT(ul_size_known);
   BIT(dl_size_known);
@@ -899,13 +651,6 @@
   EXPIRE_LAST /* not an actual timer, used as a marker only */
 } expire_id;
 
-typedef enum {
-  TRAILERS_NONE,
-  TRAILERS_INITIALIZED,
-  TRAILERS_SENDING,
-  TRAILERS_DONE
-} trailers_state;
-
 /*
  * One instance for each timeout an easy handle can set.
  */
@@ -940,8 +685,10 @@
   curl_off_t recent_conn_id; /* The most recent connection used, might no
                               * longer exist */
   struct dynbuf headerb; /* buffer to store headers in */
+#ifndef CURL_DISABLE_HSTS
   struct curl_slist *hstslist; /* list of HSTS files set by
                                   curl_easy_setopt(HSTS) calls */
+#endif
   curl_off_t current_speed;  /* the ProgressShow() function sets this,
                                 bytes / second */
 
@@ -965,9 +712,8 @@
   struct auth authhost;  /* auth details for host */
   struct auth authproxy; /* auth details for proxy */
 
-  struct Curl_dns_entry *dns[2]; /* DNS to connect FIRST/SECONDARY */
 #ifdef USE_CURL_ASYNC
-  struct Curl_async async;  /* asynchronous name resolver data */
+  struct Curl_resolv_async *async;  /* asynchronous name resolver data */
 #endif
 
 #ifdef USE_OPENSSL
@@ -1027,8 +773,6 @@
   struct Curl_llist httphdrs; /* received headers */
   struct curl_header headerout[2]; /* for external purposes */
   struct Curl_header_store *prevhead; /* the latest added header */
-  trailers_state trailers_state; /* whether we are sending trailers
-                                    and what stage are we at */
 #endif
 #ifndef CURL_DISABLE_COOKIES
   struct curl_slist *cookielist; /* list of cookie files set by
@@ -1048,13 +792,9 @@
   struct dynamically_allocated_data {
     char *uagent;
     char *accept_encoding;
-    char *userpwd;
     char *rangeline;
     char *ref;
     char *host;
-#ifndef CURL_DISABLE_COOKIES
-    char *cookiehost;
-#endif
 #ifndef CURL_DISABLE_RTSP
     char *rtsp_transport;
 #endif
@@ -1063,7 +803,6 @@
     char *user;
     char *passwd;
 #ifndef CURL_DISABLE_PROXY
-    char *proxyuserpwd;
     char *proxyuser;
     char *proxypasswd;
 #endif
diff --git a/Utilities/cmcurl/lib/vauth/digest.c b/Utilities/cmcurl/lib/vauth/digest.c
index 9609390..5ecfd60 100644
--- a/Utilities/cmcurl/lib/vauth/digest.c
+++ b/Utilities/cmcurl/lib/vauth/digest.c
@@ -255,13 +255,13 @@
  * Parameters:
  *
  * chlgref [in]     - The challenge message.
- * nonce   [in/out] - The buffer where the nonce will be stored.
+ * nonce   [in/out] - The buffer where the nonce is stored.
  * nlen    [in]     - The length of the nonce buffer.
- * realm   [in/out] - The buffer where the realm will be stored.
+ * realm   [in/out] - The buffer where the realm is stored.
  * rlen    [in]     - The length of the realm buffer.
- * alg     [in/out] - The buffer where the algorithm will be stored.
+ * alg     [in/out] - The buffer where the algorithm is stored.
  * alen    [in]     - The length of the algorithm buffer.
- * qop     [in/out] - The buffer where the qop-options will be stored.
+ * qop     [in/out] - The buffer where the qop-options is stored.
  * qlen    [in]     - The length of the qop buffer.
  *
  * Returns CURLE_OK on success.
@@ -356,6 +356,7 @@
   char *spn         = NULL;
   char *qrealm;
   char *qnonce;
+  char *quserp;
 
   /* Decode the challenge message */
   CURLcode result = auth_decode_digest_md5_message(chlg,
@@ -384,7 +385,7 @@
   if(result)
     return result;
 
-  /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
+  /* Good so far, now calculate A1 and H(A1) according to RFC 2831 */
   ctxt = Curl_MD5_init(&Curl_DIGEST_MD5);
   if(!ctxt)
     return CURLE_OUT_OF_MEMORY;
@@ -469,20 +470,22 @@
   for(i = 0; i < MD5_DIGEST_LEN; i++)
     curl_msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
 
-  /* escape double quotes and backslashes in the realm and nonce as
+  /* escape double quotes and backslashes in the username, realm and nonce as
      necessary */
   qrealm = auth_digest_string_quoted(realm);
   qnonce = auth_digest_string_quoted(nonce);
-  if(qrealm && qnonce)
+  quserp = auth_digest_string_quoted(userp);
+  if(qrealm && qnonce && quserp)
     /* Generate the response */
     response = curl_maprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
                              "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\","
                              "response=%s,qop=%s",
-                             userp, qrealm, qnonce,
+                             quserp, qrealm, qnonce,
                              cnonce, nonceCount, spn, resp_hash_hex, qop);
 
   curlx_free(qrealm);
   curlx_free(qnonce);
+  curlx_free(quserp);
   curlx_free(spn);
   if(!response)
     return CURLE_OUT_OF_MEMORY;
@@ -669,7 +672,7 @@
  * uripath [in]     - The path of the HTTP uri.
  * digest  [in/out] - The digest data struct being used and modified.
  * outptr  [in/out] - The address where a pointer to newly allocated memory
- *                    holding the result will be stored upon completion.
+ *                    holding the result is stored upon completion.
  * outlen  [out]    - The length of the output message.
  *
  * Returns CURLE_OK on success.
@@ -853,8 +856,8 @@
      nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
 
      Digest parameters are all quoted strings. Username which is provided by
-     the user will need double quotes and backslashes within it escaped.
-     realm, nonce, and opaque will need backslashes as well as they were
+     the user needs double quotes and backslashes within it escaped.
+     realm, nonce, and opaque needs backslashes as well as they were
      de-escaped when copied from request header. cnonce is generated with
      web-safe characters. uri is already percent encoded. nc is 8 hex
      characters. algorithm and qop with standard values only contain web-safe
@@ -977,7 +980,7 @@
  * uripath [in]     - The path of the HTTP uri.
  * digest  [in/out] - The digest data struct being used and modified.
  * outptr  [in/out] - The address where a pointer to newly allocated memory
- *                    holding the result will be stored upon completion.
+ *                    holding the result is stored upon completion.
  * outlen  [out]    - The length of the output message.
  *
  * Returns CURLE_OK on success.
@@ -1028,12 +1031,12 @@
  */
 void Curl_auth_digest_cleanup(struct digestdata *digest)
 {
-  Curl_safefree(digest->nonce);
-  Curl_safefree(digest->cnonce);
-  Curl_safefree(digest->realm);
-  Curl_safefree(digest->opaque);
-  Curl_safefree(digest->qop);
-  Curl_safefree(digest->algorithm);
+  curlx_safefree(digest->nonce);
+  curlx_safefree(digest->cnonce);
+  curlx_safefree(digest->realm);
+  curlx_safefree(digest->opaque);
+  curlx_safefree(digest->qop);
+  curlx_safefree(digest->algorithm);
 
   digest->nc = 0;
   digest->algo = ALGO_MD5; /* default algorithm */
diff --git a/Utilities/cmcurl/lib/vauth/digest_sspi.c b/Utilities/cmcurl/lib/vauth/digest_sspi.c
index f29e569..f0b6780 100644
--- a/Utilities/cmcurl/lib/vauth/digest_sspi.c
+++ b/Utilities/cmcurl/lib/vauth/digest_sspi.c
@@ -375,7 +375,7 @@
  * uripath [in]     - The path of the HTTP uri.
  * digest  [in/out] - The digest data struct being used and modified.
  * outptr  [in/out] - The address where a pointer to newly allocated memory
- *                    holding the result will be stored upon completion.
+ *                    holding the result is stored upon completion.
  * outlen  [out]    - The length of the output message.
  *
  * Returns CURLE_OK on success.
@@ -427,10 +427,10 @@
      (passwdp && digest->passwd && Curl_timestrcmp(passwdp, digest->passwd))) {
     if(digest->http_context) {
       Curl_pSecFn->DeleteSecurityContext(digest->http_context);
-      Curl_safefree(digest->http_context);
+      curlx_safefree(digest->http_context);
     }
-    Curl_safefree(digest->user);
-    Curl_safefree(digest->passwd);
+    curlx_safefree(digest->user);
+    curlx_safefree(digest->passwd);
   }
 
   if(digest->http_context) {
@@ -460,7 +460,7 @@
     else { /* delete the context so a new one can be made */
       infof(data, "digest_sspi: MakeSignature failed, error 0x%08lx", status);
       Curl_pSecFn->DeleteSecurityContext(digest->http_context);
-      Curl_safefree(digest->http_context);
+      curlx_safefree(digest->http_context);
     }
   }
 
@@ -474,8 +474,8 @@
     TCHAR *spn;
 
     /* free the copy of user/passwd used to make the previous identity */
-    Curl_safefree(digest->user);
-    Curl_safefree(digest->passwd);
+    curlx_safefree(digest->user);
+    curlx_safefree(digest->passwd);
 
     if(userp && *userp) {
       /* Populate our identity structure */
@@ -515,7 +515,7 @@
       if(!digest->passwd) {
         curlx_free(output_token);
         Curl_sspi_free_identity(p_identity);
-        Curl_safefree(digest->user);
+        curlx_safefree(digest->user);
         return CURLE_OUT_OF_MEMORY;
       }
     }
@@ -595,7 +595,7 @@
       Curl_sspi_free_identity(p_identity);
       curlx_free(output_token);
 
-      Curl_safefree(digest->http_context);
+      curlx_safefree(digest->http_context);
 
       if(status == SEC_E_INSUFFICIENT_MEMORY)
         return CURLE_OUT_OF_MEMORY;
@@ -637,7 +637,7 @@
 void Curl_auth_digest_cleanup(struct digestdata *digest)
 {
   /* Free the input token */
-  Curl_safefree(digest->input_token);
+  curlx_safefree(digest->input_token);
 
   /* Reset any variables */
   digest->input_token_len = 0;
@@ -645,12 +645,12 @@
   /* Delete security context */
   if(digest->http_context) {
     Curl_pSecFn->DeleteSecurityContext(digest->http_context);
-    Curl_safefree(digest->http_context);
+    curlx_safefree(digest->http_context);
   }
 
   /* Free the copy of user/passwd used to make the identity for http_context */
-  Curl_safefree(digest->user);
-  Curl_safefree(digest->passwd);
+  curlx_safefree(digest->user);
+  curlx_safefree(digest->passwd);
 }
 
 #endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_DIGEST_AUTH */
diff --git a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c
index d6af18f..64c735b 100644
--- a/Utilities/cmcurl/lib/vauth/krb5_gssapi.c
+++ b/Utilities/cmcurl/lib/vauth/krb5_gssapi.c
@@ -33,7 +33,7 @@
 #include "curl_gssapi.h"
 #include "curl_trc.h"
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 #endif
@@ -320,7 +320,7 @@
   }
 }
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic pop
 #endif
 
diff --git a/Utilities/cmcurl/lib/vauth/krb5_sspi.c b/Utilities/cmcurl/lib/vauth/krb5_sspi.c
index c08b6a4..e7491be 100644
--- a/Utilities/cmcurl/lib/vauth/krb5_sspi.c
+++ b/Utilities/cmcurl/lib/vauth/krb5_sspi.c
@@ -444,8 +444,8 @@
   krb5->p_identity = NULL;
 
   /* Free the SPN and output token */
-  Curl_safefree(krb5->spn);
-  Curl_safefree(krb5->output_token);
+  curlx_safefree(krb5->spn);
+  curlx_safefree(krb5->output_token);
 
   /* Reset any variables */
   krb5->token_max = 0;
diff --git a/Utilities/cmcurl/lib/vauth/ntlm.c b/Utilities/cmcurl/lib/vauth/ntlm.c
index b50cfdd..1e485ed 100644
--- a/Utilities/cmcurl/lib/vauth/ntlm.c
+++ b/Utilities/cmcurl/lib/vauth/ntlm.c
@@ -152,7 +152,7 @@
 /* Indicates that 128-bit encryption is supported. */
 
 #define NTLMFLAG_NEGOTIATE_KEY_EXCHANGE          (1 << 30)
-/* Indicates that the client will provide an encrypted master key in
+/* Indicates that the client provides an encrypted master key in
    the "Session Key" field of the Type 3 message. */
 
 #define NTLMFLAG_NEGOTIATE_56                    (1 << 31)
@@ -424,7 +424,7 @@
                                              const char *userp,
                                              const char *passwdp,
                                              const char *service,
-                                             const char *hostname,
+                                             const char *host,
                                              struct ntlmdata *ntlm,
                                              struct bufref *out)
 {
@@ -445,7 +445,7 @@
   size_t size;
 
   char *ntlmbuf;
-  const char *host = "";              /* empty */
+  const char *hostname = "";          /* empty */
   const char *domain = "";            /* empty */
   size_t hostlen = 0;
   size_t domlen = 0;
@@ -456,7 +456,7 @@
   (void)userp;
   (void)passwdp;
   (void)service;
-  (void)hostname;
+  (void)host;
 
   /* Clean up any former leftovers and initialise to defaults */
   Curl_auth_cleanup_ntlm(ntlm);
@@ -490,7 +490,7 @@
                           SHORTPAIR(hostlen),
                           SHORTPAIR(hostoff),
                           0, 0,
-                          host,  /* this is empty */
+                          hostname, /* this is empty */
                           domain /* this is empty */);
 
   if(!ntlmbuf)
@@ -850,7 +850,7 @@
 void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm)
 {
   /* Free the target info */
-  Curl_safefree(ntlm->target_info);
+  curlx_safefree(ntlm->target_info);
 
   /* Reset any variables */
   ntlm->target_info_len = 0;
diff --git a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c
index 12cf32b..4c41eb2 100644
--- a/Utilities/cmcurl/lib/vauth/ntlm_sspi.c
+++ b/Utilities/cmcurl/lib/vauth/ntlm_sspi.c
@@ -191,21 +191,21 @@
  * Returns CURLE_OK on success.
  */
 CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data,
-                                             const struct bufref *type2,
+                                             const struct bufref *type2ref,
                                              struct ntlmdata *ntlm)
 {
   /* Ensure we have a valid type-2 message */
-  if(!Curl_bufref_len(type2)) {
+  if(!Curl_bufref_len(type2ref)) {
     infof(data, "NTLM handshake failure (empty type-2 message)");
     return CURLE_BAD_CONTENT_ENCODING;
   }
 
   /* Store the challenge for later use */
-  ntlm->input_token = curlx_memdup0(Curl_bufref_ptr(type2),
-                                    Curl_bufref_len(type2));
+  ntlm->input_token = curlx_memdup0(Curl_bufref_ptr(type2ref),
+                                    Curl_bufref_len(type2ref));
   if(!ntlm->input_token)
     return CURLE_OUT_OF_MEMORY;
-  ntlm->input_token_len = Curl_bufref_len(type2);
+  ntlm->input_token_len = Curl_bufref_len(type2ref);
 
   return CURLE_OK;
 }
@@ -255,7 +255,7 @@
   /* ssl context comes from schannel.
    * When extended protection is used in IIS server,
    * we have to pass a second SecBuffer to the SecBufferDesc
-   * otherwise IIS will not pass the authentication (401 response).
+   * otherwise IIS does not pass the authentication (401 response).
    * Minimum supported version is Windows 7.
    * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811
    */
@@ -341,13 +341,13 @@
   ntlm->p_identity = NULL;
 
   /* Free the input and output tokens */
-  Curl_safefree(ntlm->input_token);
-  Curl_safefree(ntlm->output_token);
+  curlx_safefree(ntlm->input_token);
+  curlx_safefree(ntlm->output_token);
 
   /* Reset any variables */
   ntlm->token_max = 0;
 
-  Curl_safefree(ntlm->spn);
+  curlx_safefree(ntlm->spn);
 }
 
 #endif /* USE_WINDOWS_SSPI && USE_NTLM */
diff --git a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c
index af8498b..38bb4c1 100644
--- a/Utilities/cmcurl/lib/vauth/spnego_gssapi.c
+++ b/Utilities/cmcurl/lib/vauth/spnego_gssapi.c
@@ -32,7 +32,7 @@
 #include "curl_gssapi.h"
 #include "curl_trc.h"
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 #endif
@@ -171,7 +171,7 @@
                                            NULL);
 
   /* Free the decoded challenge as it is not required anymore */
-  Curl_safefree(input_token.value);
+  curlx_safefree(input_token.value);
 
   nego->status = major_status;
   if(GSS_ERROR(major_status)) {
@@ -211,7 +211,7 @@
  * data        [in]     - The session handle.
  * nego        [in/out] - The Negotiate data struct being used and modified.
  * outptr      [in/out] - The address where a pointer to newly allocated memory
- *                        holding the result will be stored upon completion.
+ *                        holding the result is stored upon completion.
  * outlen      [out]    - The length of the output message.
  *
  * Returns CURLE_OK on success.
@@ -288,7 +288,7 @@
   nego->havemultiplerequests = FALSE;
 }
 
-#if defined(__GNUC__) && defined(__APPLE__)
+#if defined(CURL_HAVE_DIAG) && defined(__APPLE__)
 #pragma GCC diagnostic pop
 #endif
 
diff --git a/Utilities/cmcurl/lib/vauth/spnego_sspi.c b/Utilities/cmcurl/lib/vauth/spnego_sspi.c
index 1f73123..1baf593 100644
--- a/Utilities/cmcurl/lib/vauth/spnego_sspi.c
+++ b/Utilities/cmcurl/lib/vauth/spnego_sspi.c
@@ -192,7 +192,7 @@
     /* ssl context comes from Schannel.
      * When extended protection is used in IIS server,
      * we have to pass a second SecBuffer to the SecBufferDesc
-     * otherwise IIS will not pass the authentication (401 response).
+     * otherwise IIS does not pass the authentication (401 response).
      * Minimum supported version is Windows 7.
      * https://learn.microsoft.com/security-updates/SecurityAdvisories/2009/973811
      */
@@ -278,7 +278,7 @@
  * data        [in]     - The session handle.
  * nego        [in/out] - The Negotiate data struct being used and modified.
  * outptr      [in/out] - The address where a pointer to newly allocated memory
- *                        holding the result will be stored upon completion.
+ *                        holding the result is stored upon completion.
  * outlen      [out]    - The length of the output message.
  *
  * Returns CURLE_OK on success.
@@ -329,8 +329,8 @@
   nego->p_identity = NULL;
 
   /* Free the SPN and output token */
-  Curl_safefree(nego->spn);
-  Curl_safefree(nego->output_token);
+  curlx_safefree(nego->spn);
+  curlx_safefree(nego->output_token);
 
   /* Reset any variables */
   nego->status = 0;
diff --git a/Utilities/cmcurl/lib/vauth/vauth.h b/Utilities/cmcurl/lib/vauth/vauth.h
index 3e66c89..279da60 100644
--- a/Utilities/cmcurl/lib/vauth/vauth.h
+++ b/Utilities/cmcurl/lib/vauth/vauth.h
@@ -117,6 +117,7 @@
 /* This is used to clean up the digest specific data */
 void Curl_auth_digest_cleanup(struct digestdata *digest);
 #else
+#define Curl_auth_digest_cleanup(x)
 #define Curl_auth_is_digest_supported()       FALSE
 #endif /* !CURL_DISABLE_DIGEST_AUTH */
 
@@ -199,7 +200,7 @@
                                              const char *userp,
                                              const char *passwdp,
                                              const char *service,
-                                             const char *hostname,
+                                             const char *host,
                                              struct ntlmdata *ntlm,
                                              struct bufref *out);
 
@@ -263,7 +264,7 @@
                                               const char *passwdp,
                                               const char *service,
                                               const char *host,
-                                              const bool mutual,
+                                              const bool mutual_auth,
                                               const struct bufref *chlg,
                                               struct kerberos5data *krb5,
                                               struct bufref *out);
diff --git a/Utilities/cmcurl/lib/version.c b/Utilities/cmcurl/lib/version.c
index ff9aed0..a3e69ac 100644
--- a/Utilities/cmcurl/lib/version.c
+++ b/Utilities/cmcurl/lib/version.c
@@ -46,23 +46,18 @@
 #include <libpsl.h>
 #endif
 
-#ifdef USE_LIBRTMP
-#include <librtmp/rtmp.h>
-#include "curl_rtmp.h"
-#endif
-
 #ifdef HAVE_LIBZ
 #include <cm3p/zlib.h>
 #endif
 
 #ifdef HAVE_BROTLI
-#if defined(__GNUC__) || defined(__clang__)
+#ifdef CURL_HAVE_DIAG
 /* Ignore -Wvla warnings in brotli headers */
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wvla"
 #endif
 #include <brotli/decode.h>
-#if defined(__GNUC__) || defined(__clang__)
+#ifdef CURL_HAVE_DIAG
 #pragma GCC diagnostic pop
 #endif
 #endif
@@ -178,9 +173,6 @@
 #if !defined(CURL_DISABLE_HTTP) && defined(USE_HTTP3)
   char h3_version[30];
 #endif
-#ifdef USE_LIBRTMP
-  char rtmp_version[30];
-#endif
 #ifdef USE_GSASL
   char gsasl_buf[30];
 #endif
@@ -244,10 +236,6 @@
   Curl_quic_ver(h3_version, sizeof(h3_version));
   src[i++] = h3_version;
 #endif
-#ifdef USE_LIBRTMP
-  Curl_rtmp_version(rtmp_version, sizeof(rtmp_version));
-  src[i++] = rtmp_version;
-#endif
 #ifdef USE_GSASL
   curl_msnprintf(gsasl_buf, sizeof(gsasl_buf), "libgsasl/%s",
                  gsasl_check_version(NULL));
@@ -255,11 +243,9 @@
 #endif
 #ifdef HAVE_GSSAPI
 #ifdef HAVE_GSSGNU
-  curl_msnprintf(gss_buf, sizeof(gss_buf), "libgss/%s",
-                 GSS_VERSION);
+  curl_msnprintf(gss_buf, sizeof(gss_buf), "libgss/%s", GSS_VERSION);
 #elif defined(CURL_KRB5_VERSION)
-  curl_msnprintf(gss_buf, sizeof(gss_buf), "mit-krb5/%s",
-                 CURL_KRB5_VERSION);
+  curl_msnprintf(gss_buf, sizeof(gss_buf), "mit-krb5/%s", CURL_KRB5_VERSION);
 #else
   curl_msnprintf(gss_buf, sizeof(gss_buf), "mit-krb5");
 #endif
@@ -350,14 +336,6 @@
 #if defined(USE_SSL) && !defined(CURL_DISABLE_POP3)
   "pop3s",
 #endif
-#ifdef USE_LIBRTMP
-  "rtmp",
-  "rtmpe",
-  "rtmps",
-  "rtmpt",
-  "rtmpte",
-  "rtmpts",
-#endif
 #ifndef CURL_DISABLE_RTSP
   "rtsp",
 #endif
@@ -365,7 +343,7 @@
   "scp",
   "sftp",
 #endif
-#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE)
+#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE)
   "smb",
 #  ifdef USE_SSL
   "smbs",
@@ -451,8 +429,8 @@
 #ifndef CURL_DISABLE_ALTSVC
   FEATURE("alt-svc",     NULL,                CURL_VERSION_ALTSVC),
 #endif
-#if defined(USE_ARES) && defined(CURLRES_THREADED) && defined(USE_HTTPSRR)
-  FEATURE("asyn-rr", NULL,             0),
+#if defined(USE_ARES) && defined(USE_RESOLV_THREADED) && defined(USE_HTTPSRR)
+  FEATURE("asyn-rr",     NULL,                0),
 #endif
 #ifdef CURLRES_ASYNCH
   FEATURE("AsynchDNS",   NULL,                CURL_VERSION_ASYNCHDNS),
@@ -694,13 +672,5 @@
   feature_names[n] = NULL;  /* Terminate array. */
   version_info.features = features;
 
-#ifdef USE_LIBRTMP
-  {
-    static char rtmp_version[30];
-    Curl_rtmp_version(rtmp_version, sizeof(rtmp_version));
-    version_info.rtmp_version = rtmp_version;
-  }
-#endif
-
   return &version_info;
 }
diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c
index 48905d3..8cf3886 100644
--- a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c
+++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c
@@ -52,6 +52,7 @@
 #include "rand.h"
 #include "multiif.h"
 #include "cfilters.h"
+#include "cf-dns.h"
 #include "cf-socket.h"
 #include "connect.h"
 #include "progress.h"
@@ -64,7 +65,6 @@
 #include "vquic/vquic.h"
 #include "vquic/vquic_int.h"
 #include "vquic/vquic-tls.h"
-#include "vtls/keylog.h"
 #include "vtls/vtls.h"
 #include "vtls/vtls_scache.h"
 #include "vquic/curl_ngtcp2.h"
@@ -183,7 +183,7 @@
   struct cf_ngtcp2_ctx *ctx = cf->ctx;
   const ngtcp2_transport_params *rp;
   /* Peer should have sent us its transport parameters. If it
-   * announces a positive `max_idle_timeout` it will close the
+   * announces a positive `max_idle_timeout` it closes the
    * connection when it does not hear from us for that time.
    *
    * Some servers use this as a keep-alive timer at a rather low
@@ -789,6 +789,7 @@
   }
 }
 
+/* for ngtcp2 <v1.22.0 */
 static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
                                     uint8_t *token, size_t cidlen,
                                     void *user_data)
@@ -809,6 +810,27 @@
   return 0;
 }
 
+#ifdef NGTCP2_CALLBACKS_V3  /* ngtcp2 v1.22.0+ */
+static int cb_get_new_connection_id2(ngtcp2_conn *tconn, ngtcp2_cid *cid,
+  struct ngtcp2_stateless_reset_token *token, size_t cidlen, void *user_data)
+{
+  CURLcode result;
+  (void)tconn;
+  (void)user_data;
+
+  result = Curl_rand(NULL, cid->data, cidlen);
+  if(result)
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  cid->datalen = cidlen;
+
+  result = Curl_rand(NULL, token->data, sizeof(token->data));
+  if(result)
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+
+  return 0;
+}
+#endif
+
 static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_encryption_level level,
                           void *user_data)
 {
@@ -852,7 +874,7 @@
   cb_extend_max_local_streams_bidi,
   NULL, /* extend_max_local_streams_uni */
   cb_rand,
-  cb_get_new_connection_id,
+  cb_get_new_connection_id, /* for ngtcp2 <v1.22.0 */
   NULL, /* remove_connection_id */
   ngtcp2_crypto_update_key_cb, /* update_key */
   NULL, /* path_validation */
@@ -878,6 +900,12 @@
 #ifdef NGTCP2_CALLBACKS_V2  /* ngtcp2 v1.14.0+ */
   NULL, /* begin_path_validation */
 #endif
+#ifdef NGTCP2_CALLBACKS_V3  /* ngtcp2 v1.22.0+ */
+  NULL, /* recv_stateless_reset2 */
+  cb_get_new_connection_id2, /* get_new_connection_id2 */
+  NULL, /* dcid_status2 */
+  ngtcp2_crypto_get_path_challenge_data2_cb, /* get_path_challenge_data2 */
+#endif
 };
 
 #if defined(_MSC_VER) && defined(_DLL)
@@ -1113,7 +1141,7 @@
   if(stream->rx_offset_max < stream->rx_offset)
     stream->rx_offset_max = stream->rx_offset;
 
-  CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, rx win=%" PRId64,
+  CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, rx win=%" PRIu64,
               stream->id, blen, stream->rx_offset_max - stream->rx_offset);
   cf_ngtcp2_upd_rx_win(cf, data, stream);
   return 0;
@@ -1461,8 +1489,8 @@
   result = CURLE_AGAIN;
 
 out:
-  result = Curl_1st_err(result, cf_progress_egress(cf, data, &pktx));
-  result = Curl_1st_err(result, check_and_set_expiry(cf, data, &pktx));
+  result = Curl_1st_fatal(result, cf_progress_egress(cf, data, &pktx));
+  result = Curl_1st_fatal(result, check_and_set_expiry(cf, data, &pktx));
 denied:
   CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_recv(blen=%zu) -> %d, %zu",
               stream ? stream->id : -1, blen, result, *pnread);
@@ -1559,7 +1587,7 @@
   }
 
   CURL_TRC_CF(data, cf, "[%" PRId64 "] read req body -> "
-              "%d vecs%s with %zu (buffered=%zu, left=%" FMT_OFF_T ")",
+              "%d vecs%s with %zd (buffered=%zu, left=%" FMT_OFF_T ")",
               stream->id, (int)nvecs,
               *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "",
               nwritten, Curl_bufq_len(&stream->sendbuf),
@@ -1788,7 +1816,7 @@
   result = cf_progress_egress(cf, data, &pktx);
 
 out:
-  result = Curl_1st_err(result, check_and_set_expiry(cf, data, &pktx));
+  result = Curl_1st_fatal(result, check_and_set_expiry(cf, data, &pktx));
 denied:
   CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu) -> %d, %zu",
               stream ? stream->id : -1, len, result, *pnwritten);
@@ -2018,7 +2046,7 @@
    * This is called PMTUD (Path Maximum Transmission Unit Discovery).
    * Since a PMTUD might be rejected right on send, we do not want it
    * be followed by other packets of lesser size. Because those would
-   * also fail then. So, if we detect a PMTUD while buffering, we flush.
+   * also fail then. If we detect a PMTUD while buffering, we flush.
    */
   max_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(ctx->qconn);
   path_max_payload_size =
@@ -2043,7 +2071,7 @@
       ++pktcnt;
       if(pktcnt == 1) {
         /* first packet in buffer. This is either of a known, "good"
-         * payload size or it is a PMTUD. We will see. */
+         * payload size or it is a PMTUD. We shall see. */
         gsolen = nread;
       }
       else if(nread > gsolen ||
@@ -2216,8 +2244,8 @@
       (uint8_t *)buffer, sizeof(buffer),
       &ctx->last_error, pktx.ts);
     CURL_TRC_CF(data, cf, "start shutdown(err_type=%d, err_code=%"
-                PRIu64 ") -> %d", ctx->last_error.type,
-                ctx->last_error.error_code, (int)nwritten);
+                PRIu64 ") -> %zd", ctx->last_error.type,
+                ctx->last_error.error_code, (ssize_t)nwritten);
     /* there are cases listed in ngtcp2 documentation where this call
      * may fail. Since we are doing a connection shutdown as graceful
      * as we can, such an error is ignored here. */
@@ -2257,7 +2285,7 @@
 
   if(Curl_bufq_is_empty(&ctx->q.sendbuf)) {
     /* Sent everything off. ngtcp2 seems to have no support for graceful
-     * shutdowns. So, we are done. */
+     * shutdowns. We are done. */
     CURL_TRC_CF(data, cf, "shutdown completely sent off, done");
     *done = TRUE;
     result = CURLE_OK;
@@ -2384,7 +2412,7 @@
     DEBUGASSERT(data);
     if(!data)
       return 0;
-    CURL_TRC_CF(data, cf, "SSL message: %s %s [%d]",
+    CURL_TRC_CF(data, cf, "SSL message: %s %s [%u]",
                 incoming ? "<-" : "->", gtls_hs_msg_name(htype), htype);
     switch(htype) {
     case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: {
@@ -2567,6 +2595,18 @@
   return result;
 }
 
+static bool cf_ngtcp2_need_httpsrr(struct Curl_easy *data)
+{
+#ifdef USE_OPENSSL
+  return Curl_ossl_need_httpsrr(data);
+#elif defined(USE_WOLFSSL)
+  return Curl_wssl_need_httpsrr(data);
+#else
+  (void)data;
+  return FALSE;
+#endif
+}
+
 /*
  * Might be called twice for happy eyeballs.
  */
@@ -2681,8 +2721,14 @@
   }
 
   *done = FALSE;
-  pktx_init(&pktx, cf, data);
 
+  if(cf_ngtcp2_need_httpsrr(data) &&
+     !Curl_conn_dns_resolved_https(data, cf->sockindex)) {
+    CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect");
+    return CURLE_OK;
+  }
+
+  pktx_init(&pktx, cf, data);
   CF_DATA_SAVE(save, cf, data);
 
   if(!ctx->qconn) {
@@ -2859,7 +2905,7 @@
     goto out;
 
   /* We do not announce a max idle timeout, but when the peer does
-   * it will close the connection when it expires. */
+   * it closes the connection when it expires. */
   rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn);
   if(rp && rp->max_idle_timeout) {
     timediff_t idletime_ms =
@@ -2913,7 +2959,7 @@
 CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
                                struct Curl_easy *data,
                                struct connectdata *conn,
-                               const struct Curl_addrinfo *ai)
+                               struct Curl_sockaddr_ex *addr)
 {
   struct cf_ngtcp2_ctx *ctx = NULL;
   struct Curl_cfilter *cf = NULL;
@@ -2931,7 +2977,7 @@
     goto out;
   cf->conn = conn;
 
-  result = Curl_cf_udp_create(&cf->next, data, conn, ai, TRNSPRT_QUIC);
+  result = Curl_cf_udp_create(&cf->next, data, conn, addr, TRNSPRT_QUIC);
   if(result)
     goto out;
   cf->next->conn = cf->conn;
diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h
index 1a87faf..185272a 100644
--- a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h
+++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.h
@@ -53,7 +53,7 @@
 CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
                                struct Curl_easy *data,
                                struct connectdata *conn,
-                               const struct Curl_addrinfo *ai);
+                               struct Curl_sockaddr_ex *addr);
 #endif
 
 #endif /* HEADER_CURL_VQUIC_CURL_NGTCP2_H */
diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.c b/Utilities/cmcurl/lib/vquic/curl_quiche.c
index d8e6b30..73f664a 100644
--- a/Utilities/cmcurl/lib/vquic/curl_quiche.c
+++ b/Utilities/cmcurl/lib/vquic/curl_quiche.c
@@ -32,6 +32,7 @@
 #include "uint-hash.h"
 #include "urldata.h"
 #include "cfilters.h"
+#include "cf-dns.h"
 #include "cf-socket.h"
 #include "curl_trc.h"
 #include "rand.h"
@@ -48,7 +49,6 @@
 #include "url.h"
 #include "bufref.h"
 #include "vtls/openssl.h"
-#include "vtls/keylog.h"
 #include "vtls/vtls.h"
 
 /* HTTP/3 error values defined in RFC 9114, ch. 8.1 */
@@ -481,7 +481,7 @@
     return;
 
   /* Even when the transfer has already errored, we need to receive
-   * the data from quiche, as quiche will otherwise get stuck and
+   * the data from quiche, as quiche otherwise gets stuck and
    * raise events to receive over and over again. */
   cb_ctx.cf = cf;
   cb_ctx.data = data;
@@ -779,7 +779,7 @@
       else
         failf(data, "connection closed by server");
       /* Connection timed out, expire all transfers belonging to it
-       * as will not get any more POLL events here. */
+       * as it does not get any more POLL events here. */
       cf_quiche_expire_conn_closed(cf, data);
       return CURLE_SEND_ERROR;
     }
@@ -918,7 +918,7 @@
     result = CURLE_AGAIN;
 
 out:
-  result = Curl_1st_err(result, cf_flush_egress(cf, data));
+  result = Curl_1st_fatal(result, cf_flush_egress(cf, data));
   if(*pnread > 0)
     ctx->data_recvd += *pnread;
   CURL_TRC_CF(data, cf, "[%" PRIu64 "] cf_recv(len=%zu) -> %d, %zu, total=%"
@@ -939,8 +939,8 @@
   rv = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id,
                            (uint8_t *)CURL_UNCONST(buf), len, eos);
   if(rv == QUICHE_H3_ERR_DONE || (rv == 0 && len > 0)) {
-    /* Blocked on flow control and should HOLD sending. But when do we open
-     * again? */
+    /* Blocked on flow control and should HOLD sending.
+       When do we open again? */
     if(!quiche_conn_stream_writable(ctx->qconn, stream->id, len)) {
       CURL_TRC_CF(data, cf, "[%" PRIu64 "] send_body(len=%zu) "
                   "-> window exhausted", stream->id, len);
@@ -1051,7 +1051,7 @@
       goto out;
     }
     else {
-      CURL_TRC_CF(data, cf, "send_request(%s) -> %" PRIu64,
+      CURL_TRC_CF(data, cf, "send_request(%s) -> %" PRId64,
                   Curl_bufref_ptr(&data->state.url), rv);
     }
     result = CURLE_SEND_ERROR;
@@ -1144,7 +1144,7 @@
   }
 
 out:
-  result = Curl_1st_err(result, cf_flush_egress(cf, data));
+  result = Curl_1st_fatal(result, cf_flush_egress(cf, data));
 
   CURL_TRC_CF(data, cf, "[%" PRIu64 "] cf_send(len=%zu) -> %d, %zu",
               stream ? stream->id : (uint64_t)~0, len,
@@ -1152,8 +1152,8 @@
   return result;
 }
 
-static bool stream_is_writeable(struct Curl_cfilter *cf,
-                                struct Curl_easy *data)
+static bool stream_is_writable(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
 {
   struct cf_quiche_ctx *ctx = cf->ctx;
   struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data);
@@ -1181,7 +1181,7 @@
     c_exhaust = FALSE; /* Have not found any call in quiche that tells
                           us if the connection itself is blocked */
     s_exhaust = want_send && stream && stream->opened &&
-                (stream->quic_flow_blocked || !stream_is_writeable(cf, data));
+                (stream->quic_flow_blocked || !stream_is_writable(cf, data));
     want_recv = (want_recv || c_exhaust || s_exhaust);
     want_send = (!s_exhaust && want_send) ||
                  !Curl_bufq_is_empty(&ctx->q.sendbuf);
@@ -1379,6 +1379,12 @@
   }
 
   *done = FALSE;
+  if(Curl_ossl_need_httpsrr(data) &&
+     !Curl_conn_dns_resolved_https(data, cf->sockindex)) {
+    CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect");
+    return CURLE_OK;
+  }
+
   vquic_ctx_update_time(&ctx->q, Curl_pgrs_now(data));
 
   if(!ctx->qconn) {
@@ -1634,7 +1640,7 @@
 CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
                                struct Curl_easy *data,
                                struct connectdata *conn,
-                               const struct Curl_addrinfo *ai)
+                               struct Curl_sockaddr_ex *addr)
 {
   struct cf_quiche_ctx *ctx = NULL;
   struct Curl_cfilter *cf = NULL;
@@ -1652,7 +1658,7 @@
     goto out;
   cf->conn = conn;
 
-  result = Curl_cf_udp_create(&cf->next, data, conn, ai, TRNSPRT_QUIC);
+  result = Curl_cf_udp_create(&cf->next, data, conn, addr, TRNSPRT_QUIC);
   if(result)
     goto out;
   cf->next->conn = cf->conn;
diff --git a/Utilities/cmcurl/lib/vquic/curl_quiche.h b/Utilities/cmcurl/lib/vquic/curl_quiche.h
index fb5a563..c2c88dd 100644
--- a/Utilities/cmcurl/lib/vquic/curl_quiche.h
+++ b/Utilities/cmcurl/lib/vquic/curl_quiche.h
@@ -38,7 +38,7 @@
 CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
                                struct Curl_easy *data,
                                struct connectdata *conn,
-                               const struct Curl_addrinfo *ai);
+                               struct Curl_sockaddr_ex *addr);
 
 #endif
 
diff --git a/Utilities/cmcurl/lib/vquic/vquic-tls.c b/Utilities/cmcurl/lib/vquic/vquic-tls.c
index e135ac5..d81f2a9 100644
--- a/Utilities/cmcurl/lib/vquic/vquic-tls.c
+++ b/Utilities/cmcurl/lib/vquic/vquic-tls.c
@@ -45,7 +45,6 @@
 
 #include "urldata.h"
 #include "cfilters.h"
-#include "vtls/keylog.h"
 #include "vtls/vtls.h"
 #include "vtls/vtls_scache.h"
 #include "vquic/vquic-tls.h"
diff --git a/Utilities/cmcurl/lib/vquic/vquic-tls.h b/Utilities/cmcurl/lib/vquic/vquic-tls.h
index 33adec2..c461c15 100644
--- a/Utilities/cmcurl/lib/vquic/vquic-tls.h
+++ b/Utilities/cmcurl/lib/vquic/vquic-tls.h
@@ -72,7 +72,7 @@
  * @param ctx              the TLS context to initialize
  * @param cf               the connection filter involved
  * @param data             the transfer involved
- * @param peer             the peer that will be connected to
+ * @param peer             the peer to be connected to
  * @param alpns            the ALPN specifications to negotiate, may be NULL
  * @param cb_setup         optional callback for early TLS config
  * @param cb_user_data     user_data param for callback
diff --git a/Utilities/cmcurl/lib/vquic/vquic.c b/Utilities/cmcurl/lib/vquic/vquic.c
index 1eaabb9..0040ad6 100644
--- a/Utilities/cmcurl/lib/vquic/vquic.c
+++ b/Utilities/cmcurl/lib/vquic/vquic.c
@@ -702,20 +702,20 @@
 CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
                              struct Curl_easy *data,
                              struct connectdata *conn,
-                             const struct Curl_addrinfo *ai,
+                             struct Curl_sockaddr_ex *addr,
                              uint8_t transport)
 {
   (void)transport;
   DEBUGASSERT(transport == TRNSPRT_QUIC);
 #if defined(USE_NGTCP2) && defined(USE_NGHTTP3)
-  return Curl_cf_ngtcp2_create(pcf, data, conn, ai);
+  return Curl_cf_ngtcp2_create(pcf, data, conn, addr);
 #elif defined(USE_QUICHE)
-  return Curl_cf_quiche_create(pcf, data, conn, ai);
+  return Curl_cf_quiche_create(pcf, data, conn, addr);
 #else
   *pcf = NULL;
   (void)data;
   (void)conn;
-  (void)ai;
+  (void)addr;
   return CURLE_NOT_BUILT_IN;
 #endif
 }
@@ -725,7 +725,7 @@
                              unsigned char transport)
 {
   if(transport == TRNSPRT_UNIX) {
-    /* cannot do QUIC over a Unix domain socket */
+    failf(data, "HTTP/3 cannot be used over UNIX domain sockets");
     return CURLE_QUIC_CONNECT_ERROR;
   }
   if(!(conn->scheme->flags & PROTOPT_SSL)) {
diff --git a/Utilities/cmcurl/lib/vquic/vquic.h b/Utilities/cmcurl/lib/vquic/vquic.h
index 528e184..1f0a1ab 100644
--- a/Utilities/cmcurl/lib/vquic/vquic.h
+++ b/Utilities/cmcurl/lib/vquic/vquic.h
@@ -42,7 +42,7 @@
 CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
                              struct Curl_easy *data,
                              struct connectdata *conn,
-                             const struct Curl_addrinfo *ai,
+                             struct Curl_sockaddr_ex *addr,
                              uint8_t transport);
 
 extern struct Curl_cftype Curl_cft_http3;
diff --git a/Utilities/cmcurl/lib/vssh/libssh.c b/Utilities/cmcurl/lib/vssh/libssh.c
index 3a2a52e..44094e4 100644
--- a/Utilities/cmcurl/lib/vssh/libssh.c
+++ b/Utilities/cmcurl/lib/vssh/libssh.c
@@ -147,7 +147,7 @@
     rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, &hash, &hlen);
     if(rc != SSH_OK || hlen != 16) {
       failf(data,
-            "Denied establishing ssh session: md5 fingerprint not available");
+            "Denied establishing ssh session: MD5 fingerprint not available");
       goto cleanup;
     }
 
@@ -158,7 +158,7 @@
 
     if(!curl_strequal(md5buffer, pubkey_md5)) {
       failf(data,
-            "Denied establishing ssh session: mismatch md5 fingerprint. "
+            "Denied establishing ssh session: mismatch MD5 fingerprint. "
             "Remote %s is not equal to %s", md5buffer, pubkey_md5);
       rc = SSH_ERROR;
       goto cleanup;
@@ -357,6 +357,8 @@
                                       struct ssh_conn *sshc,
                                       struct SSHPROTO *sshp)
 {
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
   Curl_pgrsSetDownloadSize(data, -1);
   if(data->req.no_body) {
     myssh_to(data, sshc, SSH_STOP);
@@ -384,6 +386,8 @@
 {
   CURLcode result = CURLE_OK;
 
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
   curlx_dyn_reset(&sshc->readdir_buf);
   if(sshc->readdir_attrs)
     sftp_attributes_free(sshc->readdir_attrs);
@@ -480,7 +484,7 @@
     sshc->readdir_longentry = sshc->readdir_link_attrs->longname;
   }
 
-  Curl_safefree(sshc->readdir_linkPath);
+  curlx_safefree(sshc->readdir_linkPath);
 
   if(curlx_dyn_addf(&sshc->readdir_buf, " -> %s", sshc->readdir_filename)) {
     /* Not using:
@@ -519,8 +523,10 @@
   ssh_string_free_char(sshc->readdir_tmp);
   sshc->readdir_tmp = NULL;
 
-  if(result)
+  if(result) {
     myssh_to(data, sshc, SSH_STOP);
+    sshc->actualcode = result;
+  }
   else
     myssh_to(data, sshc, SSH_SFTP_READDIR);
   return SSH_NO_ERROR;
@@ -544,8 +550,8 @@
   if(cmd)
     failf(data, "%s command failed: %s", cmd,
           ssh_get_error(sshc->ssh_session));
-  Curl_safefree(sshc->quote_path1);
-  Curl_safefree(sshc->quote_path2);
+  curlx_safefree(sshc->quote_path1);
+  curlx_safefree(sshc->quote_path2);
   myssh_to(data, sshc, SSH_SFTP_CLOSE);
   sshc->nextstate = SSH_NO_STATE;
   sshc->actualcode = CURLE_QUOTE_ERROR;
@@ -564,6 +570,8 @@
   else if(statvfs) {
 #ifdef _MSC_VER
 #define CURL_LIBSSH_VFS_SIZE_MASK "I64u"
+#elif defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR <= 6)
+#define CURL_LIBSSH_VFS_SIZE_MASK "llu"
 #else
 #define CURL_LIBSSH_VFS_SIZE_MASK PRIu64
 #endif
@@ -696,8 +704,8 @@
     int dir = ssh_get_poll_flags(sshc->ssh_session);
     /* translate the libssh define bits into our own bit defines */
     sshc->waitfor =
-      ((dir & SSH_READ_PENDING) ? KEEP_RECV : 0) |
-      ((dir & SSH_WRITE_PENDING) ? KEEP_SEND : 0);
+      ((dir & SSH_READ_PENDING) ? REQ_IO_RECV : 0) |
+      ((dir & SSH_WRITE_PENDING) ? REQ_IO_SEND : 0);
   }
   else
     sshc->waitfor = 0;
@@ -955,6 +963,9 @@
   int flags;
   int rc = 0;
 
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
+
   if(data->state.resume_from) {
     sftp_attributes attrs;
 
@@ -1084,7 +1095,7 @@
   /* upload data */
   Curl_xfer_setup_send(data, FIRSTSOCKET);
 
-  /* not set by Curl_xfer_setup to preserve keepon bits */
+  /* not set by Curl_xfer_setup to preserve io_flags */
   data->conn->recv_idx = FIRSTSOCKET;
 
   /* since we do not really wait for anything at this point, we want the
@@ -1101,6 +1112,8 @@
                                        struct ssh_conn *sshc,
                                        struct SSHPROTO *sshp)
 {
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
   /* Work on getting the specified file */
   if(sshc->sftp_file)
     sftp_close(sshc->sftp_file);
@@ -1205,7 +1218,7 @@
   }
   Curl_xfer_setup_recv(data, FIRSTSOCKET, data->req.size);
 
-  /* not set by Curl_xfer_setup to preserve keepon bits */
+  /* not set by Curl_xfer_setup to preserve io_flags */
   data->conn->send_idx = 0;
 
   sshc->sftp_recv_state = 0;
@@ -1218,11 +1231,13 @@
                                struct ssh_conn *sshc,
                                struct SSHPROTO *sshp)
 {
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
   if(sshc->sftp_file) {
     sftp_close(sshc->sftp_file);
     sshc->sftp_file = NULL;
   }
-  Curl_safefree(sshp->path);
+  curlx_safefree(sshp->path);
 
   CURL_TRC_SSH(data, "SFTP DONE done");
 
@@ -1306,8 +1321,8 @@
 
   /* This is the last step in the SFTP connect phase. Do note that while
      we get the homedir here, we get the "workingpath" in the DO action
-     since the homedir will remain the same between request but the
-     working path will not. */
+     since the homedir remains the same between request but the
+     working path does not. */
   CURL_TRC_SSH(data, "CONNECT phase done");
   myssh_to(data, sshc, SSH_STOP);
   return SSH_NO_ERROR;
@@ -1317,7 +1332,12 @@
                                     struct ssh_conn *sshc,
                                     struct SSHPROTO *sshp)
 {
-  CURLcode result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
+  CURLcode result;
+
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
+
+  result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
   if(result) {
     sshc->actualcode = result;
     myssh_to(data, sshc, SSH_STOP);
@@ -1346,12 +1366,12 @@
   return SSH_NO_ERROR;
 }
 
-static int return_quote_error(struct Curl_easy *data,
-                              struct ssh_conn *sshc)
+static int quote_error(struct Curl_easy *data,
+                       struct ssh_conn *sshc)
 {
   failf(data, "Suspicious data after the command line");
-  Curl_safefree(sshc->quote_path1);
-  Curl_safefree(sshc->quote_path2);
+  curlx_safefree(sshc->quote_path1);
+  curlx_safefree(sshc->quote_path2);
   myssh_to(data, sshc, SSH_SFTP_CLOSE);
   sshc->nextstate = SSH_NO_STATE;
   sshc->actualcode = CURLE_QUOTE_ERROR;
@@ -1371,9 +1391,12 @@
   const char *cmd = sshc->quote_item->data;
   sshc->acceptfail = FALSE;
 
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
+
   /* if a command starts with an asterisk, which a legal SFTP command never
-     can, the command will be allowed to fail without it causing any
-     aborts or cancels etc. It will cause libcurl to act as if the command
+     can, the command is allowed to fail without it causing any
+     aborts or cancels etc. It causes libcurl to act as if the command
      is successful, whatever the server responds. */
 
   if(cmd[0] == '*') {
@@ -1460,14 +1483,14 @@
       else
         failf(data, "Syntax error in chgrp/chmod/chown/atime/mtime: "
               "Bad second parameter");
-      Curl_safefree(sshc->quote_path1);
+      curlx_safefree(sshc->quote_path1);
       myssh_to(data, sshc, SSH_SFTP_CLOSE);
       sshc->nextstate = SSH_NO_STATE;
       sshc->actualcode = result;
       return SSH_NO_ERROR;
     }
     if(*cp)
-      return return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     sshc->quote_attrs = NULL;
     myssh_to(data, sshc, SSH_SFTP_QUOTE_STAT);
     return SSH_NO_ERROR;
@@ -1483,20 +1506,20 @@
         failf(data, "Out of memory");
       else
         failf(data, "Syntax error in ln/symlink: Bad second parameter");
-      Curl_safefree(sshc->quote_path1);
+      curlx_safefree(sshc->quote_path1);
       myssh_to(data, sshc, SSH_SFTP_CLOSE);
       sshc->nextstate = SSH_NO_STATE;
       sshc->actualcode = result;
       return SSH_NO_ERROR;
     }
     if(*cp)
-      return return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     myssh_to(data, sshc, SSH_SFTP_QUOTE_SYMLINK);
     return SSH_NO_ERROR;
   }
   else if(!strncmp(cmd, "mkdir ", 6)) {
     if(*cp)
-      return return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     /* create directory */
     myssh_to(data, sshc, SSH_SFTP_QUOTE_MKDIR);
     return SSH_NO_ERROR;
@@ -1511,42 +1534,42 @@
         failf(data, "Out of memory");
       else
         failf(data, "Syntax error in rename: Bad second parameter");
-      Curl_safefree(sshc->quote_path1);
+      curlx_safefree(sshc->quote_path1);
       myssh_to(data, sshc, SSH_SFTP_CLOSE);
       sshc->nextstate = SSH_NO_STATE;
       sshc->actualcode = result;
       return SSH_NO_ERROR;
     }
     if(*cp)
-      return return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     myssh_to(data, sshc, SSH_SFTP_QUOTE_RENAME);
     return SSH_NO_ERROR;
   }
   else if(!strncmp(cmd, "rmdir ", 6)) {
     /* delete directory */
     if(*cp)
-      return return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     myssh_to(data, sshc, SSH_SFTP_QUOTE_RMDIR);
     return SSH_NO_ERROR;
   }
   else if(!strncmp(cmd, "rm ", 3)) {
     if(*cp)
-      return return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     myssh_to(data, sshc, SSH_SFTP_QUOTE_UNLINK);
     return SSH_NO_ERROR;
   }
 #ifdef HAS_STATVFS_SUPPORT
   else if(!strncmp(cmd, "statvfs ", 8)) {
     if(*cp)
-      return return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     myssh_to(data, sshc, SSH_SFTP_QUOTE_STATVFS);
     return SSH_NO_ERROR;
   }
 #endif
 
   failf(data, "Unknown SFTP command");
-  Curl_safefree(sshc->quote_path1);
-  Curl_safefree(sshc->quote_path2);
+  curlx_safefree(sshc->quote_path1);
+  curlx_safefree(sshc->quote_path2);
   myssh_to(data, sshc, SSH_SFTP_CLOSE);
   sshc->nextstate = SSH_NO_STATE;
   sshc->actualcode = CURLE_QUOTE_ERROR;
@@ -1556,8 +1579,8 @@
 static int myssh_in_SFTP_NEXT_QUOTE(struct Curl_easy *data,
                                     struct ssh_conn *sshc)
 {
-  Curl_safefree(sshc->quote_path1);
-  Curl_safefree(sshc->quote_path2);
+  curlx_safefree(sshc->quote_path1);
+  curlx_safefree(sshc->quote_path2);
 
   sshc->quote_item = sshc->quote_item->next;
 
@@ -1583,8 +1606,8 @@
   sshc->acceptfail = FALSE;
 
   /* if a command starts with an asterisk, which a legal SFTP command never
-     can, the command will be allowed to fail without it causing any
-     aborts or cancels etc. It will cause libcurl to act as if the command
+     can, the command is allowed to fail without it causing any
+     aborts or cancels etc. It causes libcurl to act as if the command
      is successful, whatever the server responds. */
 
   if(cmd[0] == '*') {
@@ -1659,9 +1682,6 @@
       myssh_quote_error(data, sshc, NULL);
       return SSH_NO_ERROR;
     }
-    if(date > UINT_MAX)
-      /* because the liubssh API cannot deal with a larger value */
-      date = UINT_MAX;
     if(!strncmp(cmd, "atime", 5))
       sshc->quote_attrs->atime = (uint32_t)date;
     else /* mtime */
@@ -1736,7 +1756,7 @@
   data->req.maxdownload = bytecount;
   Curl_xfer_setup_recv(data, FIRSTSOCKET, bytecount);
 
-  /* not set by Curl_xfer_setup to preserve keepon bits */
+  /* not set by Curl_xfer_setup to preserve io_flags */
   data->conn->send_idx = 0;
 
   myssh_to(data, sshc, SSH_STOP);
@@ -1830,30 +1850,348 @@
       sshc->pubkey = NULL;
     }
 
-    Curl_safefree(sshc->rsa_pub);
-    Curl_safefree(sshc->rsa);
-    Curl_safefree(sshc->quote_path1);
-    Curl_safefree(sshc->quote_path2);
+    curlx_safefree(sshc->rsa_pub);
+    curlx_safefree(sshc->rsa);
+    curlx_safefree(sshc->quote_path1);
+    curlx_safefree(sshc->quote_path2);
     curlx_dyn_free(&sshc->readdir_buf);
-    Curl_safefree(sshc->readdir_linkPath);
+    curlx_safefree(sshc->readdir_linkPath);
     SSH_STRING_FREE_CHAR(sshc->homedir);
     sshc->initialised = FALSE;
   }
 }
 
+static int myssh_in_SFTP_QUOTE_SETSTAT(struct Curl_easy *data,
+                                       struct ssh_conn *sshc)
+{
+  int rc = sftp_setstat(sshc->sftp_session, sshc->quote_path2,
+                        sshc->quote_attrs);
+  if(rc == SSH_AGAIN)
+    return rc;
+  if(rc && !sshc->acceptfail) {
+    myssh_quote_error(data, sshc, "setstat");
+    return rc;
+  }
+  myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_QUOTE_SYMLINK(struct Curl_easy *data,
+                                       struct ssh_conn *sshc)
+{
+  int rc = sftp_symlink(sshc->sftp_session, sshc->quote_path2,
+                        sshc->quote_path1);
+  if(rc == SSH_AGAIN)
+    return rc;
+  if(rc && !sshc->acceptfail) {
+    myssh_quote_error(data, sshc, "symlink");
+    return rc;
+  }
+  myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_QUOTE_MKDIR(struct Curl_easy *data,
+                                     struct ssh_conn *sshc)
+{
+  int rc = sftp_mkdir(sshc->sftp_session, sshc->quote_path1,
+                      (mode_t)data->set.new_directory_perms);
+  if(rc == SSH_AGAIN)
+    return rc;
+  if(rc && !sshc->acceptfail) {
+    myssh_quote_error(data, sshc, "mkdir");
+    return rc;
+  }
+  myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_QUOTE_RENAME(struct Curl_easy *data,
+                                      struct ssh_conn *sshc)
+{
+  int rc = sftp_rename(sshc->sftp_session, sshc->quote_path1,
+                       sshc->quote_path2);
+  if(rc == SSH_AGAIN)
+    return rc;
+  if(rc && !sshc->acceptfail) {
+    myssh_quote_error(data, sshc, "rename");
+    return rc;
+  }
+  myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_QUOTE_RMDIR(struct Curl_easy *data,
+                                     struct ssh_conn *sshc)
+{
+  int rc = sftp_rmdir(sshc->sftp_session, sshc->quote_path1);
+  if(rc == SSH_AGAIN)
+    return rc;
+  if(rc && !sshc->acceptfail) {
+    myssh_quote_error(data, sshc, "rmdir");
+    return rc;
+  }
+  myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_QUOTE_UNLINK(struct Curl_easy *data,
+                                      struct ssh_conn *sshc)
+{
+  int rc = sftp_unlink(sshc->sftp_session, sshc->quote_path1);
+  if(rc == SSH_AGAIN)
+    return rc;
+  if(rc && !sshc->acceptfail) {
+    myssh_quote_error(data, sshc, "rm");
+    return rc;
+  }
+  myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_GETINFO(struct Curl_easy *data,
+                                 struct ssh_conn *sshc)
+{
+  if(data->set.get_filetime)
+    myssh_to(data, sshc, SSH_SFTP_FILETIME);
+  else
+    myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_FILETIME(struct Curl_easy *data,
+                                  struct ssh_conn *sshc,
+                                  struct SSHPROTO *sshp)
+{
+  sftp_attributes attrs;
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
+
+  attrs = sftp_stat(sshc->sftp_session, sshp->path);
+  if(attrs) {
+    data->info.filetime = attrs->mtime;
+    sftp_attributes_free(attrs);
+  }
+
+  myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_TRANS_INIT(struct Curl_easy *data,
+                                    struct ssh_conn *sshc,
+                                    struct SSHPROTO *sshp)
+{
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
+  if(data->state.upload)
+    myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
+  else {
+    size_t path_len = strlen(sshp->path);
+
+    if(path_len && sshp->path[path_len - 1] == '/')
+      myssh_to(data, sshc, SSH_SFTP_READDIR_INIT);
+    else
+      myssh_to(data, sshc, SSH_SFTP_DOWNLOAD_INIT);
+  }
+
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_CREATE_DIRS_INIT(struct Curl_easy *data,
+                                          struct ssh_conn *sshc,
+                                          struct SSHPROTO *sshp)
+{
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
+
+  if(strlen(sshp->path) > 1) {
+    sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */
+    myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS);
+  }
+  else {
+    myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
+  }
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_CREATE_DIRS(struct Curl_easy *data,
+                                     struct ssh_conn *sshc,
+                                     struct SSHPROTO *sshp)
+{
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
+
+  sshc->slash_pos = strchr(sshc->slash_pos, '/');
+  if(sshc->slash_pos) {
+    *sshc->slash_pos = 0;
+
+    infof(data, "Creating directory '%s'", sshp->path);
+    myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR);
+    return SSH_NO_ERROR;
+  }
+  myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SFTP_CREATE_DIRS_MKDIR(struct Curl_easy *data,
+                                           struct ssh_conn *sshc,
+                                           struct SSHPROTO *sshp)
+{
+  int rc;
+  int err;
+  /* 'mode' - parameter is preliminary - default to 0644 */
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
+
+  rc = sftp_mkdir(sshc->sftp_session, sshp->path,
+                  (mode_t)data->set.new_directory_perms);
+  if(rc == SSH_AGAIN)
+    return rc;
+  *sshc->slash_pos = '/';
+  ++sshc->slash_pos;
+  if(rc < 0) {
+    /*
+     * Abort if failure was not that the directory already
+     * exists or the permission was denied (creation might
+     * succeed further down the path) - retry on unspecific
+     * FAILURE also
+     */
+    err = sftp_get_error(sshc->sftp_session);
+    if((err != SSH_FX_FILE_ALREADY_EXISTS) &&
+       (err != SSH_FX_FAILURE) &&
+       (err != SSH_FX_PERMISSION_DENIED)) {
+      return myssh_to_SFTP_CLOSE(data, sshc);
+    }
+    rc = 0; /* clear rc and continue */
+  }
+  myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS);
+  return rc;
+}
+
+static int myssh_in_SCP_UPLOAD_INIT(struct Curl_easy *data,
+                                    struct ssh_conn *sshc,
+                                    struct SSHPROTO *sshp)
+{
+  int rc;
+  if(!sshp)
+    return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
+
+  rc = ssh_scp_init(sshc->scp_session);
+  if(rc != SSH_OK) {
+    const char *err_msg = ssh_get_error(sshc->ssh_session);
+    failf(data, "%s", err_msg);
+    return myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
+  }
+
+  rc = ssh_scp_push_file64(sshc->scp_session, sshp->path,
+                           (uint64_t)data->state.infilesize,
+                           (int)data->set.new_file_perms);
+
+  if(rc != SSH_OK) {
+    const char *err_msg = ssh_get_error(sshc->ssh_session);
+    failf(data, "%s", err_msg);
+    return myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
+  }
+
+  /* upload data */
+  Curl_xfer_setup_send(data, FIRSTSOCKET);
+
+  /* not set by Curl_xfer_setup to preserve io_flags */
+  data->conn->recv_idx = FIRSTSOCKET;
+
+  myssh_to(data, sshc, SSH_STOP);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SCP_DOWNLOAD_INIT(struct Curl_easy *data,
+                                      struct ssh_conn *sshc)
+{
+  int rc = ssh_scp_init(sshc->scp_session);
+  if(rc != SSH_OK) {
+    const char *err_msg = ssh_get_error(sshc->ssh_session);
+    failf(data, "%s", err_msg);
+    return myssh_to_ERROR(data, sshc,
+                          CURLE_COULDNT_CONNECT);
+  }
+  myssh_to(data, sshc, SSH_SCP_DOWNLOAD);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SCP_DONE(struct Curl_easy *data,
+                             struct ssh_conn *sshc)
+{
+  if(data->state.upload)
+    myssh_to(data, sshc, SSH_SCP_SEND_EOF);
+  else
+    myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SCP_SEND_EOF(struct Curl_easy *data,
+                                 struct ssh_conn *sshc)
+{
+  if(sshc->scp_session) {
+    int rc = ssh_scp_close(sshc->scp_session);
+    if(rc == SSH_AGAIN) {
+      /* Currently the ssh_scp_close handles waiting for
+       * EOF in blocking way.
+       */
+      return SSH_AGAIN;
+    }
+    if(rc != SSH_OK) {
+      infof(data,
+            "Failed to close libssh scp channel: %s",
+            ssh_get_error(sshc->ssh_session));
+    }
+  }
+
+  myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
+  return SSH_NO_ERROR;
+}
+
+static int myssh_in_SCP_CHANNEL_FREE(struct Curl_easy *data,
+                                     struct ssh_conn *sshc)
+{
+  if(sshc->scp_session) {
+    ssh_scp_free(sshc->scp_session);
+    sshc->scp_session = NULL;
+  }
+  CURL_TRC_SSH(data, "SCP DONE phase complete");
+
+  ssh_set_blocking(sshc->ssh_session, 0);
+
+  myssh_to(data, sshc, SSH_SESSION_DISCONNECT);
+  return SSH_NO_ERROR;
+}
+
+static CURLcode myssh_in_SESSION_FREE(struct Curl_easy *data,
+                                      struct ssh_conn *sshc)
+{
+  CURLcode result;
+  sshc_cleanup(sshc);
+  /* the code we are about to return */
+  result = sshc->actualcode;
+  memset(sshc, 0, sizeof(struct ssh_conn));
+  connclose(data->conn, "SSH session free");
+  sshc->state = SSH_SESSION_FREE;   /* current */
+  sshc->nextstate = SSH_NO_STATE;
+  myssh_to(data, sshc, SSH_STOP);
+  return result;
+}
+
 /*
- * ssh_statemach_act() runs the SSH state machine as far as it can without
+ * myssh_statemachine() runs the SSH state machine as far as it can without
  * blocking and without reaching the end. The data the pointer 'block' points
- * to will be set to TRUE if the libssh function returns SSH_AGAIN
+ * to is set to TRUE if the libssh function returns SSH_AGAIN
  * meaning it wants to be called again when the socket is ready
  */
-static CURLcode myssh_statemach_act(struct Curl_easy *data,
-                                    struct ssh_conn *sshc,
-                                    struct SSHPROTO *sshp,
-                                    bool *block)
+static CURLcode myssh_statemachine(struct Curl_easy *data,
+                                   struct ssh_conn *sshc,
+                                   struct SSHPROTO *sshp,
+                                   bool *block)
 {
   CURLcode result = CURLE_OK;
-  int rc = SSH_NO_ERROR, err;
+  int rc = SSH_NO_ERROR;
 
   *block = FALSE;                   /* we are not blocking by default */
   do {
@@ -1909,14 +2247,13 @@
       rc = myssh_in_SFTP_REALPATH(data, sshc);
       break;
     case SSH_SFTP_QUOTE_INIT:
-      rc = sshp ? myssh_in_SFTP_QUOTE_INIT(data, sshc, sshp) :
-        CURLE_FAILED_INIT;
+      rc = myssh_in_SFTP_QUOTE_INIT(data, sshc, sshp);
       break;
     case SSH_SFTP_POSTQUOTE_INIT:
       rc = myssh_in_SFTP_POSTQUOTE_INIT(data, sshc);
       break;
     case SSH_SFTP_QUOTE:
-      rc = sshp ? myssh_in_SFTP_QUOTE(data, sshc, sshp) : CURLE_FAILED_INIT;
+      rc = myssh_in_SFTP_QUOTE(data, sshc, sshp);
       break;
     case SSH_SFTP_NEXT_QUOTE:
       rc = myssh_in_SFTP_NEXT_QUOTE(data, sshc);
@@ -1925,186 +2262,52 @@
       rc = myssh_in_SFTP_QUOTE_STAT(data, sshc);
       break;
     case SSH_SFTP_QUOTE_SETSTAT:
-      rc = sftp_setstat(sshc->sftp_session, sshc->quote_path2,
-                        sshc->quote_attrs);
-      if(rc == SSH_AGAIN)
-        break;
-      if(rc && !sshc->acceptfail) {
-        myssh_quote_error(data, sshc, "setstat");
-        break;
-      }
-      myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+      rc = myssh_in_SFTP_QUOTE_SETSTAT(data, sshc);
       break;
-
     case SSH_SFTP_QUOTE_SYMLINK:
-      rc = sftp_symlink(sshc->sftp_session, sshc->quote_path2,
-                        sshc->quote_path1);
-      if(rc == SSH_AGAIN)
-        break;
-      if(rc && !sshc->acceptfail) {
-        myssh_quote_error(data, sshc, "symlink");
-        break;
-      }
-      myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+      rc = myssh_in_SFTP_QUOTE_SYMLINK(data, sshc);
       break;
-
     case SSH_SFTP_QUOTE_MKDIR:
-      rc = sftp_mkdir(sshc->sftp_session, sshc->quote_path1,
-                      (mode_t)data->set.new_directory_perms);
-      if(rc == SSH_AGAIN)
-        break;
-      if(rc && !sshc->acceptfail) {
-        myssh_quote_error(data, sshc, "mkdir");
-        break;
-      }
-      myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+      rc = myssh_in_SFTP_QUOTE_MKDIR(data, sshc);
       break;
-
     case SSH_SFTP_QUOTE_RENAME:
-      rc = sftp_rename(sshc->sftp_session, sshc->quote_path1,
-                       sshc->quote_path2);
-      if(rc == SSH_AGAIN)
-        break;
-      if(rc && !sshc->acceptfail) {
-        myssh_quote_error(data, sshc, "rename");
-        break;
-      }
-      myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+      rc = myssh_in_SFTP_QUOTE_RENAME(data, sshc);
       break;
-
     case SSH_SFTP_QUOTE_RMDIR:
-      rc = sftp_rmdir(sshc->sftp_session, sshc->quote_path1);
-      if(rc == SSH_AGAIN)
-        break;
-      if(rc && !sshc->acceptfail) {
-        myssh_quote_error(data, sshc, "rmdir");
-        break;
-      }
-      myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+      rc = myssh_in_SFTP_QUOTE_RMDIR(data, sshc);
       break;
-
     case SSH_SFTP_QUOTE_UNLINK:
-      rc = sftp_unlink(sshc->sftp_session, sshc->quote_path1);
-      if(rc == SSH_AGAIN)
-        break;
-      if(rc && !sshc->acceptfail) {
-        myssh_quote_error(data, sshc, "rm");
-        break;
-      }
-      myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
+      rc = myssh_in_SFTP_QUOTE_UNLINK(data, sshc);
       break;
-
     case SSH_SFTP_QUOTE_STATVFS:
       rc = myssh_in_SFTP_QUOTE_STATVFS(data, sshc);
       break;
-
     case SSH_SFTP_GETINFO:
-      if(data->set.get_filetime) {
-        myssh_to(data, sshc, SSH_SFTP_FILETIME);
-      }
-      else {
-        myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
-      }
+      rc = myssh_in_SFTP_GETINFO(data, sshc);
       break;
-
-    case SSH_SFTP_FILETIME: {
-      sftp_attributes attrs;
-      if(!sshp) {
-        result = CURLE_FAILED_INIT;
-        break;
-      }
-      attrs = sftp_stat(sshc->sftp_session, sshp->path);
-      if(attrs) {
-        data->info.filetime = attrs->mtime;
-        sftp_attributes_free(attrs);
-      }
-
-      myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
+    case SSH_SFTP_FILETIME:
+      rc = myssh_in_SFTP_FILETIME(data, sshc, sshp);
       break;
-    }
-
     case SSH_SFTP_TRANS_INIT:
-      if(data->state.upload)
-        myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
-      else if(sshp) {
-        if(sshp->path[strlen(sshp->path) - 1] == '/')
-          myssh_to(data, sshc, SSH_SFTP_READDIR_INIT);
-        else
-          myssh_to(data, sshc, SSH_SFTP_DOWNLOAD_INIT);
-      }
-      else
-        result = CURLE_FAILED_INIT;
+      rc = myssh_in_SFTP_TRANS_INIT(data, sshc, sshp);
       break;
-
     case SSH_SFTP_UPLOAD_INIT:
-      rc = sshp ? myssh_in_UPLOAD_INIT(data, sshc, sshp) :
-           CURLE_FAILED_INIT;
+      rc = myssh_in_UPLOAD_INIT(data, sshc, sshp);
       break;
-
     case SSH_SFTP_CREATE_DIRS_INIT:
-      if(!sshp) {
-        result = CURLE_FAILED_INIT;
-        break;
-      }
-      else if(strlen(sshp->path) > 1) {
-        sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */
-        myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS);
-      }
-      else {
-        myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
-      }
+      rc = myssh_in_SFTP_CREATE_DIRS_INIT(data, sshc, sshp);
       break;
-
     case SSH_SFTP_CREATE_DIRS:
-      sshc->slash_pos = strchr(sshc->slash_pos, '/');
-      if(!sshp) {
-        result = CURLE_FAILED_INIT;
-        break;
-      }
-      else if(sshc->slash_pos) {
-        *sshc->slash_pos = 0;
-
-        infof(data, "Creating directory '%s'", sshp->path);
-        myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR);
-        break;
-      }
-      myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
+      rc = myssh_in_SFTP_CREATE_DIRS(data, sshc, sshp);
       break;
-
     case SSH_SFTP_CREATE_DIRS_MKDIR:
-      /* 'mode' - parameter is preliminary - default to 0644 */
-      if(!sshp) {
-        result = CURLE_FAILED_INIT;
-        break;
-      }
-      rc = sftp_mkdir(sshc->sftp_session, sshp->path,
-                      (mode_t)data->set.new_directory_perms);
-      *sshc->slash_pos = '/';
-      ++sshc->slash_pos;
-      if(rc < 0) {
-        /*
-         * Abort if failure was not that the directory already exists or
-         * the permission was denied (creation might succeed further down
-         * the path) - retry on unspecific FAILURE also
-         */
-        err = sftp_get_error(sshc->sftp_session);
-        if((err != SSH_FX_FILE_ALREADY_EXISTS) &&
-           (err != SSH_FX_FAILURE) &&
-           (err != SSH_FX_PERMISSION_DENIED)) {
-          rc = myssh_to_SFTP_CLOSE(data, sshc);
-          break;
-        }
-        rc = 0; /* clear rc and continue */
-      }
-      myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS);
+      rc = myssh_in_SFTP_CREATE_DIRS_MKDIR(data, sshc, sshp);
       break;
-
     case SSH_SFTP_READDIR_INIT:
-      rc = sshp ? myssh_in_SFTP_READDIR_INIT(data, sshc, sshp) :
-        CURLE_FAILED_INIT;
+      rc = myssh_in_SFTP_READDIR_INIT(data, sshc, sshp);
       break;
     case SSH_SFTP_READDIR:
-      rc = sshp ? myssh_in_SFTP_READDIR(data, sshc, sshp) : CURLE_FAILED_INIT;
+      rc = myssh_in_SFTP_READDIR(data, sshc, sshp);
       break;
     case SSH_SFTP_READDIR_LINK:
       rc = myssh_in_SFTP_READDIR_LINK(data, sshc);
@@ -2116,14 +2319,13 @@
       rc = myssh_in_SFTP_READDIR_DONE(data, sshc);
       break;
     case SSH_SFTP_DOWNLOAD_INIT:
-      rc = sshp ? myssh_in_SFTP_DOWNLOAD_INIT(data, sshc, sshp) :
-        CURLE_FAILED_INIT;
+      rc = myssh_in_SFTP_DOWNLOAD_INIT(data, sshc, sshp);
       break;
     case SSH_SFTP_DOWNLOAD_STAT:
       rc = myssh_in_SFTP_DOWNLOAD_STAT(data, sshc);
       break;
     case SSH_SFTP_CLOSE:
-      rc = sshp ? myssh_in_SFTP_CLOSE(data, sshc, sshp) : CURLE_FAILED_INIT;
+      rc = myssh_in_SFTP_CLOSE(data, sshc, sshp);
       break;
     case SSH_SFTP_SHUTDOWN:
       rc = myssh_in_SFTP_SHUTDOWN(data, sshc);
@@ -2131,107 +2333,32 @@
     case SSH_SCP_TRANS_INIT:
       rc = myssh_in_TRANS_INIT(data, sshc, sshp);
       break;
-
     case SSH_SCP_UPLOAD_INIT:
-      if(!sshp) {
-        result = CURLE_FAILED_INIT;
-        break;
-      }
-      rc = ssh_scp_init(sshc->scp_session);
-      if(rc != SSH_OK) {
-        const char *err_msg = ssh_get_error(sshc->ssh_session);
-        failf(data, "%s", err_msg);
-        rc = myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
-        break;
-      }
-
-      rc = ssh_scp_push_file64(sshc->scp_session, sshp->path,
-                               (uint64_t)data->state.infilesize,
-                               (int)data->set.new_file_perms);
-
-      if(rc != SSH_OK) {
-        const char *err_msg = ssh_get_error(sshc->ssh_session);
-        failf(data, "%s", err_msg);
-        rc = myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
-        break;
-      }
-
-      /* upload data */
-      Curl_xfer_setup_send(data, FIRSTSOCKET);
-
-      /* not set by Curl_xfer_setup to preserve keepon bits */
-      data->conn->recv_idx = FIRSTSOCKET;
-
-      myssh_to(data, sshc, SSH_STOP);
-
+      rc = myssh_in_SCP_UPLOAD_INIT(data, sshc, sshp);
       break;
-
     case SSH_SCP_DOWNLOAD_INIT:
-      rc = ssh_scp_init(sshc->scp_session);
-      if(rc != SSH_OK) {
-        const char *err_msg = ssh_get_error(sshc->ssh_session);
-        failf(data, "%s", err_msg);
-        rc = myssh_to_ERROR(data, sshc, CURLE_COULDNT_CONNECT);
+      rc = myssh_in_SCP_DOWNLOAD_INIT(data, sshc);
+      if(rc)
         break;
-      }
-      myssh_to(data, sshc, SSH_SCP_DOWNLOAD);
       FALLTHROUGH();
-
     case SSH_SCP_DOWNLOAD:
       rc = myssh_SSH_SCP_DOWNLOAD(data, sshc);
       break;
-
     case SSH_SCP_DONE:
-      if(data->state.upload)
-        myssh_to(data, sshc, SSH_SCP_SEND_EOF);
-      else
-        myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
+      rc = myssh_in_SCP_DONE(data, sshc);
       break;
-
     case SSH_SCP_SEND_EOF:
-      if(sshc->scp_session) {
-        rc = ssh_scp_close(sshc->scp_session);
-        if(rc == SSH_AGAIN) {
-          /* Currently the ssh_scp_close handles waiting for EOF in
-           * blocking way.
-           */
-          break;
-        }
-        if(rc != SSH_OK) {
-          infof(data, "Failed to close libssh scp channel: %s",
-                ssh_get_error(sshc->ssh_session));
-        }
-      }
-
-      myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
+      rc = myssh_in_SCP_SEND_EOF(data, sshc);
       break;
-
     case SSH_SCP_CHANNEL_FREE:
-      if(sshc->scp_session) {
-        ssh_scp_free(sshc->scp_session);
-        sshc->scp_session = NULL;
-      }
-      CURL_TRC_SSH(data, "SCP DONE phase complete");
-
-      ssh_set_blocking(sshc->ssh_session, 0);
-
-      myssh_to(data, sshc, SSH_SESSION_DISCONNECT);
+      myssh_in_SCP_CHANNEL_FREE(data, sshc);
       FALLTHROUGH();
-
     case SSH_SESSION_DISCONNECT:
       myssh_SESSION_DISCONNECT(data, sshc);
       FALLTHROUGH();
     case SSH_SESSION_FREE:
-      sshc_cleanup(sshc);
-      /* the code we are about to return */
-      result = sshc->actualcode;
-      memset(sshc, 0, sizeof(struct ssh_conn));
-      connclose(data->conn, "SSH session free");
-      sshc->state = SSH_SESSION_FREE;   /* current */
-      sshc->nextstate = SSH_NO_STATE;
-      myssh_to(data, sshc, SSH_STOP);
+      result = myssh_in_SESSION_FREE(data, sshc);
       break;
-
     case SSH_QUIT:
     default:
       /* internal error */
@@ -2268,12 +2395,12 @@
   if(!sshc || (sock == CURL_SOCKET_BAD))
     return CURLE_FAILED_INIT;
 
-  waitfor = sshc->waitfor ? sshc->waitfor : data->req.keepon;
+  waitfor = sshc->waitfor ? sshc->waitfor : data->req.io_flags;
   if(waitfor) {
     int flags = 0;
-    if(waitfor & KEEP_RECV)
+    if(waitfor & REQ_IO_RECV)
       flags |= CURL_POLL_IN;
-    if(waitfor & KEEP_SEND)
+    if(waitfor & REQ_IO_SEND)
       flags |= CURL_POLL_OUT;
     DEBUGASSERT(flags);
     CURL_TRC_SSH(data, "pollset, flags=%x", flags);
@@ -2298,7 +2425,7 @@
 
   if(!sshc || !sshp)
     return CURLE_FAILED_INIT;
-  result = myssh_statemach_act(data, sshc, sshp, &block);
+  result = myssh_statemachine(data, sshc, sshp, &block);
   *done = (sshc->state == SSH_STOP);
   myssh_block2waitfor(conn, sshc, block);
 
@@ -2317,7 +2444,7 @@
     bool block;
     timediff_t left_ms = 1000;
 
-    result = myssh_statemach_act(data, sshc, sshp, &block);
+    result = myssh_statemachine(data, sshc, sshp, &block);
     if(result)
       break;
 
@@ -2348,7 +2475,7 @@
   struct SSHPROTO *sshp = entry;
   (void)key;
   (void)klen;
-  Curl_safefree(sshp->path);
+  curlx_safefree(sshp->path);
   curlx_free(sshp);
 }
 
@@ -2422,13 +2549,9 @@
     return CURLE_FAILED_INIT;
   }
 
-  if(conn->bits.ipv6_ip) {
-    char ipv6[MAX_IPADR_LEN];
-    curl_msnprintf(ipv6, sizeof(ipv6), "[%s]", conn->host.name);
-    rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, ipv6);
-  }
-  else
-    rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, conn->host.name);
+  rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST,
+                       (data->state.up.hostname[0] == '[') ?
+                       data->state.up.hostname : conn->host.name);
 
   if(rc != SSH_OK) {
     failf(data, "Could not set remote host");
@@ -2600,7 +2723,7 @@
   if(Curl_pgrsDone(data))
     return CURLE_ABORTED_BY_CALLBACK;
 
-  data->req.keepon = 0;   /* clear all bits */
+  CURL_REQ_CLEAR_IO(data);
   return result;
 }
 
@@ -2637,7 +2760,7 @@
 
 #if 0
   /* The following code is misleading, mostly added as wishful thinking
-   * that libssh at some point will implement non-blocking ssh_scp_write/read.
+   * that libssh at some point would implement non-blocking ssh_scp_write/read.
    * Currently rc can only be number of bytes read or SSH_ERROR. */
   myssh_block2waitfor(conn, sshc, (rc == SSH_AGAIN));
 
@@ -2671,7 +2794,7 @@
     return CURLE_SSH;
 #if 0
   /* The following code is misleading, mostly added as wishful thinking
-   * that libssh at some point will implement non-blocking ssh_scp_write/read.
+   * that libssh at some point would implement non-blocking ssh_scp_write/read.
    * Currently rc can only be SSH_OK or SSH_ERROR. */
 
   myssh_block2waitfor(conn, sshc, (nread == SSH_AGAIN));
@@ -2985,7 +3108,7 @@
   scp_disconnect,               /* disconnect */
   ZERO_NULL,                    /* write_resp */
   ZERO_NULL,                    /* write_resp_hd */
-  ZERO_NULL,                    /* connection_check */
+  ZERO_NULL,                    /* connection_is_dead */
   ZERO_NULL,                    /* attach connection */
   ZERO_NULL,                    /* follow */
 };
@@ -3008,7 +3131,7 @@
   sftp_disconnect,                      /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   ZERO_NULL,                            /* follow */
 };
diff --git a/Utilities/cmcurl/lib/vssh/libssh2.c b/Utilities/cmcurl/lib/vssh/libssh2.c
index 63f5735..4e2a722 100644
--- a/Utilities/cmcurl/lib/vssh/libssh2.c
+++ b/Utilities/cmcurl/lib/vssh/libssh2.c
@@ -148,7 +148,9 @@
 #endif /* CURL_LIBSSH2_DEBUG */
   if(num_prompts == 1) {
     struct connectdata *conn = data->conn;
-    responses[0].text = curlx_strdup(conn->passwd);
+    /* this function must allocate memory that can be freed by libssh2, which
+       uses the LIBSSH2_FREE_FUNC callback */
+    responses[0].text = Curl_cstrdup(conn->passwd);
     responses[0].length =
       responses[0].text == NULL ? 0 : curlx_uztoui(strlen(conn->passwd));
   }
@@ -405,7 +407,7 @@
       rc = CURLKHSTAT_REJECT;
 
     switch(rc) {
-    default: /* unknown return codes will equal reject */
+    default: /* unknown return codes is the same as reject */
     case CURLKHSTAT_REJECT:
       myssh_to(data, sshc, SSH_SESSION_FREE);
       FALLTHROUGH();
@@ -476,7 +478,7 @@
                                        LIBSSH2_HOSTKEY_HASH_SHA256);
     if(!fingerprint) {
       failf(data,
-            "Denied establishing ssh session: sha256 fingerprint "
+            "Denied establishing ssh session: SHA256 fingerprint "
             "not available");
       myssh_to(data, sshc, SSH_SESSION_FREE);
       return CURLE_PEER_FAILED_VERIFICATION;
@@ -491,7 +493,7 @@
     }
 
     if(!fingerprint_b64) {
-      failf(data, "sha256 fingerprint could not be encoded");
+      failf(data, "SHA256 fingerprint could not be encoded");
       myssh_to(data, sshc, SSH_SESSION_FREE);
       return CURLE_PEER_FAILED_VERIFICATION;
     }
@@ -509,13 +511,13 @@
       b64_pos++;
     }
 
-    /* Before we authenticate we check the hostkey's sha256 fingerprint
+    /* Before we authenticate we check the hostkey's SHA256 fingerprint
      * against a known fingerprint, if available.
      */
     if((pub_pos != b64_pos) ||
        strncmp(fingerprint_b64, pubkey_sha256, pub_pos)) {
       failf(data,
-            "Denied establishing ssh session: mismatch sha256 fingerprint. "
+            "Denied establishing ssh session: mismatch SHA256 fingerprint. "
             "Remote %s is not equal to %s", fingerprint_b64, pubkey_sha256);
       curlx_free(fingerprint_b64);
       myssh_to(data, sshc, SSH_SESSION_FREE);
@@ -545,17 +547,17 @@
       infof(data, "SSH MD5 fingerprint: %s", md5buffer);
     }
 
-    /* This does NOT verify the length of 'pubkey_md5' separately, which will
-       make the comparison below fail unless it is exactly 32 characters */
+    /* This does NOT verify the length of 'pubkey_md5' separately, which
+       makes the comparison below fail unless it is exactly 32 characters */
     if(!fingerprint || !curl_strequal(md5buffer, pubkey_md5)) {
       if(fingerprint) {
         failf(data,
-              "Denied establishing ssh session: mismatch md5 fingerprint. "
+              "Denied establishing ssh session: mismatch MD5 fingerprint. "
               "Remote %s is not equal to %s", md5buffer, pubkey_md5);
       }
       else {
         failf(data,
-              "Denied establishing ssh session: md5 fingerprint "
+              "Denied establishing ssh session: MD5 fingerprint "
               "not available");
       }
       myssh_to(data, sshc, SSH_SESSION_FREE);
@@ -600,7 +602,7 @@
 }
 
 /*
- * ssh_force_knownhost_key_type() will check the known hosts file and try to
+ * ssh_force_knownhost_key_type() checks the known hosts file and try to
  * force a specific public key type from the server if an entry is found.
  */
 static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data,
@@ -624,7 +626,7 @@
     struct connectdata *conn = data->conn;
     /* lets try to find our host in the known hosts file */
     while(!libssh2_knownhost_get(sshc->kh, &store, store)) {
-      /* For non-standard ports, the name will be enclosed in */
+      /* For non-standard ports, the name is enclosed in */
       /* square brackets, followed by a colon and the port */
       if(store) {
         if(store->name) {
@@ -715,12 +717,12 @@
   return result;
 }
 
-static CURLcode return_quote_error(struct Curl_easy *data,
-                                   struct ssh_conn *sshc)
+static CURLcode quote_error(struct Curl_easy *data,
+                            struct ssh_conn *sshc)
 {
   failf(data, "Suspicious data after the command line");
-  Curl_safefree(sshc->quote_path1);
-  Curl_safefree(sshc->quote_path2);
+  curlx_safefree(sshc->quote_path1);
+  curlx_safefree(sshc->quote_path2);
   return CURLE_QUOTE_ERROR;
 }
 
@@ -741,8 +743,8 @@
   sshc->acceptfail = FALSE;
 
   /* if a command starts with an asterisk, which a legal SFTP command never
-     can, the command will be allowed to fail without it causing any
-     aborts or cancels etc. It will cause libcurl to act as if the command
+     can, the command is allowed to fail without it causing any
+     aborts or cancels etc. It causes libcurl to act as if the command
      is successful, whatever the server responds. */
 
   if(cmd[0] == '*') {
@@ -808,11 +810,11 @@
     if(result) {
       if(result != CURLE_OUT_OF_MEMORY)
         failf(data, "Syntax error in %s: Bad second parameter", cmd);
-      Curl_safefree(sshc->quote_path1);
+      curlx_safefree(sshc->quote_path1);
       return result;
     }
     if(*cp)
-      return_quote_error(data, sshc);
+      return quote_error(data, sshc);
 
     memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
     myssh_to(data, sshc, SSH_SFTP_QUOTE_STAT);
@@ -827,17 +829,17 @@
     if(result) {
       if(result != CURLE_OUT_OF_MEMORY)
         failf(data, "Syntax error in ln/symlink: Bad second parameter");
-      Curl_safefree(sshc->quote_path1);
+      curlx_safefree(sshc->quote_path1);
       return result;
     }
     if(*cp)
-      return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     myssh_to(data, sshc, SSH_SFTP_QUOTE_SYMLINK);
     return result;
   }
   else if(!strncmp(cmd, "mkdir ", 6)) {
     if(*cp)
-      return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     /* create directory */
     myssh_to(data, sshc, SSH_SFTP_QUOTE_MKDIR);
     return result;
@@ -850,37 +852,37 @@
     if(result) {
       if(result != CURLE_OUT_OF_MEMORY)
         failf(data, "Syntax error in rename: Bad second parameter");
-      Curl_safefree(sshc->quote_path1);
+      curlx_safefree(sshc->quote_path1);
       return result;
     }
     if(*cp)
-      return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     myssh_to(data, sshc, SSH_SFTP_QUOTE_RENAME);
     return result;
   }
   else if(!strncmp(cmd, "rmdir ", 6)) {
     if(*cp)
-      return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     /* delete directory */
     myssh_to(data, sshc, SSH_SFTP_QUOTE_RMDIR);
     return result;
   }
   else if(!strncmp(cmd, "rm ", 3)) {
     if(*cp)
-      return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     myssh_to(data, sshc, SSH_SFTP_QUOTE_UNLINK);
     return result;
   }
   else if(!strncmp(cmd, "statvfs ", 8)) {
     if(*cp)
-      return_quote_error(data, sshc);
+      return quote_error(data, sshc);
     myssh_to(data, sshc, SSH_SFTP_QUOTE_STATVFS);
     return result;
   }
 
   failf(data, "Unknown SFTP command");
-  Curl_safefree(sshc->quote_path1);
-  Curl_safefree(sshc->quote_path2);
+  curlx_safefree(sshc->quote_path1);
+  curlx_safefree(sshc->quote_path2);
   return CURLE_QUOTE_ERROR;
 }
 
@@ -894,7 +896,7 @@
   /*
    * NOTE!!!  libssh2 requires that the destination path is a full path
    *          that includes the destination file and name OR ends in a "/"
-   *          If this is not done the destination file will be named the
+   *          If this is not done the destination file is named the
    *          same name as the last directory in the path.
    */
 
@@ -1054,7 +1056,7 @@
   /* upload data */
   Curl_xfer_setup_send(data, FIRSTSOCKET);
 
-  /* not set by Curl_xfer_setup to preserve keepon bits */
+  /* not set by Curl_xfer_setup to preserve io_flags */
   data->conn->recv_idx = FIRSTSOCKET;
 
   /* since we do not really wait for anything at this point, we want the
@@ -1103,7 +1105,7 @@
           if(!sshc->rsa)
             out_of_memory = TRUE;
           else if(curlx_stat(sshc->rsa, &sbuf)) {
-            Curl_safefree(sshc->rsa);
+            curlx_safefree(sshc->rsa);
           }
         }
         curlx_free(home);
@@ -1138,8 +1140,8 @@
     }
 
     if(out_of_memory || !sshc->rsa) {
-      Curl_safefree(sshc->rsa);
-      Curl_safefree(sshc->rsa_pub);
+      curlx_safefree(sshc->rsa);
+      curlx_safefree(sshc->rsa_pub);
       myssh_to(data, sshc, SSH_SESSION_FREE);
       return CURLE_OUT_OF_MEMORY;
     }
@@ -1157,7 +1159,7 @@
   else {
     myssh_to(data, sshc, SSH_AUTH_PASS_INIT);
   }
-  return 0;
+  return CURLE_OK;
 }
 
 static CURLcode sftp_quote_stat(struct Curl_easy *data,
@@ -1169,8 +1171,8 @@
   sshc->acceptfail = FALSE;
 
   /* if a command starts with an asterisk, which a legal SFTP command never
-     can, the command will be allowed to fail without it causing any aborts or
-     cancels etc. It will cause libcurl to act as if the command is
+     can, the command is allowed to fail without it causing any aborts or
+     cancels etc. It causes libcurl to act as if the command is
      successful, whatever the server responds. */
 
   if(cmd[0] == '*') {
@@ -1266,8 +1268,8 @@
   myssh_to(data, sshc, SSH_SFTP_QUOTE_SETSTAT);
   return CURLE_OK;
 fail:
-  Curl_safefree(sshc->quote_path1);
-  Curl_safefree(sshc->quote_path2);
+  curlx_safefree(sshc->quote_path1);
+  curlx_safefree(sshc->quote_path2);
   return CURLE_QUOTE_ERROR;
 }
 
@@ -1296,7 +1298,7 @@
     data->req.size = -1;
     data->req.maxdownload = -1;
     Curl_pgrsSetDownloadSize(data, -1);
-    attrs.filesize = 0; /* might be uninitialized but will be read below */
+    attrs.filesize = 0; /* might be uninitialized but is read below */
   }
   else {
     curl_off_t size = attrs.filesize;
@@ -1358,7 +1360,7 @@
   }
   Curl_xfer_setup_recv(data, FIRSTSOCKET, data->req.size);
 
-  /* not set by Curl_xfer_setup to preserve keepon bits */
+  /* not set by Curl_xfer_setup to preserve io_flags */
   data->conn->send_idx = 0;
 
   myssh_to(data, sshc, SSH_STOP);
@@ -1489,9 +1491,9 @@
    * must never change it later. Thus, always specify the correct username
    * here, even though the libssh2 docs kind of indicate that it should be
    * possible to get a 'generic' list (not user-specific) of authentication
-   * methods, presumably with a blank username. That will not work in my
+   * methods, presumably with a blank username. That does not work in my
    * experience.
-   * So always specify it here.
+   * Therefore always specify it here.
    */
   struct connectdata *conn = data->conn;
   sshc->authlist = libssh2_userauth_list(sshc->ssh_session,
@@ -1535,8 +1537,8 @@
   if(rc == LIBSSH2_ERROR_EAGAIN)
     return CURLE_AGAIN;
 
-  Curl_safefree(sshc->rsa_pub);
-  Curl_safefree(sshc->rsa);
+  curlx_safefree(sshc->rsa_pub);
+  curlx_safefree(sshc->rsa);
 
   if(rc == 0) {
     sshc->authed = TRUE;
@@ -1801,10 +1803,15 @@
   /*
    * Get the "home" directory
    */
-  int rc = libssh2_sftp_symlink_ex(sshc->sftp_session,
-                                   ".", curlx_uztoui(strlen(".")),
-                                   sshp->readdir_filename, CURL_PATH_MAX,
-                                   LIBSSH2_SFTP_REALPATH);
+  int rc;
+
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
+  rc = libssh2_sftp_symlink_ex(sshc->sftp_session, ".",
+                               curlx_uztoui(strlen(".")),
+                               sshp->readdir_filename, CURL_PATH_MAX,
+                               LIBSSH2_SFTP_REALPATH);
   if(rc == LIBSSH2_ERROR_EAGAIN)
     return CURLE_AGAIN;
 
@@ -1835,8 +1842,7 @@
 
   /* This is the last step in the SFTP connect phase. Do note that while we
      get the homedir here, we get the "workingpath" in the DO action since the
-     homedir will remain the same between request but the working path will
-     not. */
+     homedir remains the same between request but the working path does not. */
   CURL_TRC_SSH(data, "CONNECT phase done");
   return CURLE_OK;
 }
@@ -1845,7 +1851,12 @@
                                           struct ssh_conn *sshc,
                                           struct SSHPROTO *sshp)
 {
-  CURLcode result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
+  CURLcode result;
+
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
+  result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
   if(result) {
     myssh_to(data, sshc, SSH_STOP);
     return result;
@@ -1881,7 +1892,12 @@
                                      struct SSHPROTO *sshp)
 {
   /* Send quote commands */
-  CURLcode result = sftp_quote(data, sshc, sshp);
+  CURLcode result;
+
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
+  result = sftp_quote(data, sshc, sshp);
   if(result) {
     myssh_to(data, sshc, SSH_SFTP_CLOSE);
     sshc->nextstate = SSH_NO_STATE;
@@ -1892,8 +1908,8 @@
 static CURLcode ssh_state_sftp_next_quote(struct Curl_easy *data,
                                           struct ssh_conn *sshc)
 {
-  Curl_safefree(sshc->quote_path1);
-  Curl_safefree(sshc->quote_path2);
+  curlx_safefree(sshc->quote_path1);
+  curlx_safefree(sshc->quote_path2);
 
   sshc->quote_item = sshc->quote_item->next;
 
@@ -1917,7 +1933,12 @@
                                           struct SSHPROTO *sshp,
                                           bool *blockp)
 {
-  CURLcode result = sftp_quote_stat(data, sshc, sshp, blockp);
+  CURLcode result;
+
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
+  result = sftp_quote_stat(data, sshc, sshp, blockp);
   if(result) {
     myssh_to(data, sshc, SSH_SFTP_CLOSE);
     sshc->nextstate = SSH_NO_STATE;
@@ -1929,11 +1950,15 @@
                                              struct ssh_conn *sshc,
                                              struct SSHPROTO *sshp)
 {
-  int rc =
-    libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2,
-                         curlx_uztoui(strlen(sshc->quote_path2)),
-                         LIBSSH2_SFTP_SETSTAT,
-                         &sshp->quote_attrs);
+  int rc;
+
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
+  rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2,
+                            curlx_uztoui(strlen(sshc->quote_path2)),
+                            LIBSSH2_SFTP_SETSTAT,
+                            &sshp->quote_attrs);
   if(rc == LIBSSH2_ERROR_EAGAIN)
     return CURLE_AGAIN;
 
@@ -1941,8 +1966,8 @@
     unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
     failf(data, "Attempt to set SFTP stats for \"%s\" failed: %s",
           sshc->quote_path2, sftp_libssh2_strerror(sftperr));
-    Curl_safefree(sshc->quote_path1);
-    Curl_safefree(sshc->quote_path2);
+    curlx_safefree(sshc->quote_path1);
+    curlx_safefree(sshc->quote_path2);
     myssh_to(data, sshc, SSH_SFTP_CLOSE);
     sshc->nextstate = SSH_NO_STATE;
     return CURLE_QUOTE_ERROR;
@@ -1968,8 +1993,8 @@
     failf(data, "symlink \"%s\" to \"%s\" failed: %s",
           sshc->quote_path1, sshc->quote_path2,
           sftp_libssh2_strerror(sftperr));
-    Curl_safefree(sshc->quote_path1);
-    Curl_safefree(sshc->quote_path2);
+    curlx_safefree(sshc->quote_path1);
+    curlx_safefree(sshc->quote_path2);
     myssh_to(data, sshc, SSH_SFTP_CLOSE);
     sshc->nextstate = SSH_NO_STATE;
     return CURLE_QUOTE_ERROR;
@@ -1991,7 +2016,7 @@
     unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
     failf(data, "mkdir \"%s\" failed: %s",
           sshc->quote_path1, sftp_libssh2_strerror(sftperr));
-    Curl_safefree(sshc->quote_path1);
+    curlx_safefree(sshc->quote_path1);
     myssh_to(data, sshc, SSH_SFTP_CLOSE);
     sshc->nextstate = SSH_NO_STATE;
     return CURLE_QUOTE_ERROR;
@@ -2020,8 +2045,8 @@
     failf(data, "rename \"%s\" to \"%s\" failed: %s",
           sshc->quote_path1, sshc->quote_path2,
           sftp_libssh2_strerror(sftperr));
-    Curl_safefree(sshc->quote_path1);
-    Curl_safefree(sshc->quote_path2);
+    curlx_safefree(sshc->quote_path1);
+    curlx_safefree(sshc->quote_path2);
     myssh_to(data, sshc, SSH_SFTP_CLOSE);
     sshc->nextstate = SSH_NO_STATE;
     return CURLE_QUOTE_ERROR;
@@ -2042,7 +2067,7 @@
     unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
     failf(data, "rmdir \"%s\" failed: %s",
           sshc->quote_path1, sftp_libssh2_strerror(sftperr));
-    Curl_safefree(sshc->quote_path1);
+    curlx_safefree(sshc->quote_path1);
     myssh_to(data, sshc, SSH_SFTP_CLOSE);
     sshc->nextstate = SSH_NO_STATE;
     return CURLE_QUOTE_ERROR;
@@ -2063,7 +2088,7 @@
     unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
     failf(data, "rm \"%s\" failed: %s",
           sshc->quote_path1, sftp_libssh2_strerror(sftperr));
-    Curl_safefree(sshc->quote_path1);
+    curlx_safefree(sshc->quote_path1);
     myssh_to(data, sshc, SSH_SFTP_CLOSE);
     sshc->nextstate = SSH_NO_STATE;
     return CURLE_QUOTE_ERROR;
@@ -2087,7 +2112,7 @@
     unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
     failf(data, "statvfs \"%s\" failed: %s",
           sshc->quote_path1, sftp_libssh2_strerror(sftperr));
-    Curl_safefree(sshc->quote_path1);
+    curlx_safefree(sshc->quote_path1);
     myssh_to(data, sshc, SSH_SFTP_CLOSE);
     sshc->nextstate = SSH_NO_STATE;
     return CURLE_QUOTE_ERROR;
@@ -2140,9 +2165,14 @@
                                                  struct SSHPROTO *sshp)
 {
   /* 'mode' - parameter is preliminary - default to 0644 */
-  int rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path,
-                                 curlx_uztoui(strlen(sshp->path)),
-                                 (long)data->set.new_directory_perms);
+  int rc;
+
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
+  rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path,
+                             curlx_uztoui(strlen(sshp->path)),
+                             (long)data->set.new_directory_perms);
   if(rc == LIBSSH2_ERROR_EAGAIN)
     return CURLE_AGAIN;
 
@@ -2170,6 +2200,9 @@
                                             struct ssh_conn *sshc,
                                             struct SSHPROTO *sshp)
 {
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
   Curl_pgrsSetDownloadSize(data, -1);
   if(data->req.no_body) {
     myssh_to(data, sshc, SSH_STOP);
@@ -2204,13 +2237,16 @@
                                             struct SSHPROTO *sshp)
 {
   CURLcode result;
-  int rc =
-    libssh2_sftp_symlink_ex(sshc->sftp_session,
-                            curlx_dyn_ptr(&sshp->readdir_link),
-                            (unsigned int)
-                            curlx_dyn_len(&sshp->readdir_link),
-                            sshp->readdir_filename,
-                            CURL_PATH_MAX, LIBSSH2_SFTP_READLINK);
+  int rc;
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
+  rc = libssh2_sftp_symlink_ex(sshc->sftp_session,
+                               curlx_dyn_ptr(&sshp->readdir_link),
+                               (unsigned int)
+                               curlx_dyn_len(&sshp->readdir_link),
+                               sshp->readdir_filename,
+                               CURL_PATH_MAX, LIBSSH2_SFTP_READLINK);
   if(rc == LIBSSH2_ERROR_EAGAIN)
     return CURLE_AGAIN;
 
@@ -2233,9 +2269,12 @@
                                             struct SSHPROTO *sshp)
 {
   curl_off_t bytecount;
+  libssh2_struct_stat sb;
 
+  if(!sshp)
+    return CURLE_FAILED_INIT;
   /*
-   * We must check the remote file; if it is a directory no values will
+   * We must check the remote file; if it is a directory no values are
    * be set in sb
    */
 
@@ -2244,7 +2283,6 @@
    */
 
   /* get a fresh new channel from the ssh layer */
-  libssh2_struct_stat sb;
   memset(&sb, 0, sizeof(libssh2_struct_stat));
   sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session, sshp->path, &sb);
   if(!sshc->ssh_channel) {
@@ -2266,7 +2304,7 @@
   data->req.maxdownload = (curl_off_t)sb.st_size;
   Curl_xfer_setup_recv(data, FIRSTSOCKET, bytecount);
 
-  /* not set by Curl_xfer_setup to preserve keepon bits */
+  /* not set by Curl_xfer_setup to preserve io_flags */
   data->conn->send_idx = 0;
 
   myssh_to(data, sshc, SSH_STOP);
@@ -2278,6 +2316,8 @@
                                      struct SSHPROTO *sshp)
 {
   int rc = 0;
+  if(!sshp)
+    return CURLE_FAILED_INIT;
   if(sshc->sftp_handle) {
     rc = libssh2_sftp_close(sshc->sftp_handle);
     if(rc == LIBSSH2_ERROR_EAGAIN)
@@ -2291,7 +2331,7 @@
     sshc->sftp_handle = NULL;
   }
 
-  Curl_safefree(sshp->path);
+  curlx_safefree(sshp->path);
 
   CURL_TRC_SSH(data, "SFTP DONE done");
 
@@ -2339,7 +2379,7 @@
     sshc->sftp_session = NULL;
   }
 
-  Curl_safefree(sshc->homedir);
+  curlx_safefree(sshc->homedir);
 
   myssh_to(data, sshc, SSH_SESSION_DISCONNECT);
   return CURLE_OK;
@@ -2349,6 +2389,8 @@
                                              struct ssh_conn *sshc,
                                              struct SSHPROTO *sshp)
 {
+  if(!sshp)
+    return CURLE_FAILED_INIT;
   /*
    * Work on getting the specified file
    */
@@ -2377,10 +2419,12 @@
                                           struct ssh_conn *sshc,
                                           struct SSHPROTO *sshp)
 {
+  if(!sshp)
+    return CURLE_FAILED_INIT;
   /*
    * libssh2 requires that the destination path is a full path that
    * includes the destination file and name OR ends in a "/" . If this is
-   * not done the destination file will be named the same name as the last
+   * not done the destination file is named the same name as the last
    * directory in the path.
    */
   sshc->ssh_channel =
@@ -2412,7 +2456,7 @@
   Curl_pgrsSetUploadSize(data, data->state.infilesize);
   Curl_xfer_setup_send(data, FIRSTSOCKET);
 
-  /* not set by Curl_xfer_setup to preserve keepon bits */
+  /* not set by Curl_xfer_setup to preserve io_flags */
   data->conn->recv_idx = FIRSTSOCKET;
 
   myssh_to(data, sshc, SSH_STOP);
@@ -2452,7 +2496,7 @@
     }
   }
 
-  Curl_safefree(sshc->homedir);
+  curlx_safefree(sshc->homedir);
 
   myssh_to(data, sshc, SSH_SESSION_FREE);
   return CURLE_OK;
@@ -2548,19 +2592,320 @@
   DEBUGASSERT(sshc->kh == NULL);
   DEBUGASSERT(sshc->ssh_agent == NULL);
 
-  Curl_safefree(sshc->rsa_pub);
-  Curl_safefree(sshc->rsa);
-  Curl_safefree(sshc->quote_path1);
-  Curl_safefree(sshc->quote_path2);
-  Curl_safefree(sshc->homedir);
+  curlx_safefree(sshc->rsa_pub);
+  curlx_safefree(sshc->rsa);
+  curlx_safefree(sshc->quote_path1);
+  curlx_safefree(sshc->quote_path2);
+  curlx_safefree(sshc->homedir);
 
   return CURLE_OK;
 }
 
+static CURLcode ssh_state_sftp_getinfo(struct Curl_easy *data,
+                                       struct ssh_conn *sshc)
+{
+  if(data->set.get_filetime)
+    myssh_to(data, sshc, SSH_SFTP_FILETIME);
+  else
+    myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_sftp_filetime(struct Curl_easy *data,
+                                        struct ssh_conn *sshc,
+                                        struct SSHPROTO *sshp)
+{
+  LIBSSH2_SFTP_ATTRIBUTES attrs;
+  int rc;
+
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
+  rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
+                            curlx_uztoui(strlen(sshp->path)),
+                            LIBSSH2_SFTP_STAT, &attrs);
+  if(rc == LIBSSH2_ERROR_EAGAIN)
+    return CURLE_AGAIN;
+
+  if(rc == 0)
+    data->info.filetime = (time_t)attrs.mtime;
+
+  myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_sftp_trans_init(struct Curl_easy *data,
+                                          struct ssh_conn *sshc,
+                                          struct SSHPROTO *sshp)
+{
+  if(data->state.upload)
+    myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
+  else if(sshp) {
+    size_t plen = strlen(sshp->path);
+    if(plen && (sshp->path[plen - 1] == '/'))
+      myssh_to(data, sshc, SSH_SFTP_READDIR_INIT);
+    else
+      myssh_to(data, sshc, SSH_SFTP_DOWNLOAD_INIT);
+  }
+  else
+    return CURLE_FAILED_INIT;
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_sftp_upload_init(struct Curl_easy *data,
+                                           struct ssh_conn *sshc,
+                                           struct SSHPROTO *sshp,
+                                           bool *block)
+{
+  CURLcode result;
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+  result = sftp_upload_init(data, sshc, sshp, block);
+  if(result) {
+    myssh_to(data, sshc, SSH_SFTP_CLOSE);
+    sshc->nextstate = SSH_NO_STATE;
+  }
+  return result;
+}
+
+static CURLcode ssh_state_sftp_create_dirs_init(struct Curl_easy *data,
+                                                struct ssh_conn *sshc,
+                                                struct SSHPROTO *sshp)
+{
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+  if(strlen(sshp->path) > 1) {
+    sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */
+    myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS);
+  }
+  else
+    myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_sftp_create_dirs(struct Curl_easy *data,
+                                           struct ssh_conn *sshc,
+                                           struct SSHPROTO *sshp)
+{
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+  sshc->slash_pos = strchr(sshc->slash_pos, '/');
+  if(sshc->slash_pos) {
+    *sshc->slash_pos = 0;
+    infof(data, "Creating directory '%s'", sshp->path);
+    myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR);
+    return CURLE_OK;
+  }
+  myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_sftp_readdir(struct Curl_easy *data,
+                                       struct ssh_conn *sshc,
+                                       struct SSHPROTO *sshp,
+                                       bool *block)
+{
+  CURLcode result;
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+  result = sftp_readdir(data, sshc, sshp, block);
+  if(result)
+    myssh_to(data, sshc, SSH_SFTP_CLOSE);
+  return result;
+}
+
+static CURLcode ssh_state_sftp_readdir_bottom(struct Curl_easy *data,
+                                              struct ssh_conn *sshc,
+                                              struct SSHPROTO *sshp)
+{
+  CURLcode result;
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+
+  result = curlx_dyn_addn(&sshp->readdir, "\n", 1);
+  if(!result)
+    result = Curl_client_write(data, CLIENTWRITE_BODY,
+                               curlx_dyn_ptr(&sshp->readdir),
+                               curlx_dyn_len(&sshp->readdir));
+  if(result) {
+    curlx_dyn_free(&sshp->readdir);
+    myssh_to(data, sshc, SSH_STOP);
+  }
+  else {
+    curlx_dyn_reset(&sshp->readdir);
+    myssh_to(data, sshc, SSH_SFTP_READDIR);
+  }
+  return result;
+}
+
+static CURLcode ssh_state_sftp_readdir_done(struct Curl_easy *data,
+                                            struct ssh_conn *sshc)
+{
+  if(libssh2_sftp_closedir(sshc->sftp_handle) ==
+     LIBSSH2_ERROR_EAGAIN)
+    return CURLE_AGAIN;
+
+  sshc->sftp_handle = NULL;
+
+  /* no data to transfer */
+  Curl_xfer_setup_nop(data);
+  myssh_to(data, sshc, SSH_STOP);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_sftp_download_stat(struct Curl_easy *data,
+                                             struct ssh_conn *sshc,
+                                             struct SSHPROTO *sshp,
+                                             bool *block)
+{
+  CURLcode result;
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+  result = sftp_download_stat(data, sshc, sshp, block);
+  if(result) {
+    myssh_to(data, sshc, SSH_SFTP_CLOSE);
+    sshc->nextstate = SSH_NO_STATE;
+  }
+  return result;
+}
+
+static CURLcode ssh_state_scp_trans_init(struct Curl_easy *data,
+                                         struct ssh_conn *sshc,
+                                         struct SSHPROTO *sshp)
+{
+  CURLcode result;
+  if(!sshp)
+    return CURLE_FAILED_INIT;
+  result = Curl_getworkingpath(data, sshc->homedir,
+                               &sshp->path);
+  if(result) {
+    myssh_to(data, sshc, SSH_STOP);
+    return result;
+  }
+
+  if(data->state.upload) {
+    if(data->state.infilesize < 0) {
+      failf(data, "SCP requires a known file size for upload");
+      myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
+      return CURLE_UPLOAD_FAILED;
+    }
+    myssh_to(data, sshc, SSH_SCP_UPLOAD_INIT);
+  }
+  else
+    myssh_to(data, sshc, SSH_SCP_DOWNLOAD_INIT);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_scp_done(struct Curl_easy *data,
+                                   struct ssh_conn *sshc)
+{
+  if(data->state.upload)
+    myssh_to(data, sshc, SSH_SCP_SEND_EOF);
+  else
+    myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_scp_send_eof(struct Curl_easy *data,
+                                       struct ssh_conn *sshc)
+{
+  if(sshc->ssh_channel) {
+    int rc = libssh2_channel_send_eof(sshc->ssh_channel);
+    if(rc == LIBSSH2_ERROR_EAGAIN)
+      return CURLE_AGAIN;
+    if(rc) {
+      char *err_msg = NULL;
+      (void)libssh2_session_last_error(sshc->ssh_session,
+                                       &err_msg, NULL, 0);
+      infof(data,
+            "Failed to send libssh2 channel EOF: %d %s",
+            rc, err_msg);
+    }
+  }
+  myssh_to(data, sshc, SSH_SCP_WAIT_EOF);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_scp_wait_eof(struct Curl_easy *data,
+                                       struct ssh_conn *sshc)
+{
+  if(sshc->ssh_channel) {
+    int rc = libssh2_channel_wait_eof(sshc->ssh_channel);
+    if(rc == LIBSSH2_ERROR_EAGAIN)
+      return CURLE_AGAIN;
+    if(rc) {
+      char *err_msg = NULL;
+      (void)libssh2_session_last_error(sshc->ssh_session,
+                                       &err_msg, NULL, 0);
+      infof(data, "Failed to get channel EOF: %d %s",
+            rc, err_msg);
+    }
+  }
+  myssh_to(data, sshc, SSH_SCP_WAIT_CLOSE);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_scp_wait_close(struct Curl_easy *data,
+                                         struct ssh_conn *sshc)
+{
+  if(sshc->ssh_channel) {
+    int rc =
+      libssh2_channel_wait_closed(sshc->ssh_channel);
+    if(rc == LIBSSH2_ERROR_EAGAIN)
+      return CURLE_AGAIN;
+    if(rc) {
+      char *err_msg = NULL;
+      (void)libssh2_session_last_error(sshc->ssh_session,
+                                       &err_msg, NULL, 0);
+      infof(data, "Channel failed to close: %d %s",
+            rc, err_msg);
+    }
+  }
+  myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_scp_channel_free(
+  struct Curl_easy *data,
+  struct ssh_conn *sshc)
+{
+  if(sshc->ssh_channel) {
+    int rc = libssh2_channel_free(sshc->ssh_channel);
+    if(rc == LIBSSH2_ERROR_EAGAIN)
+      return CURLE_AGAIN;
+    if(rc < 0) {
+      char *err_msg = NULL;
+      (void)libssh2_session_last_error(sshc->ssh_session,
+                                       &err_msg, NULL, 0);
+      infof(data,
+            "Failed to free libssh2 scp subsystem: %d %s",
+            rc, err_msg);
+    }
+    sshc->ssh_channel = NULL;
+  }
+  CURL_TRC_SSH(data, "SCP DONE phase complete");
+  myssh_to(data, sshc, SSH_STOP);
+  return CURLE_OK;
+}
+
+static CURLcode ssh_state_session_free(struct Curl_easy *data,
+                                       struct ssh_conn *sshc)
+{
+  CURLcode result = sshc_cleanup(sshc, data, FALSE);
+  struct connectdata *conn = data->conn;
+  if(result)
+    return result;
+  memset(sshc, 0, sizeof(struct ssh_conn));
+  connclose(conn, "SSH session free");
+  sshc->state = SSH_SESSION_FREE; /* current */
+  myssh_to(data, sshc, SSH_STOP);
+  return CURLE_OK;
+}
+
 /*
  * ssh_statemachine() runs the SSH state machine as far as it can without
  * blocking and without reaching the end. The data the pointer 'block' points
- * to will be set to TRUE if the libssh2 function returns LIBSSH2_ERROR_EAGAIN
+ * to is set to TRUE if the libssh2 function returns LIBSSH2_ERROR_EAGAIN
  * meaning it wants to be called again when the socket is ready
  */
 static CURLcode ssh_statemachine(struct Curl_easy *data,
@@ -2569,7 +2914,6 @@
                                  bool *block)
 {
   CURLcode result = CURLE_OK;
-  struct connectdata *conn = data->conn;
   *block = 0; /* we are not blocking by default */
 
   do {
@@ -2647,13 +2991,11 @@
       break;
 
     case SSH_SFTP_REALPATH:
-      result = sshp ? ssh_state_sftp_realpath(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_realpath(data, sshc, sshp);
       break;
 
     case SSH_SFTP_QUOTE_INIT:
-      result = sshp ? ssh_state_sftp_quote_init(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_quote_init(data, sshc, sshp);
       break;
 
     case SSH_SFTP_POSTQUOTE_INIT:
@@ -2661,8 +3003,7 @@
       break;
 
     case SSH_SFTP_QUOTE:
-      result = sshp ? ssh_state_sftp_quote(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_quote(data, sshc, sshp);
       break;
 
     case SSH_SFTP_NEXT_QUOTE:
@@ -2670,13 +3011,11 @@
       break;
 
     case SSH_SFTP_QUOTE_STAT:
-      result = sshp ? ssh_state_sftp_quote_stat(data, sshc, sshp, block) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_quote_stat(data, sshc, sshp, block);
       break;
 
     case SSH_SFTP_QUOTE_SETSTAT:
-      result = sshp ? ssh_state_sftp_quote_setstat(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_quote_setstat(data, sshc, sshp);
       break;
 
     case SSH_SFTP_QUOTE_SYMLINK:
@@ -2704,160 +3043,63 @@
       break;
 
     case SSH_SFTP_GETINFO:
-      if(data->set.get_filetime) {
-        myssh_to(data, sshc, SSH_SFTP_FILETIME);
-      }
-      else {
-        myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
-      }
+      result = ssh_state_sftp_getinfo(data, sshc);
       break;
 
-    case SSH_SFTP_FILETIME: {
-      LIBSSH2_SFTP_ATTRIBUTES attrs;
-      int rc;
-
-      if(!sshp) {
-        result = CURLE_FAILED_INIT;
-        break;
-      }
-      rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
-                                curlx_uztoui(strlen(sshp->path)),
-                                LIBSSH2_SFTP_STAT, &attrs);
-      if(rc == LIBSSH2_ERROR_EAGAIN) {
-        result = CURLE_AGAIN;
-        break;
-      }
-      if(rc == 0) {
-        data->info.filetime = (time_t)attrs.mtime;
-      }
-
-      myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
+    case SSH_SFTP_FILETIME:
+      result = ssh_state_sftp_filetime(data, sshc, sshp);
       break;
-    }
 
     case SSH_SFTP_TRANS_INIT:
-      if(data->state.upload)
-        myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
-      else if(sshp) {
-        if(sshp->path[strlen(sshp->path) - 1] == '/')
-          myssh_to(data, sshc, SSH_SFTP_READDIR_INIT);
-        else
-          myssh_to(data, sshc, SSH_SFTP_DOWNLOAD_INIT);
-      }
-      else
-        result = CURLE_FAILED_INIT;
+      result = ssh_state_sftp_trans_init(data, sshc, sshp);
       break;
 
     case SSH_SFTP_UPLOAD_INIT:
-      result = sshp ? sftp_upload_init(data, sshc, sshp, block) :
-               CURLE_FAILED_INIT;
-      if(result) {
-        myssh_to(data, sshc, SSH_SFTP_CLOSE);
-        sshc->nextstate = SSH_NO_STATE;
-      }
+      result = ssh_state_sftp_upload_init(data, sshc, sshp, block);
       break;
 
     case SSH_SFTP_CREATE_DIRS_INIT:
-      if(!sshp)
-        result = CURLE_FAILED_INIT;
-      else if(strlen(sshp->path) > 1) {
-        sshc->slash_pos = sshp->path + 1; /* ignore the leading '/' */
-        myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS);
-      }
-      else {
-        myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
-      }
+      result = ssh_state_sftp_create_dirs_init(data, sshc, sshp);
       break;
 
     case SSH_SFTP_CREATE_DIRS:
-      if(!sshp) {
-        result = CURLE_FAILED_INIT;
-        break;
-      }
-      sshc->slash_pos = strchr(sshc->slash_pos, '/');
-      if(sshc->slash_pos) {
-        *sshc->slash_pos = 0;
-
-        infof(data, "Creating directory '%s'", sshp->path);
-        myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR);
-        break;
-      }
-      myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
+      result = ssh_state_sftp_create_dirs(data, sshc, sshp);
       break;
 
     case SSH_SFTP_CREATE_DIRS_MKDIR:
-      result = sshp ? ssh_state_sftp_create_dirs_mkdir(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_create_dirs_mkdir(data, sshc, sshp);
       break;
 
     case SSH_SFTP_READDIR_INIT:
-      result = sshp ? ssh_state_sftp_readdir_init(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_readdir_init(data, sshc, sshp);
       break;
 
     case SSH_SFTP_READDIR:
-      result = sshp ? sftp_readdir(data, sshc, sshp, block) :
-               CURLE_FAILED_INIT;
-      if(result) {
-        myssh_to(data, sshc, SSH_SFTP_CLOSE);
-      }
+      result = ssh_state_sftp_readdir(data, sshc, sshp, block);
       break;
 
     case SSH_SFTP_READDIR_LINK:
-      result = sshp ? ssh_state_sftp_readdir_link(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_readdir_link(data, sshc, sshp);
       break;
 
     case SSH_SFTP_READDIR_BOTTOM:
-      if(!sshp) {
-        result = CURLE_FAILED_INIT;
-        break;
-      }
-      result = curlx_dyn_addn(&sshp->readdir, "\n", 1);
-      if(!result)
-        result = Curl_client_write(data, CLIENTWRITE_BODY,
-                                   curlx_dyn_ptr(&sshp->readdir),
-                                   curlx_dyn_len(&sshp->readdir));
-
-      if(result) {
-        curlx_dyn_free(&sshp->readdir);
-        myssh_to(data, sshc, SSH_STOP);
-      }
-      else {
-        curlx_dyn_reset(&sshp->readdir);
-        myssh_to(data, sshc, SSH_SFTP_READDIR);
-      }
+      result = ssh_state_sftp_readdir_bottom(data, sshc, sshp);
       break;
 
     case SSH_SFTP_READDIR_DONE:
-      if(libssh2_sftp_closedir(sshc->sftp_handle) == LIBSSH2_ERROR_EAGAIN)
-        result = CURLE_AGAIN;
-      else {
-        sshc->sftp_handle = NULL;
-
-        /* no data to transfer */
-        Curl_xfer_setup_nop(data);
-        myssh_to(data, sshc, SSH_STOP);
-      }
+      result = ssh_state_sftp_readdir_done(data, sshc);
       break;
 
     case SSH_SFTP_DOWNLOAD_INIT:
-      result = sshp ? ssh_state_sftp_download_init(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_download_init(data, sshc, sshp);
       break;
 
     case SSH_SFTP_DOWNLOAD_STAT:
-      result = sshp ? sftp_download_stat(data, sshc, sshp, block) :
-               CURLE_FAILED_INIT;
-      if(result) {
-        myssh_to(data, sshc, SSH_SFTP_CLOSE);
-        sshc->nextstate = SSH_NO_STATE;
-      }
+      result = ssh_state_sftp_download_stat(data, sshc, sshp, block);
       break;
 
     case SSH_SFTP_CLOSE:
-      result = sshp ? ssh_state_sftp_close(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_sftp_close(data, sshc, sshp);
       break;
 
     case SSH_SFTP_SHUTDOWN:
@@ -2865,114 +3107,35 @@
       break;
 
     case SSH_SCP_TRANS_INIT:
-      result = sshp ? Curl_getworkingpath(data, sshc->homedir, &sshp->path) :
-               CURLE_FAILED_INIT;
-      if(result) {
-        myssh_to(data, sshc, SSH_STOP);
-        break;
-      }
-
-      if(data->state.upload) {
-        if(data->state.infilesize < 0) {
-          failf(data, "SCP requires a known file size for upload");
-          result = CURLE_UPLOAD_FAILED;
-          myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
-          break;
-        }
-        myssh_to(data, sshc, SSH_SCP_UPLOAD_INIT);
-      }
-      else {
-        myssh_to(data, sshc, SSH_SCP_DOWNLOAD_INIT);
-      }
+      result = ssh_state_scp_trans_init(data, sshc, sshp);
       break;
 
     case SSH_SCP_UPLOAD_INIT:
-      result = sshp ? ssh_state_scp_upload_init(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_scp_upload_init(data, sshc, sshp);
       break;
 
     case SSH_SCP_DOWNLOAD_INIT:
-      result = sshp ? ssh_state_scp_download_init(data, sshc, sshp) :
-               CURLE_FAILED_INIT;
+      result = ssh_state_scp_download_init(data, sshc, sshp);
       break;
 
     case SSH_SCP_DONE:
-      if(data->state.upload)
-        myssh_to(data, sshc, SSH_SCP_SEND_EOF);
-      else
-        myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
+      result = ssh_state_scp_done(data, sshc);
       break;
 
     case SSH_SCP_SEND_EOF:
-      if(sshc->ssh_channel) {
-        int rc = libssh2_channel_send_eof(sshc->ssh_channel);
-        if(rc == LIBSSH2_ERROR_EAGAIN) {
-          result = CURLE_AGAIN;
-          break;
-        }
-        if(rc) {
-          char *err_msg = NULL;
-          (void)libssh2_session_last_error(sshc->ssh_session,
-                                           &err_msg, NULL, 0);
-          infof(data, "Failed to send libssh2 channel EOF: %d %s",
-                rc, err_msg);
-        }
-      }
-      myssh_to(data, sshc, SSH_SCP_WAIT_EOF);
+      result = ssh_state_scp_send_eof(data, sshc);
       break;
 
     case SSH_SCP_WAIT_EOF:
-      if(sshc->ssh_channel) {
-        int rc = libssh2_channel_wait_eof(sshc->ssh_channel);
-        if(rc == LIBSSH2_ERROR_EAGAIN) {
-          result = CURLE_AGAIN;
-          break;
-        }
-        if(rc) {
-          char *err_msg = NULL;
-          (void)libssh2_session_last_error(sshc->ssh_session,
-                                           &err_msg, NULL, 0);
-          infof(data, "Failed to get channel EOF: %d %s", rc, err_msg);
-        }
-      }
-      myssh_to(data, sshc, SSH_SCP_WAIT_CLOSE);
+      result = ssh_state_scp_wait_eof(data, sshc);
       break;
 
     case SSH_SCP_WAIT_CLOSE:
-      if(sshc->ssh_channel) {
-        int rc = libssh2_channel_wait_closed(sshc->ssh_channel);
-        if(rc == LIBSSH2_ERROR_EAGAIN) {
-          result = CURLE_AGAIN;
-          break;
-        }
-        if(rc) {
-          char *err_msg = NULL;
-          (void)libssh2_session_last_error(sshc->ssh_session,
-                                           &err_msg, NULL, 0);
-          infof(data, "Channel failed to close: %d %s", rc, err_msg);
-        }
-      }
-      myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
+      result = ssh_state_scp_wait_close(data, sshc);
       break;
 
     case SSH_SCP_CHANNEL_FREE:
-      if(sshc->ssh_channel) {
-        int rc = libssh2_channel_free(sshc->ssh_channel);
-        if(rc == LIBSSH2_ERROR_EAGAIN) {
-          result = CURLE_AGAIN;
-          break;
-        }
-        if(rc < 0) {
-          char *err_msg = NULL;
-          (void)libssh2_session_last_error(sshc->ssh_session,
-                                           &err_msg, NULL, 0);
-          infof(data, "Failed to free libssh2 scp subsystem: %d %s",
-                rc, err_msg);
-        }
-        sshc->ssh_channel = NULL;
-      }
-      CURL_TRC_SSH(data, "SCP DONE phase complete");
-      myssh_to(data, sshc, SSH_STOP);
+      result = ssh_state_scp_channel_free(data, sshc);
       break;
 
     case SSH_SESSION_DISCONNECT:
@@ -2980,14 +3143,7 @@
       break;
 
     case SSH_SESSION_FREE:
-      result = sshc_cleanup(sshc, data, FALSE);
-      if(result)
-        break;
-      /* the code we are about to return */
-      memset(sshc, 0, sizeof(struct ssh_conn));
-      connclose(conn, "SSH session free");
-      sshc->state = SSH_SESSION_FREE; /* current */
-      myssh_to(data, sshc, SSH_STOP);
+      result = ssh_state_session_free(data, sshc);
       break;
 
     case SSH_QUIT:
@@ -3024,12 +3180,12 @@
   if(!sshc || (sock == CURL_SOCKET_BAD))
     return CURLE_FAILED_INIT;
 
-  waitfor = sshc->waitfor ? sshc->waitfor : data->req.keepon;
+  waitfor = sshc->waitfor ? sshc->waitfor : data->req.io_flags;
   if(waitfor) {
     int flags = 0;
-    if(waitfor & KEEP_RECV)
+    if(waitfor & REQ_IO_RECV)
       flags |= CURL_POLL_IN;
-    if(waitfor & KEEP_SEND)
+    if(waitfor & REQ_IO_SEND)
       flags |= CURL_POLL_OUT;
     DEBUGASSERT(flags);
     CURL_TRC_SSH(data, "pollset, flags=%x", flags);
@@ -3058,8 +3214,9 @@
     dir = libssh2_session_block_directions(sshc->ssh_session);
     if(dir) {
       /* translate the libssh2 define bits into our own bit defines */
-      sshc->waitfor = ((dir & LIBSSH2_SESSION_BLOCK_INBOUND) ? KEEP_RECV : 0) |
-                      ((dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) ? KEEP_SEND : 0);
+      sshc->waitfor =
+        ((dir & LIBSSH2_SESSION_BLOCK_INBOUND) ? REQ_IO_RECV : 0) |
+        ((dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) ? REQ_IO_SEND : 0);
     }
   }
   if(!dir)
@@ -3146,7 +3303,7 @@
   struct SSHPROTO *sshp = entry;
   (void)key;
   (void)klen;
-  Curl_safefree(sshp->path);
+  curlx_safefree(sshp->path);
   curlx_dyn_free(&sshp->readdir);
   curlx_dyn_free(&sshp->readdir_link);
   curlx_free(sshp);
@@ -3520,7 +3677,7 @@
   if(Curl_pgrsDone(data))
     return CURLE_ABORTED_BY_CALLBACK;
 
-  data->req.keepon = 0; /* clear all bits */
+  CURL_REQ_CLEAR_IO(data);
   return result;
 }
 
@@ -3835,7 +3992,7 @@
   scp_disconnect,                       /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ssh_attach,                           /* attach */
   ZERO_NULL,                            /* follow */
 };
@@ -3858,7 +4015,7 @@
   sftp_disconnect,                      /* disconnect */
   ZERO_NULL,                            /* write_resp */
   ZERO_NULL,                            /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ssh_attach,                           /* attach */
   ZERO_NULL,                            /* follow */
 };
diff --git a/Utilities/cmcurl/lib/vssh/ssh.h b/Utilities/cmcurl/lib/vssh/ssh.h
index 1afb1e8..44e72ab 100644
--- a/Utilities/cmcurl/lib/vssh/ssh.h
+++ b/Utilities/cmcurl/lib/vssh/ssh.h
@@ -29,9 +29,6 @@
 extern const struct Curl_protocol Curl_protocol_sftp;
 extern const struct Curl_protocol Curl_protocol_scp;
 
-extern const struct Curl_scheme Curl_scheme_sftp;
-extern const struct Curl_scheme Curl_scheme_scp;
-
 #ifdef USE_SSH
 
 #ifdef USE_LIBSSH2
@@ -162,7 +159,7 @@
   int secondCreateDirs;         /* counter use by the code to see if the
                                    second attempt has been made to change
                                    to/create a directory */
-  int waitfor;                  /* KEEP_RECV/KEEP_SEND bits overriding
+  int waitfor;                  /* REQ_IO_RECV/REQ_IO_SEND bits overriding
                                    pollset given flags */
   char *slash_pos;              /* used by the SFTP_CREATE_DIRS state */
 
diff --git a/Utilities/cmcurl/lib/vssh/vssh.c b/Utilities/cmcurl/lib/vssh/vssh.c
index d499a63..0ba4a9e 100644
--- a/Utilities/cmcurl/lib/vssh/vssh.c
+++ b/Utilities/cmcurl/lib/vssh/vssh.c
@@ -331,34 +331,3 @@
 }
 
 #endif /* USE_SSH */
-
-/*
- * SFTP protocol handler.
- */
-const struct Curl_scheme Curl_scheme_sftp = {
-  "SFTP",                               /* scheme */
-#ifndef USE_SSH
-  NULL,
-#else
-  &Curl_protocol_sftp,
-#endif
-  CURLPROTO_SFTP,                       /* protocol */
-  CURLPROTO_SFTP,                       /* family */
-  PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION | /* flags */
-  PROTOPT_NOURLQUERY | PROTOPT_CONN_REUSE,
-  PORT_SSH                              /* defport */
-};
-
-const struct Curl_scheme Curl_scheme_scp = {
-  "SCP",                                /* scheme */
-#ifndef USE_SSH
-  NULL,
-#else
-  &Curl_protocol_scp,
-#endif
-  CURLPROTO_SCP,                        /* protocol */
-  CURLPROTO_SCP,                        /* family */
-  PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION | /* flags */
-  PROTOPT_NOURLQUERY | PROTOPT_CONN_REUSE,
-  PORT_SSH,                             /* defport */
-};
diff --git a/Utilities/cmcurl/lib/vtls/apple.c b/Utilities/cmcurl/lib/vtls/apple.c
index a6a5ba0..e28a40c 100644
--- a/Utilities/cmcurl/lib/vtls/apple.c
+++ b/Utilities/cmcurl/lib/vtls/apple.c
@@ -130,7 +130,7 @@
     struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
     if(!ssl_config->no_revoke) {
       if(__builtin_available(macOS 10.9, iOS 7, tvOS 9, watchOS 2, *)) {
-        /* Even without this set, validation will seemingly-unavoidably fail
+        /* Even without this set, validation seemingly-unavoidably fails
          * for certificates that trustd already knows to be revoked.
          * This policy further allows trustd to consult CRLs and OCSP data
          * to determine revocation status (which it may then cache). */
@@ -142,7 +142,7 @@
          * of a cert being NOT REVOKED. Which not in general available for
          * certificates on the Internet.
          * It seems that applications using this policy are expected to PIN
-         * their certificate public keys or verification will fail.
+         * their certificate public keys or verification fails.
          * This does not seem to be what we want here. */
         if(!ssl_config->revoke_best_effort) {
           revocation_flags |= kSecRevocationRequirePositiveResponse;
diff --git a/Utilities/cmcurl/lib/vtls/gtls.c b/Utilities/cmcurl/lib/vtls/gtls.c
index a0a465c..9b6a4fa 100644
--- a/Utilities/cmcurl/lib/vtls/gtls.c
+++ b/Utilities/cmcurl/lib/vtls/gtls.c
@@ -127,7 +127,7 @@
   }
 
   result = Curl_conn_cf_recv(cf->next, data, buf, blen, &nread);
-  CURL_TRC_CF(data, cf, "glts_pull(len=%zu) -> %d, %zd", blen, result, nread);
+  CURL_TRC_CF(data, cf, "glts_pull(len=%zu) -> %d, %zu", blen, result, nread);
   backend->gtls.io_result = result;
   if(result) {
     /* !checksrc! disable ERRNOVAR 1 */
@@ -203,10 +203,15 @@
   f = curlx_fopen(file, "rb");
   if(!f)
     return loaded_file;
-  if(fseek(f, 0, SEEK_END) != 0 ||
-     (filelen = ftell(f)) < 0 ||
-     fseek(f, 0, SEEK_SET) != 0 ||
-     !(ptr = curlx_malloc((size_t)filelen)))
+  if(fseek(f, 0, SEEK_END) != 0)
+    goto out;
+  filelen = ftell(f);
+  if(filelen < 0 || filelen > CURL_MAX_INPUT_LENGTH)
+    goto out;
+  if(fseek(f, 0, SEEK_SET) != 0)
+    goto out;
+  ptr = curlx_malloc((size_t)filelen);
+  if(!ptr)
     goto out;
   if(fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) {
     curlx_free(ptr);
@@ -257,6 +262,7 @@
     return CURLE_AGAIN;
   }
   else if((rc < 0) && !gnutls_error_is_fatal(rc)) {
+#ifdef CURLVERBOSE
     const char *strerr = NULL;
 
     if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) {
@@ -268,6 +274,7 @@
       strerr = gnutls_strerror(rc);
 
     infof(data, "gnutls_handshake() warning: %s", strerr);
+#endif
     return CURLE_AGAIN;
   }
   else if((rc < 0) && backend->gtls.io_result) {
@@ -304,7 +311,7 @@
 
 #define GNUTLS_CIPHERS "NORMAL:%PROFILE_MEDIUM:-ARCFOUR-128:" \
   "-CTYPE-ALL:+CTYPE-X509"
-/* If GnuTLS was compiled without support for SRP it will error out if SRP is
+/* If GnuTLS was compiled without support for SRP it errors out if SRP is
    requested in the priority string, so treat it specially
  */
 #define GNUTLS_SRP "+SRP"
@@ -710,7 +717,7 @@
 
   /* get the session ID data size */
   gnutls_session_get_data(session, NULL, &sdata_len);
-  if(!sdata_len) /* gnutls does this for some version combinations */
+  if(!sdata_len) /* GnuTLS does this for some version combinations */
     return CURLE_OK;
 
   sdata = curlx_malloc(sdata_len); /* get a buffer for it */
@@ -786,7 +793,7 @@
   if(when) { /* after message has been processed */
     struct Curl_easy *data = CF_DATA_CURRENT(cf);
     if(data) {
-      CURL_TRC_CF(data, cf, "handshake: %s message type %d",
+      CURL_TRC_CF(data, cf, "handshake: %s message type %u",
                   incoming ? "incoming" : "outgoing", htype);
       switch(htype) {
       case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: {
@@ -818,7 +825,7 @@
 #ifdef USE_GNUTLS_SRP
   if(conn_config->username) {
     /* Only add SRP to the cipher list if SRP is requested. Otherwise
-     * GnuTLS will disable TLS 1.3 support. */
+     * GnuTLS disables TLS 1.3 support. */
     result = curlx_dyn_add(&buf, priority);
     if(!result)
       result = curlx_dyn_add(&buf, ":" GNUTLS_SRP);
@@ -1116,7 +1123,7 @@
   Curl_alpn_copy(&alpns, alpns_requested);
 
   /* This might be a reconnect, so we check for a session ID in the cache
-     to speed up things. We need to do this before constructing the gnutls
+     to speed up things. We need to do this before constructing the GnuTLS
      session since we need to set flags depending on the kind of reuse. */
   if(conn_config->cache_session && !conn_config->verifystatus) {
     result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs);
@@ -1138,7 +1145,8 @@
       else {
         infof(data, "SSL reusing session with ALPN '%s'",
               scs->alpn ? scs->alpn : "-");
-        if(ssl_config->earlydata && scs->alpn && !cf->conn->connect_only) {
+        if(ssl_config->earlydata && scs->alpn &&
+           !cf->conn->bits.connect_only) {
           bool do_early_data = FALSE;
           if(sess_reuse_cb) {
             result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data);
@@ -1178,7 +1186,7 @@
 #endif
 
   /* convert the ALPN string from our arguments to a list of strings that
-   * gnutls wants and will convert internally back to this string for sending
+   * GnuTLS wants and does convert internally back to this string for sending
    * to the server. nice. */
   if(!gtls_alpns_count && alpns.count) {
     size_t i;
@@ -1306,7 +1314,7 @@
   if(key)
     gnutls_pubkey_deinit(key);
 
-  Curl_safefree(buff1);
+  curlx_safefree(buff1);
 
   return result;
 }
@@ -1579,7 +1587,7 @@
   long * const certverifyresult = &ssl_config->certverifyresult;
 
   (void)cf;
-  /* This function will return the peer's raw certificate (chain) as sent by
+  /* This function returns the peer's raw certificate (chain) as sent by
      the peer. These certificates are in raw format (DER encoded for
      X.509). In case of a X.509 then a certificate list may be present. The
      first certificate in the list is the peer's certificate, following the
@@ -1611,7 +1619,7 @@
 
   if(data->set.ssl.certinfo && chain.certs) {
     if(chain.num_certs > MAX_ALLOWED_CERT_AMOUNT) {
-      failf(data, "%u certificates is more than allowed (%u)",
+      failf(data, "%u certificates is more than allowed (%d)",
             chain.num_certs, MAX_ALLOWED_CERT_AMOUNT);
       result = CURLE_SSL_CONNECT_ERROR;
       goto out;
@@ -1637,7 +1645,7 @@
   if(config->verifypeer) {
     bool verified = FALSE;
     unsigned int verify_status = 0;
-    /* This function will try to verify the peer's certificate and return
+    /* This function tries to verify the peer's certificate and return
        its status (trusted, invalid etc.). The value of status should be
        one or more of the gnutls_certificate_status_t enumerated elements
        bitwise or'd. To avoid denial of service attacks some default
@@ -1693,7 +1701,7 @@
 
   /* initialize an X.509 certificate structure. */
   if(gnutls_x509_crt_init(&x509_cert)) {
-    failf(data, "failed to init gnutls x509_crt");
+    failf(data, "failed to init GnuTLS x509_crt");
     *certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND;
     result = CURLE_SSL_CONNECT_ERROR;
     goto out;
@@ -1777,7 +1785,7 @@
   if(config->issuercert) {
     gnutls_datum_t issuerp;
     if(gnutls_x509_crt_init(&x509_issuer)) {
-      failf(data, "failed to init gnutls x509_crt for issuer");
+      failf(data, "failed to init GnuTLS x509_crt for issuer");
       result = CURLE_SSL_ISSUER_ERROR;
       goto out;
     }
@@ -1796,7 +1804,7 @@
           config->issuercert ? config->issuercert : "none");
   }
 
-  /* This function will check if the given certificate's subject matches the
+  /* This function checks if the given certificate's subject matches the
      given hostname. This is a basic implementation of the matching described
      in RFC2818 (HTTPS), which takes into account wildcards, and the subject
      alternative name PKIX extension. Returns non zero on success, and zero on
@@ -1890,7 +1898,7 @@
       goto out;
     }
     else if(!n) {
-      /* gnutls is buggy, it *SHOULD* return the amount of bytes it took in.
+      /* GnuTLS is buggy, it *SHOULD* return the amount of bytes it took in.
        * Instead it returns 0 if everything was written. */
       n = (ssize_t)blen;
     }
diff --git a/Utilities/cmcurl/lib/vtls/gtls.h b/Utilities/cmcurl/lib/vtls/gtls.h
index a5e55cd..49106dd 100644
--- a/Utilities/cmcurl/lib/vtls/gtls.h
+++ b/Utilities/cmcurl/lib/vtls/gtls.h
@@ -89,7 +89,7 @@
                             struct Curl_cfilter *cf,
                             struct Curl_easy *data,
                             struct ssl_peer *peer,
-                            const struct alpn_spec *alpns,
+                            const struct alpn_spec *alpns_requested,
                             Curl_gtls_ctx_setup_cb *cb_setup,
                             void *cb_user_data,
                             void *ssl_user_data,
diff --git a/Utilities/cmcurl/lib/vtls/keylog.c b/Utilities/cmcurl/lib/vtls/keylog.c
index 0206e32..4ae2387 100644
--- a/Utilities/cmcurl/lib/vtls/keylog.c
+++ b/Utilities/cmcurl/lib/vtls/keylog.c
@@ -22,27 +22,23 @@
  *
  ***************************************************************************/
 #include "curl_setup.h"
+#include "vtls/keylog.h"
 
-#if defined(USE_OPENSSL) || \
-  defined(USE_GNUTLS) || \
-  defined(USE_WOLFSSL) || \
-  (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \
-  defined(USE_QUICHE) || \
+#if defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \
   defined(USE_RUSTLS)
 
-#include "vtls/keylog.h"
 #include "escape.h"
 #include "curlx/fopen.h"
 
 /* The fp for the open SSLKEYLOGFILE, or NULL if not open */
 static FILE *keylog_file_fp;
+/* Used for verbose logging */
+static char *keylog_file_name;
 
 void Curl_tls_keylog_open(void)
 {
-  char *keylog_file_name;
-
   if(!keylog_file_fp) {
-    keylog_file_name = curl_getenv("SSLKEYLOGFILE");
+    keylog_file_name = getenv("SSLKEYLOGFILE");
     if(keylog_file_name) {
       keylog_file_fp = curlx_fopen(keylog_file_name, FOPEN_APPENDTEXT);
       if(keylog_file_fp) {
@@ -56,7 +52,6 @@
           keylog_file_fp = NULL;
         }
       }
-      Curl_safefree(keylog_file_name);
     }
   }
 }
@@ -74,6 +69,11 @@
   return keylog_file_fp != NULL;
 }
 
+const char *Curl_tls_keylog_file_name(void)
+{
+  return keylog_file_name;
+}
+
 bool Curl_tls_keylog_write_line(const char *line)
 {
   /* The current maximum valid keylog line length LF and NUL is 195. */
@@ -145,4 +145,16 @@
   return TRUE;
 }
 
-#endif /* TLS or QUIC backend */
+#else /* TLS backend */
+
+bool Curl_tls_keylog_enabled(void)
+{
+  return FALSE;
+}
+
+const char *Curl_tls_keylog_file_name(void)
+{
+  return NULL;
+}
+
+#endif  /* TLS backend */
diff --git a/Utilities/cmcurl/lib/vtls/keylog.h b/Utilities/cmcurl/lib/vtls/keylog.h
index 7e741b9..68ded47 100644
--- a/Utilities/cmcurl/lib/vtls/keylog.h
+++ b/Utilities/cmcurl/lib/vtls/keylog.h
@@ -53,12 +53,17 @@
 bool Curl_tls_keylog_enabled(void);
 
 /*
+ * Returns a pointer to the filename keys are being written to, if enabled.
+ */
+const char *Curl_tls_keylog_file_name(void);
+
+/*
  * Appends a key log file entry.
  * Returns true iff the key log file is open and a valid entry was provided.
  */
 bool Curl_tls_keylog_write(const char *label,
-                           const unsigned char client_random[32],
-                           const unsigned char *secret, size_t secretlen);
+                         const unsigned char client_random[CLIENT_RANDOM_SIZE],
+                         const unsigned char *secret, size_t secretlen);
 
 /*
  * Appends a line to the key log file, ensure it is terminated by an LF.
diff --git a/Utilities/cmcurl/lib/vtls/mbedtls.c b/Utilities/cmcurl/lib/vtls/mbedtls.c
index 2bac406..e282266 100644
--- a/Utilities/cmcurl/lib/vtls/mbedtls.c
+++ b/Utilities/cmcurl/lib/vtls/mbedtls.c
@@ -265,9 +265,11 @@
 {
   uint16_t id = Curl_cipher_suite_walk_str(str, end);
   size_t len = *end - *str;
+  static const char ecjpake_suite[] = "TLS_ECJPAKE_WITH_AES_128_CCM_8";
 
   if(!id) {
-    if(curl_strnequal("TLS_ECJPAKE_WITH_AES_128_CCM_8", *str, len))
+    if((len == sizeof(ecjpake_suite) - 1) &&
+       curl_strnequal(ecjpake_suite, *str, len))
       id = MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8;
   }
   return id;
@@ -423,7 +425,7 @@
     cert_count++;
 
   if(cert_count > MAX_ALLOWED_CERT_AMOUNT) {
-    infof(data, "Certificates is more than allowed (%u), skipping certinfo",
+    infof(data, "More certificates than allowed (%d), skipping certinfo",
           MAX_ALLOWED_CERT_AMOUNT);
     return;
   }
@@ -517,8 +519,8 @@
       curlx_free(newblob);
     }
 #else
-    /* DER encoded certs do not need to be null terminated because it is a
-       binary format. So if we are not compiling with PEM_PARSE we can avoid
+    /* DER encoded certs do not need to be null-terminated because it is a
+       binary format. Thus, if we are not compiling with PEM_PARSE we can avoid
        the extra memory copies altogether. */
     ret = mbedtls_x509_crt_parse_der(&backend->cacert, ca_info_blob->data,
                                      ca_info_blob->len);
@@ -630,8 +632,8 @@
       curlx_free(newblob);
     }
 #else
-    /* DER encoded certs do not need to be null terminated because it is a
-       binary format. So if we are not compiling with PEM_PARSE we can avoid
+    /* DER encoded certs do not need to be null-terminated because it is a
+       binary format. Thus, if we are not compiling with PEM_PARSE we can avoid
        the extra memory copies altogether. */
     ret = mbedtls_x509_crt_parse_der(&backend->clicert, ssl_cert_blob->data,
                                      ssl_cert_blob->len);
@@ -932,8 +934,8 @@
   if(mbedtls_ssl_set_hostname(&backend->ssl, connssl->peer.sni ?
                               connssl->peer.sni : connssl->peer.hostname)) {
     /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and
-       the name to set in the SNI extension. So even if curl connects to a
-       host specified as an IP address, this function must be used. */
+       the name to set in the SNI extension. Thus even if curl connects to
+       a host specified as an IP address, this function must be used. */
     failf(data, "Failed to set SNI");
     return CURLE_SSL_CONNECT_ERROR;
   }
@@ -1210,7 +1212,7 @@
   connssl->io_need = CURL_SSL_IO_NEED_NONE;
   /* mbedTLS is picky when a mbedtls_ssl_write() was previously blocked.
    * It requires to be called with the same amount of bytes again, or it
-   * will lose bytes, e.g. reporting all was sent but they were not.
+   * loses bytes, e.g. reporting all was sent but they were not.
    * Remember the blocked length and use that when set. */
   if(backend->send_blocked) {
     DEBUGASSERT(backend->send_blocked_len <= len);
@@ -1353,15 +1355,15 @@
 
   (void)data;
   DEBUGASSERT(backend);
-  if(backend->initialized) {
-    mbedtls_pk_free(&backend->pk);
-    mbedtls_x509_crt_free(&backend->clicert);
-    mbedtls_x509_crt_free(&backend->cacert);
+  mbedtls_pk_free(&backend->pk);
+  mbedtls_x509_crt_free(&backend->clicert);
+  mbedtls_x509_crt_free(&backend->cacert);
 #ifdef MBEDTLS_X509_CRL_PARSE_C
-    mbedtls_x509_crl_free(&backend->crl);
+  mbedtls_x509_crl_free(&backend->crl);
 #endif
-    Curl_safefree(backend->ciphersuites);
-    mbedtls_ssl_config_free(&backend->config);
+  curlx_safefree(backend->ciphersuites);
+  mbedtls_ssl_config_free(&backend->config);
+  if(backend->initialized) {
     mbedtls_ssl_free(&backend->ssl);
     backend->initialized = FALSE;
   }
@@ -1511,11 +1513,9 @@
   ret = mbedtls_ctr_drbg_seed(&rng.drbg, mbedtls_entropy_func, &rng.entropy,
                               NULL, 0);
 
-  if(ret) {
-    failf(NULL, "failed: mbedtls_ctr_drbg_seed returned -0x%x",
-          (unsigned int)-ret);
+  if(ret)
+    /* mbedtls_ctr_drbg_seed returned error */
     return 0;
-  }
 
   /* To prevent an adversary from reading your random data,
      you can enable prediction resistance.
diff --git a/Utilities/cmcurl/lib/vtls/openssl.c b/Utilities/cmcurl/lib/vtls/openssl.c
index 50bf1e0..4629ca4 100644
--- a/Utilities/cmcurl/lib/vtls/openssl.c
+++ b/Utilities/cmcurl/lib/vtls/openssl.c
@@ -27,40 +27,7 @@
  */
 #include "curl_setup.h"
 
-#if defined(USE_QUICHE) || defined(USE_OPENSSL)
-
-#ifdef USE_WIN32_CRYPTO
-#include <wincrypt.h>
-/* If <wincrypt.h> is included directly, or indirectly via <schannel.h>,
- * <winldap.h>, <iphlpapi.h>, or something else, <wincrypt.h> does this:
- *   #define X509_NAME  ((LPCSTR)7)
- *
- * And in BoringSSL/AWC-LC's <openssl/base.h> there is:
- *  typedef struct X509_name_st X509_NAME;
- *  etc.
- *
- * The redefined symbols break these OpenSSL headers when included after
- * <wincrypt.h>.
- * The workaround is to undefine those defines here (and only here).
- *
- * For unity builds it may need to be repeated elsewhere too, e.g. in ldap.c,
- * to apply to other sources using OpenSSL includes. Each compilation unit
- * needs undefine them between the first <wincrypt.h> include and the first
- * OpenSSL include.
- *
- * OpenSSL does this in <openssl/ssl.h> and <openssl/x509v3.h>, but it
- * also does the #undef by including <openssl/ossl_typ.h>. <3.1.0 only does
- * it on the first include.
- *
- * LibreSSL automatically undefines these symbols before using them.
- */
-#undef X509_NAME
-#undef X509_EXTENSIONS
-#undef PKCS7_ISSUER_AND_SERIAL
-#undef PKCS7_SIGNER_INFO
-#undef OCSP_REQUEST
-#undef OCSP_RESPONSE
-#endif
+#ifdef USE_OPENSSL
 
 #include "urldata.h"
 #include "curl_trc.h"
@@ -70,6 +37,7 @@
 #include "curlx/inet_pton.h"
 #include "vtls/openssl.h"
 #include "connect.h"
+#include "cf-dns.h"
 #include "progress.h"
 #include "vtls/vtls.h"
 #include "vtls/vtls_int.h"
@@ -84,6 +52,9 @@
 #include "curlx/strcopy.h"
 #include "curlx/strdup.h"
 #include "vtls/apple.h"
+#ifdef USE_ECH
+#include "curlx/base64.h"
+#endif
 
 #include <openssl/rand.h>
 #include <openssl/x509v3.h>
@@ -308,7 +279,7 @@
 #else
   RSA_get0_key(rsa, &n, &e, NULL);
 #endif /* HAVE_EVP_PKEY_GET_PARAMS */
-  BIO_printf(mem, "%d", n ? BN_num_bits(n) : 0);
+  BIO_printf(mem, "%d", (int)(n ? BN_num_bits(n) : 0));
   result = push_certinfo(data, mem, "RSA Public Key", i);
   if(!result) {
     result = print_pubkey_BN(rsa, n, i);
@@ -413,7 +384,7 @@
 
   numcerts = sk_X509_num(sk);
   if(numcerts > MAX_ALLOWED_CERT_AMOUNT) {
-    failf(data, "%d certificates is more than allowed (%u)", (int)numcerts,
+    failf(data, "%d certificates is more than allowed (%d)", (int)numcerts,
           MAX_ALLOWED_CERT_AMOUNT);
     return CURLE_SSL_CONNECT_ERROR;
   }
@@ -544,10 +515,6 @@
   return result;
 }
 
-#endif /* USE_QUICHE || USE_OPENSSL */
-
-#ifdef USE_OPENSSL
-
 static int ossl_bio_cf_create(BIO *bio)
 {
   BIO_set_shutdown(bio, 1);
@@ -658,7 +625,7 @@
   }
 
   /* Before returning server replies to the SSL instance, we need
-   * to have setup the x509 store or verification will fail. */
+   * to have setup the x509 store or verification fails. */
   if(!octx->x509_store_setup) {
     r2 = Curl_ssl_setup_x509_store(cf, data, octx);
     if(r2) {
@@ -1783,7 +1750,7 @@
   }
   OSSL_LIB_CTX_free(data->state.libctx);
   data->state.libctx = NULL;
-  Curl_safefree(data->state.propq);
+  curlx_safefree(data->state.propq);
   data->state.provider_loaded = FALSE;
 }
 
@@ -2138,9 +2105,9 @@
         case GEN_DNS: /* name/pattern comparison */
           /* The OpenSSL man page explicitly says: "In general it cannot be
              assumed that the data returned by ASN1_STRING_data() is null
-             terminated or does not contain embedded nulls." But also that
-             "The actual format of the data will depend on the actual string
-             type itself: for example for an IA5String the data will be ASCII"
+             terminated or does not contain embedded nulls.", but also that
+             "The actual format of the data depends on the actual string
+             type itself: for example for an IA5String the data is ASCII"
 
              It has been however verified that in 0.9.6 and 0.9.7, IA5String
              is always null-terminated.
@@ -2198,7 +2165,7 @@
         i = j;
     }
 
-    /* we have the name entry and we will now convert this to a string
+    /* we have the name entry and we now convert this to a string
        that we can use for comparison. Doing this we support BMPstring,
        UTF8, etc. */
 
@@ -2405,31 +2372,6 @@
 
 static const char *ssl_msg_type(int ssl_ver, int msg)
 {
-#ifdef SSL2_VERSION_MAJOR  /* OpenSSL 1.0.2, LibreSSL <=3.9.2 */
-  if(ssl_ver == SSL2_VERSION_MAJOR) {
-    switch(msg) {
-    case SSL2_MT_ERROR:
-      return "Error";
-    case SSL2_MT_CLIENT_HELLO:
-      return "Client hello";
-    case SSL2_MT_CLIENT_MASTER_KEY:
-      return "Client key";
-    case SSL2_MT_CLIENT_FINISHED:
-      return "Client finished";
-    case SSL2_MT_SERVER_HELLO:
-      return "Server hello";
-    case SSL2_MT_SERVER_VERIFY:
-      return "Server verify";
-    case SSL2_MT_SERVER_FINISHED:
-      return "Server finished";
-    case SSL2_MT_REQUEST_CERTIFICATE:
-      return "Request CERT";
-    case SSL2_MT_CLIENT_CERTIFICATE:
-      return "Client CERT";
-    }
-  }
-  else
-#endif
   if(ssl_ver == SSL3_VERSION_MAJOR) {
     switch(msg) {
     case SSL3_MT_HELLO_REQUEST:
@@ -2528,11 +2470,6 @@
     return;
 
   switch(ssl_ver) {
-#ifdef SSL2_VERSION /* removed in recent versions */
-  case SSL2_VERSION:
-    verstr = "SSLv2";
-    break;
-#endif
 #ifdef SSL3_VERSION
   case SSL3_VERSION:
     verstr = "SSLv3";
@@ -2583,7 +2520,7 @@
     ssl_ver >>= 8; /* check the upper 8 bits only below */
 
     /* SSLv2 does not seem to have TLS record-type headers, so OpenSSL
-     * always pass-up content-type as 0. But the interesting message-type
+     * always pass-up content-type as 0, but the interesting message-type
      * is at 'buf[0]'.
      */
     if(ssl_ver == SSL3_VERSION_MAJOR && content_type)
@@ -2678,7 +2615,7 @@
   case CURL_SSLVERSION_MAX_DEFAULT:  /* max selected */
   default:
     /* SSL_CTX_set_max_proto_version states that: setting the maximum to 0
-       will enable protocol versions up to the highest version supported by
+       enables protocol versions up to the highest version supported by
        the library */
     ossl_ssl_version_max = 0;
     break;
@@ -2850,6 +2787,7 @@
 
 #ifdef USE_WIN32_CRYPTO
 static CURLcode ossl_win_load_store(struct Curl_easy *data,
+                                    struct Curl_cfilter *cf,
                                     const char *win_store,
                                     X509_STORE *store,
                                     bool *padded)
@@ -2862,11 +2800,13 @@
   hStore = CertOpenSystemStoreA(0, win_store);
   if(hStore) {
     PCCERT_CONTEXT pContext = NULL;
-    /* The array of enhanced key usage OIDs will vary per certificate and
+    /* The array of enhanced key usage OIDs varies per certificate and
        is declared outside of the loop so that rather than malloc/free each
        iteration we can grow it with realloc, when necessary. */
     CERT_ENHKEY_USAGE *enhkey_usage = NULL;
     DWORD enhkey_usage_size = 0;
+    VERBOSE(size_t total = 0);
+    VERBOSE(size_t imported = 0);
 
     /* This loop makes a best effort to import all valid certificates from
        the MS root store. If a certificate cannot be imported it is
@@ -2883,8 +2823,10 @@
       if(!pContext)
         break;
 
+      VERBOSE(++total);
+
 #if defined(DEBUGBUILD) && defined(CURLVERBOSE)
-      else {
+      {
         char cert_name[256];
         if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
                                NULL, cert_name, sizeof(cert_name)))
@@ -2972,6 +2914,7 @@
          such as duplicate certificate, which is allowed by MS but not
          OpenSSL. */
       if(X509_STORE_add_cert(store, x509) == 1) {
+        VERBOSE(++imported);
 #ifdef DEBUGBUILD
         infof(data, "SSL: Imported cert");
 #endif
@@ -2984,6 +2927,10 @@
     CertFreeCertificateContext(pContext);
     CertCloseStore(hStore, 0);
 
+    CURL_TRC_CF(data, cf,
+                "ossl_win_load_store() found: %zu imported: %zu certs in %s.",
+                total, imported, win_store);
+
     if(result)
       return result;
   }
@@ -2999,7 +2946,7 @@
   /* Import certificates from the Windows root certificate store if
      requested.
      https://stackoverflow.com/questions/9507184/
-     https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
+     https://github.com/d3x0r/SACK/blob/ff15424d3c581b86d40f818532e5a400c516d39d/src/netlib/ssl_layer.c#L1410
      https://datatracker.ietf.org/doc/html/rfc5280 */
   const char *win_stores[] = {
     "ROOT",   /* Trusted Root Certification Authorities */
@@ -3011,7 +2958,7 @@
   *padded = FALSE;
   for(i = 0; i < CURL_ARRAYSIZE(win_stores); ++i) {
     bool store_added = FALSE;
-    result = ossl_win_load_store(data, win_stores[i], store, &store_added);
+    result = ossl_win_load_store(data, cf, win_stores[i], store, &store_added);
     if(result)
       return result;
     if(store_added) {
@@ -3126,7 +3073,7 @@
 
 #ifdef CURL_CA_FALLBACK
   if(octx->store_is_empty) {
-    /* verifying the peer without any CA certificates will not
+    /* verifying the peer without any CA certificates does not
        work so use OpenSSL's built-in default as fallback */
     X509_STORE_set_default_paths(store);
     infof(data, "  OpenSSL default paths (fallback)");
@@ -3428,7 +3375,7 @@
 #ifdef HAVE_OPENSSL_EARLYDATA
             if(ssl_config->earlydata && scs->alpn &&
                SSL_SESSION_get_max_early_data(ssl_session) &&
-               !cf->conn->connect_only &&
+               !cf->conn->bits.connect_only &&
                (SSL_version(octx->ssl) == TLS1_3_VERSION)) {
               bool do_early_data = FALSE;
               if(sess_reuse_cb) {
@@ -3477,6 +3424,16 @@
 }
 
 #ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST
+bool Curl_ossl_need_httpsrr(struct Curl_easy *data)
+{
+  if(!CURLECH_ENABLED(data))
+    return FALSE;
+  if((data->set.tls_ech & CURLECH_GREASE) ||
+     (data->set.tls_ech & CURLECH_CLA_CFG))
+   return FALSE;
+  return TRUE;
+}
+
 static CURLcode ossl_init_ech(struct ossl_ctx *octx,
                               struct Curl_cfilter *cf,
                               struct Curl_easy *data,
@@ -3486,9 +3443,9 @@
   size_t ech_config_len = 0;
   char *outername = data->set.str[STRING_ECH_PUBLIC];
   int trying_ech_now = 0;
-  CURLcode result;
+  CURLcode result = CURLE_OK;
 
-  if(!ECH_ENABLED(data))
+  if(!CURLECH_ENABLED(data))
     return CURLE_OK;
 
   if(data->set.tls_ech & CURLECH_GREASE) {
@@ -3542,44 +3499,32 @@
     infof(data, "ECH: ECHConfig from command line");
   }
   else {
-    struct Curl_dns_entry *dns = NULL;
+    const struct Curl_https_rrinfo *rinfo =
+      Curl_conn_dns_get_https(data, cf->sockindex);
 
-    if(peer->hostname)
-      dns = Curl_dnscache_get(data, peer->hostname, peer->port,
-                              cf->conn->ip_version);
-    if(!dns) {
-      infof(data, "ECH: requested but no DNS info available");
-      if(data->set.tls_ech & CURLECH_HARD)
-        return CURLE_SSL_CONNECT_ERROR;
-    }
-    else {
-      struct Curl_https_rrinfo *rinfo = NULL;
+    if(rinfo && rinfo->echconfiglist) {
+      const unsigned char *ecl = rinfo->echconfiglist;
+      size_t elen = rinfo->echconfiglist_len;
 
-      rinfo = dns->hinfo;
-      if(rinfo && rinfo->echconfiglist) {
-        unsigned char *ecl = rinfo->echconfiglist;
-        size_t elen = rinfo->echconfiglist_len;
-
-        infof(data, "ECH: ECHConfig from DoH HTTPS RR");
-        if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) {
-          infof(data, "ECH: SSL_set1_ech_config_list failed");
-          if(data->set.tls_ech & CURLECH_HARD)
-            return CURLE_SSL_CONNECT_ERROR;
-        }
-        else {
-          trying_ech_now = 1;
-          infof(data, "ECH: imported ECHConfigList of length %zu", elen);
-        }
-      }
-      else {
-        infof(data, "ECH: requested but no ECHConfig available");
+      infof(data, "ECH: ECHConfig from HTTPS RR");
+      if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) {
+        infof(data, "ECH: SSL_set1_ech_config_list failed");
         if(data->set.tls_ech & CURLECH_HARD)
           return CURLE_SSL_CONNECT_ERROR;
       }
-      Curl_resolv_unlink(data, &dns);
+      else {
+        trying_ech_now = 1;
+        infof(data, "ECH: imported ECHConfigList of length %zu", elen);
+      }
+    }
+    else {
+      infof(data, "ECH: requested but no ECHConfig available");
+      if(data->set.tls_ech & CURLECH_HARD)
+        return CURLE_SSL_CONNECT_ERROR;
     }
   }
 #ifdef HAVE_BORINGSSL_LIKE
+  (void)peer;
   if(trying_ech_now && outername) {
     infof(data, "ECH: setting public_name not supported with BoringSSL");
     return CURLE_SSL_CONNECT_ERROR;
@@ -3605,7 +3550,13 @@
 
   return CURLE_OK;
 }
-#endif /* HAVE_SSL_SET1_ECH_CONFIG_LIST */
+#else /* HAVE_SSL_SET1_ECH_CONFIG_LIST */
+bool Curl_ossl_need_httpsrr(struct Curl_easy *data)
+{
+  (void)data;
+  return FALSE;
+}
+#endif /* else HAVE_SSL_SET1_ECH_CONFIG_LIST */
 
 static CURLcode ossl_init_ssl(struct ossl_ctx *octx,
                               struct Curl_cfilter *cf,
@@ -3672,7 +3623,7 @@
     case CURL_SSLVERSION_TLSv1_1:
     case CURL_SSLVERSION_TLSv1_2:
     case CURL_SSLVERSION_TLSv1_3:
-      /* it will be handled later with the context options */
+      /* it is handled later with the context options */
       *pmethod = TLS_client_method();
       break;
     case CURL_SSLVERSION_SSLv2:
@@ -4077,7 +4028,7 @@
 #ifdef HAVE_SSL_SET0_WBIO
   /* with OpenSSL v1.1.1 we get an alternative to SSL_set_bio() that works
    * without backward compat quirks. Every call takes one reference, so we
-   * up it and pass. SSL* then owns it and will free.
+   * up it and pass. SSL* then owns and frees it.
    * We check on the function in configure, since LibreSSL and friends
    * each have their own versions to add support for this. */
   BIO_up_ref(bio);
@@ -4100,8 +4051,8 @@
 
 #ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST
 /* If we have retry configs, then trace those out */
-static void ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL *ssl,
-                                         int reason)
+static int ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL *ssl,
+                                        int reason)
 {
   CURLcode result = CURLE_OK;
   size_t rcl = 0;
@@ -4117,10 +4068,11 @@
   size_t out_name_len = 0;
   int servername_type = 0;
 #endif
+  NOVERBOSE((void)reason);
 
   /* nothing to trace if not doing ECH */
-  if(!ECH_ENABLED(data))
-    return;
+  if(!CURLECH_ENABLED(data))
+    return rv;
 #ifndef HAVE_BORINGSSL_LIKE
   rv = SSL_ech_get1_retry_config(ssl, &rcs, &rcl);
 #else
@@ -4153,9 +4105,11 @@
   else
     infof(data, "ECH: no retry_configs (rv = %d)", rv);
 #ifndef HAVE_BORINGSSL_LIKE
-  OPENSSL_free((void *)rcs);
+  OPENSSL_free(inner);
+  OPENSSL_free(rcs);
+  OPENSSL_free(outer);
 #endif
-  return;
+  return rv;
 }
 
 #endif
@@ -4244,8 +4198,12 @@
       reason = ERR_GET_REASON(errdetail);
 
       if((lib == ERR_LIB_SSL) &&
-         ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) ||
-          (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) {
+         ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED)
+/* Missing from OpenSSL 4+ OPENSSL_NO_DEPRECATED_3_0 builds */
+#ifdef SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED
+          || (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED)
+#endif
+         )) {
         result = CURLE_PEER_FAILED_VERIFICATION;
 
         lerr = SSL_get_verify_result(octx->ssl);
@@ -4317,51 +4275,52 @@
     Curl_ossl_report_handshake(data, octx);
 
 #if defined(HAVE_SSL_SET1_ECH_CONFIG_LIST) && !defined(HAVE_BORINGSSL_LIKE)
-    if(ECH_ENABLED(data)) {
+    if(CURLECH_ENABLED(data)) {
       char *inner = NULL, *outer = NULL;
-      const char *status = NULL;
       int rv;
+      VERBOSE(const char *status);
 
       rv = SSL_ech_get1_status(octx->ssl, &inner, &outer);
       switch(rv) {
       case SSL_ECH_STATUS_SUCCESS:
-        status = "succeeded";
+        VERBOSE(status = "succeeded");
         break;
       case SSL_ECH_STATUS_GREASE_ECH:
-        status = "sent GREASE, got retry-configs";
+        VERBOSE(status = "sent GREASE, got retry-configs");
         break;
       case SSL_ECH_STATUS_GREASE:
-        status = "sent GREASE";
+        VERBOSE(status = "sent GREASE");
         break;
       case SSL_ECH_STATUS_NOT_TRIED:
-        status = "not attempted";
+        VERBOSE(status = "not attempted");
         break;
       case SSL_ECH_STATUS_NOT_CONFIGURED:
-        status = "not configured";
+        VERBOSE(status = "not configured");
         break;
       case SSL_ECH_STATUS_BACKEND:
-        status = "backend (unexpected)";
+        VERBOSE(status = "backend (unexpected)");
         break;
       case SSL_ECH_STATUS_FAILED:
-        status = "failed";
+        VERBOSE(status = "failed");
         break;
       case SSL_ECH_STATUS_BAD_CALL:
-        status = "bad call (unexpected)";
+        VERBOSE(status = "bad call (unexpected)");
         break;
       case SSL_ECH_STATUS_BAD_NAME: {
         struct ssl_primary_config *conn_config =
           Curl_ssl_cf_get_primary_config(cf);
         if(!conn_config->verifypeer && !conn_config->verifyhost &&
            inner && !strcmp(inner, connssl->peer.hostname)) {
-          status = "bad name (tolerated without peer verification)";
+          VERBOSE(status = "bad name (tolerated without peer verification)");
           rv = SSL_ECH_STATUS_SUCCESS;
         }
-        else
-          status = "bad name (unexpected)";
+        else {
+          VERBOSE(status = "bad name (unexpected)");
+        }
         break;
       }
       default:
-        status = "unexpected status";
+        VERBOSE(status = "unexpected status");
         infof(data, "ECH: unexpected status %d", rv);
       }
       infof(data, "ECH: result: status is %s, inner is %s, outer is %s",
@@ -4437,7 +4396,7 @@
     /*
      * These checks are verifying we got back the same values as when we
      * sized the buffer. it is pretty weak since they should always be the
-     * same. But it gives us something to test.
+     * same, but it gives us something to test.
      */
     if((len1 != len2) || !temp || ((temp - buff1) != len1))
       break; /* failed */
@@ -4744,16 +4703,26 @@
     unsigned char *ocsp_data = NULL;
 #endif
     long ocsp_len = 0;
+    bool ocsp_missing = FALSE;
     if(conn_config->verifystatus && !octx->reused_session)
       ocsp_len = (long)SSL_get_tlsext_status_ocsp_resp(octx->ssl, &ocsp_data);
 
     /* SSL_get_tlsext_status_ocsp_resp() returns the length of the OCSP
        response data or -1 if there is no OCSP response data. */
-    if(ocsp_len < 0)
+    if(ocsp_len < 0) {
       ocsp_len = 0; /* no data available */
+      ocsp_missing = TRUE;
+    }
     result = Curl_vtls_apple_verify(cf, data, peer, chain.num_certs,
                                     ossl_chain_get_der, &chain,
                                     ocsp_data, ocsp_len);
+    if(!result && ocsp_missing && conn_config->verifystatus &&
+       !octx->reused_session) {
+      /* verified, but OCSP stapling is required and server sent none */
+      *pverified = TRUE;
+      failf(data, "No OCSP response received");
+      return CURLE_SSL_INVALIDCERTSTATUS;
+    }
   }
   *pverified = !result;
   return result;
@@ -4992,6 +4961,11 @@
   connssl->io_need = CURL_SSL_IO_NEED_NONE;
 
   if(ssl_connect_1 == connssl->connecting_state) {
+    if(Curl_ossl_need_httpsrr(data) &&
+       !Curl_conn_dns_resolved_https(data, cf->sockindex)) {
+      CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect");
+      return CURLE_OK;
+    }
     CURL_TRC_CF(data, cf, "ossl_connect, step1");
     result = ossl_connect_step1(cf, data);
     if(result)
@@ -5085,7 +5059,7 @@
   memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
   if(octx->blocked_ssl_write_len && (octx->blocked_ssl_write_len != memlen)) {
     /* The previous SSL_write() call was blocked, using that length.
-     * We need to use that again or OpenSSL will freak out. A shorter
+     * We need to use that again or OpenSSL freaks out. A shorter
      * length should not happen and is a bug in libcurl. */
     if(octx->blocked_ssl_write_len > memlen) {
       DEBUGASSERT(0);
@@ -5237,7 +5211,7 @@
           result = CURLE_RECV_ERROR;
         }
         else {
-          /* We should no longer get here nowadays. But handle
+          /* We should no longer get here nowadays, but handle
            * the error in case of some weirdness in the OSSL stack */
           int sockerr = SOCKERRNO;
           if(sockerr)
@@ -5270,13 +5244,15 @@
   return result;
 }
 
-static CURLcode ossl_get_channel_binding(struct Curl_easy *data, int sockindex,
+static CURLcode ossl_get_channel_binding(struct Curl_easy *data,
+                                         int sockindex,
                                          struct dynbuf *binding)
 {
   X509 *cert;
-  int algo_nid;
-  const EVP_MD *algo_type;
-  const char *algo_name;
+  int mdnid;
+  bool no_digest_acceptable = FALSE;
+  const EVP_MD *algo_type = NULL;
+  const char *algo_name = NULL;
   unsigned int length;
   unsigned char buf[EVP_MAX_MD_SIZE];
 
@@ -5308,43 +5284,108 @@
     /* No server certificate, do not do channel binding */
     return CURLE_OK;
 
-  if(!OBJ_find_sigid_algs(X509_get_signature_nid(cert), &algo_nid, NULL)) {
+#ifdef HAVE_OPENSSL3
+  {
+    int pknid, secbits;
+    uint32_t flags;
+    EVP_PKEY *pkey = X509_get0_pubkey(cert);
+
+    if(!X509_get_signature_info(cert, &mdnid, &pknid, &secbits, &flags)) {
+      failf(data, "certificate signature algorithm not recognized");
+      result = CURLE_SSL_INVALIDCERTSTATUS;
+      goto out;
+    }
+
+    if(mdnid != NID_undef) {
+      if(mdnid == NID_md5 || mdnid == NID_sha1) {
+        algo_type = EVP_sha256();
+      }
+      else
+        algo_type = EVP_get_digestbynid(mdnid);
+    }
+    else if(pkey && !EVP_PKEY_is_a(pkey, OBJ_nid2sn(pknid))) {
+      /* The cert's pkey is different from the algorithm used to sign
+       * the certificate. Since the reported `mdnid` is undefined, there
+       * is no digest algorithm available here. This happens in PQC
+       * and is accepted, resulting in no addition to the binding. */
+      no_digest_acceptable = TRUE;
+    }
+    else if(pkey) {
+      /* cert's pkey type is the same as the cert signer (or same family).
+       * Ask for the mandatory/advisory digest algorithm for the pkey.
+       */
+      char mdname[128] = "";
+      int rc = EVP_PKEY_get_default_digest_name(pkey, mdname, sizeof(mdname));
+      bool md_is_undef = !strcmp(mdname, "UNDEF");
+
+      if(rc == 2 && md_is_undef) {
+        /* OpenSSL declares "undef" the *mandatory* digest for this key.
+         * This is some PQC shit, accept it, no addition to binding. */
+        no_digest_acceptable = TRUE;
+      }
+      else if(rc > 0 && mdname[0] != '\0' && !md_is_undef) {
+        infof(data, "Digest algorithm : %s%s (derived from public key)"
+              ", but unavailable",
+              mdname, rc == 2 ? " [mandatory]" : " [advisory]");
+      }
+    }
+  }
+#else /* HAVE_OPENSSL3 */
+
+  if(!OBJ_find_sigid_algs(X509_get_signature_nid(cert), &mdnid, NULL)) {
     failf(data,
           "Unable to find digest NID for certificate signature algorithm");
     result = CURLE_SSL_INVALIDCERTSTATUS;
-    goto error;
+    goto out;
   }
 
   /* https://datatracker.ietf.org/doc/html/rfc5929#section-4.1 */
-  if(algo_nid == NID_md5 || algo_nid == NID_sha1) {
+  if(mdnid == NID_md5 || mdnid == NID_sha1) {
     algo_type = EVP_sha256();
   }
   else {
-    algo_type = EVP_get_digestbynid(algo_nid);
+    algo_type = EVP_get_digestbynid(mdnid);
     if(!algo_type) {
-      algo_name = OBJ_nid2sn(algo_nid);
+      algo_name = OBJ_nid2sn(mdnid);
       failf(data, "Could not find digest algorithm %s (NID %d)",
-            algo_name ? algo_name : "(null)", algo_nid);
+            algo_name ? algo_name : "(null)", mdnid);
       result = CURLE_SSL_INVALIDCERTSTATUS;
-      goto error;
+      goto out;
     }
   }
 
-  if(!X509_digest(cert, algo_type, buf, &length)) {
-    failf(data, "X509_digest() failed");
+#endif /* HAVE_OPENSSL3, else */
+
+  if(!algo_type) {
+    if(no_digest_acceptable) {
+      infof(data, "certificate exposes no signing digest algorithm, "
+            "nothing to add to channel binding");
+      result = CURLE_OK;
+      goto out;
+    }
+    /* unacceptable, something is wrong, fail */
+    algo_name = OBJ_nid2sn(mdnid);
+    failf(data, "Unable to find digest algorithm %s (NID %d) "
+          "for channel binding", algo_name ? algo_name : "(null)", mdnid);
     result = CURLE_SSL_INVALIDCERTSTATUS;
-    goto error;
+    goto out;
+  }
+
+  if(!X509_digest(cert, algo_type, buf, &length)) {
+    failf(data, "X509_digest() failed for channel binding");
+    result = CURLE_SSL_INVALIDCERTSTATUS;
+    goto out;
   }
 
   /* Append "tls-server-end-point:" */
   result = curlx_dyn_addn(binding, prefix, sizeof(prefix) - 1);
   if(result)
-    goto error;
+    goto out;
 
   /* Append digest */
   result = curlx_dyn_addn(binding, buf, length);
 
-error:
+out:
   X509_free(cert);
   return result;
 }
diff --git a/Utilities/cmcurl/lib/vtls/openssl.h b/Utilities/cmcurl/lib/vtls/openssl.h
index cfffe9d..61d4a17 100644
--- a/Utilities/cmcurl/lib/vtls/openssl.h
+++ b/Utilities/cmcurl/lib/vtls/openssl.h
@@ -26,6 +26,40 @@
 #include "curl_setup.h"
 
 #ifdef USE_OPENSSL
+
+#ifdef USE_WIN32_CRYPTO
+#include <wincrypt.h>
+/* If <wincrypt.h> is included directly, or indirectly via <schannel.h>,
+ * <winldap.h>, <iphlpapi.h>, or something else, <wincrypt.h> does this:
+ *   #define X509_NAME  ((LPCSTR)7)
+ *
+ * In BoringSSL/AWC-LC's <openssl/base.h> there is:
+ *  typedef struct X509_name_st X509_NAME;
+ *  etc.
+ *
+ * The redefined symbols break these OpenSSL headers when included after
+ * <wincrypt.h>.
+ * The workaround is to undefine those defines here (and only here).
+ *
+ * For unity builds it may need to be repeated elsewhere too, e.g. in ldap.c,
+ * to apply to other sources using OpenSSL includes. Each compilation unit
+ * needs undefine them between the first <wincrypt.h> include and the first
+ * OpenSSL include.
+ *
+ * OpenSSL does this in <openssl/ssl.h> and <openssl/x509v3.h>, but it
+ * also does the #undef by including <openssl/ossl_typ.h>. <3.1.0 only does
+ * it on the first include.
+ *
+ * LibreSSL automatically undefines these symbols before using them.
+ */
+#undef X509_NAME
+#undef X509_EXTENSIONS
+#undef PKCS7_ISSUER_AND_SERIAL
+#undef PKCS7_SIGNER_INFO
+#undef OCSP_REQUEST
+#undef OCSP_RESPONSE
+#endif /* USE_WIN32_CRYPTO */
+
 /*
  * This header should only be needed to get included by vtls.c, openssl.c
  * and ngtcp2.c
@@ -113,6 +147,9 @@
                             void *ssl_user_data,
                             Curl_ossl_init_session_reuse_cb *sess_reuse_cb);
 
+/* Is a resolved HTTPS-RR needed for initializing OpenSSL? */
+bool Curl_ossl_need_httpsrr(struct Curl_easy *data);
+
 #ifndef HAVE_OPENSSL3
 #define SSL_get1_peer_certificate SSL_get_peer_certificate
 #endif
@@ -121,7 +158,7 @@
 
 /**
  * Setup the OpenSSL X509_STORE in `ssl_ctx` for the cfilter `cf` and
- * easy handle `data`. Will allow reuse of a shared cache if suitable
+ * easy handle `data`. Allows reuse of a shared cache if suitable
  * and configured.
  */
 CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf,
diff --git a/Utilities/cmcurl/lib/vtls/rustls.c b/Utilities/cmcurl/lib/vtls/rustls.c
index 061b444..d886c85 100644
--- a/Utilities/cmcurl/lib/vtls/rustls.c
+++ b/Utilities/cmcurl/lib/vtls/rustls.c
@@ -32,6 +32,7 @@
 #include "curlx/fopen.h"
 #include "curlx/strerr.h"
 #include "urldata.h"
+#include "cf-dns.h"
 #include "curl_trc.h"
 #include "httpsrr.h"
 #include "vtls/vtls.h"
@@ -40,6 +41,9 @@
 #include "vtls/keylog.h"
 #include "vtls/cipher_suite.h"
 #include "vtls/x509asn1.h"
+#ifdef USE_ECH
+#include "curlx/base64.h"
+#endif
 
 struct rustls_ssl_backend_data {
   const struct rustls_client_config *config;
@@ -294,8 +298,8 @@
  *    we get either an error or EAGAIN/EWOULDBLOCK.
  *
  * it is okay to call this function with plainbuf == NULL and plainlen == 0.
- * In that case, it will not read anything into Rustls' plaintext input buffer.
- * It will only drain Rustls' plaintext output buffer into the socket.
+ * In that case, it does not read anything into Rustls' plaintext input buffer.
+ * It only drains Rustls' plaintext output buffer into the socket.
  */
 static CURLcode cr_send(struct Curl_cfilter *cf, struct Curl_easy *data,
                         const void *plainbuf, size_t plainlen,
@@ -369,7 +373,7 @@
     *pnwritten += (ssize_t)plainwritten;
 
 out:
-  CURL_TRC_CF(data, cf, "rustls_send(len=%zu) -> %d, %zd",
+  CURL_TRC_CF(data, cf, "rustls_send(len=%zu) -> %d, %zu",
               plainlen, result, *pnwritten);
   return result;
 }
@@ -570,7 +574,7 @@
   }
 
 #ifdef USE_ECH
-  if(ECH_ENABLED(data)) {
+  if(CURLECH_ENABLED(data)) {
     tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3;
     tls_versions_len = 1;
     infof(data, "rustls: ECH enabled, forcing TLSv1.3");
@@ -906,16 +910,26 @@
 }
 
 #ifdef USE_ECH
+
+static bool cr_ech_need_httpsrr(struct Curl_easy *data)
+{
+  if(!CURLECH_ENABLED(data))
+    return FALSE;
+  if((data->set.tls_ech & CURLECH_GREASE) ||
+     (data->set.tls_ech & CURLECH_CLA_CFG))
+   return FALSE;
+  return TRUE;
+}
+
 static CURLcode
 init_config_builder_ech(struct Curl_easy *data,
-                        const struct ssl_connect_data *connssl,
+                        struct Curl_cfilter *cf,
                         struct rustls_client_config_builder *builder)
 {
   const rustls_hpke *hpke = rustls_supported_hpke();
   unsigned char *ech_config = NULL;
   size_t ech_config_len = 0;
   struct Curl_dns_entry *dns = NULL;
-  struct Curl_https_rrinfo *rinfo = NULL;
   CURLcode result = CURLE_OK;
   rustls_result rr;
 
@@ -960,16 +974,9 @@
     }
   }
   else {
-    if(connssl->peer.hostname) {
-      dns = Curl_dnscache_get(data, connssl->peer.hostname,
-                              connssl->peer.port, data->conn->ip_version);
-    }
-    if(!dns) {
-      failf(data, "rustls: ECH requested but no DNS info available");
-      result = CURLE_SSL_CONNECT_ERROR;
-      goto cleanup;
-    }
-    rinfo = dns->hinfo;
+    const struct Curl_https_rrinfo *rinfo =
+      Curl_conn_dns_get_https(data, cf->sockindex);
+
     if(!rinfo || !rinfo->echconfiglist) {
       failf(data, "rustls: ECH requested but no ECHConfig available");
       result = CURLE_SSL_CONNECT_ERROR;
@@ -994,7 +1001,7 @@
     curlx_free(ech_config);
   }
   if(dns) {
-    Curl_resolv_unlink(data, &dns);
+    Curl_dns_entry_unlink(data, &dns);
   }
   return result;
 }
@@ -1065,8 +1072,8 @@
   }
 
 #ifdef USE_ECH
-  if(ECH_ENABLED(data)) {
-    result = init_config_builder_ech(data, connssl, config_builder);
+  if(CURLECH_ENABLED(data)) {
+    result = init_config_builder_ech(data, cf, config_builder);
     if(result != CURLE_OK && data->set.tls_ech & CURLECH_HARD) {
       rustls_client_config_builder_free(config_builder);
       return result;
@@ -1117,7 +1124,7 @@
 
 /* Given an established network connection, do a TLS handshake.
  *
- * This function will set `*done` to true once the handshake is complete.
+ * This function sets `*done` to true once the handshake is complete.
  * This function never reads the value of `*done*`.
  */
 static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data,
@@ -1128,22 +1135,33 @@
     (struct rustls_ssl_backend_data *)connssl->backend;
   const struct rustls_connection *rconn = NULL;
   CURLcode tmperr = CURLE_OK;
-  int result;
+  CURLcode result;
   bool wants_read;
   bool wants_write;
+  ssize_t nread;
 
   DEBUGASSERT(backend);
 
   CURL_TRC_CF(data, cf, "cr_connect, state=%d", connssl->state);
   *done = FALSE;
 
-  if(!backend->conn) {
-    result = cr_init_backend(cf, data,
-               (struct rustls_ssl_backend_data *)connssl->backend);
-    CURL_TRC_CF(data, cf, "cr_connect, init backend -> %d", result);
-    if(result != CURLE_OK) {
-      return result;
+#ifdef USE_ECH
+    /* if we do ECH and need the HTTPS-RR information for it,
+     * we delay the connect until it arrives or DNS resolve fails. */
+    if(cr_ech_need_httpsrr(data) &&
+       !Curl_conn_dns_resolved_https(data, cf->sockindex)) {
+      CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect");
+      return CURLE_OK;
     }
+#endif /* USE_ECH */
+
+  if(!backend->conn) {
+    result =
+      cr_init_backend(cf, data,
+                      (struct rustls_ssl_backend_data *)connssl->backend);
+    CURL_TRC_CF(data, cf, "cr_connect, init backend -> %d", result);
+    if(result)
+      return result;
     connssl->state = ssl_connection_negotiating;
   }
   rconn = backend->conn;
@@ -1199,7 +1217,7 @@
         while(rustls_connection_get_peer_certificate(rconn, num_certs)) {
           num_certs++;
           if(num_certs > MAX_ALLOWED_CERT_AMOUNT) {
-            failf(data, "%zu certificates is more than allowed (%u)",
+            failf(data, "%zu certificates is more than allowed (%d)",
                   num_certs, MAX_ALLOWED_CERT_AMOUNT);
             return CURLE_SSL_CONNECT_ERROR;
           }
@@ -1263,7 +1281,13 @@
 
     if(wants_read) {
       CURL_TRC_CF(data, cf, "rustls_connection wants us to read_tls.");
-      if(tls_recv_more(cf, data, &tmperr) < 0) {
+      nread = tls_recv_more(cf, data, &tmperr);
+      if(nread == 0) {
+        connssl->peer_closed = TRUE;
+        failf(data, "TLS connect error: Connection closed abruptly");
+        return CURLE_SSL_CONNECT_ERROR;
+      }
+      if(nread < 0) {
         if(tmperr == CURLE_AGAIN) {
           CURL_TRC_CF(data, cf, "reading would block");
           connssl->io_need = CURL_SSL_IO_NEED_RECV;
diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c
index 650a0e3..fb5ff0e 100644
--- a/Utilities/cmcurl/lib/vtls/schannel.c
+++ b/Utilities/cmcurl/lib/vtls/schannel.c
@@ -175,20 +175,20 @@
   for(; i <= (ssl_version_max >> 16); ++i) {
     switch(i) {
     case CURL_SSLVERSION_TLSv1_0:
-      (*enabled_protocols) |= SP_PROT_TLS1_0_CLIENT;
+      *enabled_protocols |= SP_PROT_TLS1_0_CLIENT;
       break;
     case CURL_SSLVERSION_TLSv1_1:
-      (*enabled_protocols) |= SP_PROT_TLS1_1_CLIENT;
+      *enabled_protocols |= SP_PROT_TLS1_1_CLIENT;
       break;
     case CURL_SSLVERSION_TLSv1_2:
-      (*enabled_protocols) |= SP_PROT_TLS1_2_CLIENT;
+      *enabled_protocols |= SP_PROT_TLS1_2_CLIENT;
       break;
     case CURL_SSLVERSION_TLSv1_3:
 
       /* Windows Server 2022 and newer */
       if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT,
                                       VERSION_GREATER_THAN_EQUAL)) {
-        (*enabled_protocols) |= SP_PROT_TLS1_3_CLIENT;
+        *enabled_protocols |= SP_PROT_TLS1_3_CLIENT;
         break;
       }
       else { /* Windows 10 and older */
@@ -277,7 +277,7 @@
   return 0; /* not found */
 }
 
-#define NUM_CIPHERS 47 /* There are 47 options listed above */
+#define NUM_CIPHERS 47 /* There are a maximum of 47 options listed above */
 
 static CURLcode set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers,
                                 ALG_ID *algIds)
@@ -636,7 +636,7 @@
   }
   else {
     /* Pre-Windows 10 1809 or the user set a legacy algorithm list.
-       Schannel will not negotiate TLS 1.3 when SCHANNEL_CRED is used. */
+       Schannel does not negotiate TLS 1.3 when SCHANNEL_CRED is used. */
     ALG_ID algIds[NUM_CIPHERS];
     char *ciphers = conn_config->cipher_list;
     SCHANNEL_CRED schannel_cred = { 0 };
@@ -914,18 +914,18 @@
     unsigned short *list_len = NULL;
     struct alpn_proto_buf proto;
 
-    /* The first four bytes will be an unsigned int indicating number
+    /* The first four bytes is an unsigned int indicating number
        of bytes of data in the rest of the buffer. */
     extension_len = (unsigned int *)(void *)(&alpn_buffer[cur]);
     cur += (int)sizeof(unsigned int);
 
-    /* The next four bytes are an indicator that this buffer will contain
+    /* The next four bytes are an indicator that this buffer contains
        ALPN data, as opposed to NPN, for example. */
     *(unsigned int *)(void *)&alpn_buffer[cur] =
       SecApplicationProtocolNegotiationExt_ALPN;
     cur += (int)sizeof(unsigned int);
 
-    /* The next two bytes will be an unsigned short indicating the number
+    /* The next two bytes is an unsigned short indicating the number
        of bytes used to list the preferred protocols. */
     list_len = (unsigned short *)(void *)(&alpn_buffer[cur]);
     cur += (int)sizeof(unsigned short);
@@ -994,7 +994,7 @@
 
   if(sspi_status != SEC_I_CONTINUE_NEEDED) {
     char buffer[STRERROR_LEN];
-    Curl_safefree(backend->ctxt);
+    curlx_safefree(backend->ctxt);
     switch(sspi_status) {
     case SEC_E_INSUFFICIENT_MEMORY:
       failf(data, "schannel: initial InitializeSecurityContext failed: %s",
@@ -1330,12 +1330,12 @@
     memcpy(inbuf[0].pvBuffer, backend->encdata.buffer,
            backend->encdata.offset);
 
-    /* The socket must be writeable (or a poll error occurred) before we call
+    /* The socket must be writable (or a poll error occurred) before we call
        InitializeSecurityContext to continue processing the received TLS
        records. This is because that function is not idempotent and we do not
        support partial save/resume sending replies of handshake tokens. */
     if(!SOCKET_WRITABLE(Curl_conn_cf_get_socket(cf, data), 0)) {
-      SCH_DEV(infof(data, "schannel: handshake waiting for writeable socket"));
+      SCH_DEV(infof(data, "schannel: handshake waiting for writable socket"));
       connssl->io_need = CURL_SSL_IO_NEED_SEND;
       curlx_free(inbuf[0].pvBuffer);
       return CURLE_OK;
@@ -1348,7 +1348,7 @@
       &outbuf_desc, &backend->ret_flags, NULL);
 
     /* free buffer for received handshake data */
-    Curl_safefree(inbuf[0].pvBuffer);
+    curlx_safefree(inbuf[0].pvBuffer);
 
     /* check if the handshake was incomplete */
     switch(sspi_status) {
@@ -1394,7 +1394,7 @@
     case SEC_I_INCOMPLETE_CREDENTIALS:
       if(!(backend->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) {
         /* If the server has requested a client certificate, attempt to
-           continue the handshake without one. This will allow connections to
+           continue the handshake without one. This allows connections to
            servers which request a client certificate but do not require
            it. */
         backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS;
@@ -1475,7 +1475,7 @@
   }
 
   /* Verify the hostname manually when certificate verification is disabled,
-     because in that case Schannel will not verify it. */
+     because in that case Schannel does not verify it. */
   if(!conn_config->verifypeer && conn_config->verifyhost)
     return Curl_verify_host(cf, data);
 
@@ -1568,7 +1568,7 @@
         CertCloseStore(cred->client_cert_store, 0);
         cred->client_cert_store = NULL;
       }
-      Curl_safefree(cred);
+      curlx_safefree(cred);
     }
   }
 }
@@ -1670,7 +1670,7 @@
 
     traverse_cert_store(ccert_context, cert_counter_callback, &certs_count);
     if(certs_count > MAX_ALLOWED_CERT_AMOUNT) {
-      failf(data, "%d certificates is more than allowed (%u)",
+      failf(data, "%d certificates is more than allowed (%d)",
             certs_count, MAX_ALLOWED_CERT_AMOUNT);
       CertFreeCertificateContext(ccert_context);
       return CURLE_SSL_CONNECT_ERROR;
@@ -1757,7 +1757,13 @@
   SCH_RENEG_CALLER_IS_SEND
 };
 
-#define MAX_RENEG_BLOCK_TIME (7 * 1000) /* 7 seconds in milliseconds */
+/* The maximum time we allow for Schannel renegotiation which may in some
+   rare cases block either due to libcurl (waiting on the socket) or Windows
+   (waiting on an interactive security prompt). Note Schannel "renegotiation"
+   is not necessarily literal TLS renegotiation, but means DecryptMessage
+   returned SEC_I_RENEGOTIATE which means at least the security context needs
+   to be re-established. */
+#define MAX_RENEG_BLOCK_TIME (60 * 1000) /* 60 seconds in milliseconds */
 
 /* This function renegotiates the connection due to a server request received
    by schannel_recv. This function returns CURLE_AGAIN if the renegotiation is
@@ -1845,11 +1851,11 @@
      * occur if the user is waiting on the socket only in one direction.
      *
      * For example, if the user has called recv then they may not be waiting
-     * for a writeable socket and vice versa, so we block to avoid that.
+     * for a writable socket and vice versa, so we block to avoid that.
      *
      * In practice a wait is unlikely to occur. For caller recv if handshake
-     * data needs to be sent then we block for a writeable socket that should
-     * be writeable immediately except for OS resource constraints. For caller
+     * data needs to be sent then we block for a writable socket that should
+     * be writable immediately except for OS resource constraints. For caller
      * send if handshake data needs to be received then we block for a readable
      * socket, which could take some time, but it is more likely the user has
      * called recv since they had called it prior (only recv can start
@@ -1905,7 +1911,7 @@
       SCH_DEV(infof(data, "schannel: renegotiation wait until socket is"
                     "%s%s for up to %" FMT_TIMEDIFF_T " ms",
                     ((readfd != CURL_SOCKET_BAD) ? " readable" : ""),
-                    ((writefd != CURL_SOCKET_BAD) ? " writeable" : ""),
+                    ((writefd != CURL_SOCKET_BAD) ? " writable" : ""),
                     timeout_ms));
 
       what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, timeout_ms);
@@ -2028,7 +2034,7 @@
       sent. The unwritten encrypted bytes would be the first bytes to
       send on the next invocation.
       Here's the catch with this - if we tell the client that all the
-      bytes have been sent, will the client call this method again to
+      bytes have been sent, does the client call this method again to
       send the buffered data?  Looking at who calls this function, it
       seems the answer is NO.
     */
@@ -2082,7 +2088,7 @@
     result = CURLE_SEND_ERROR;
   }
 
-  Curl_safefree(ptr);
+  curlx_safefree(ptr);
 
   if(len == *pnwritten)
     /* Encrypted message including header, data and trailer entirely sent.
@@ -2550,7 +2556,7 @@
   if(backend->ctxt) {
     DEBUGF(infof(data, "schannel: clear security context handle"));
     Curl_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle);
-    Curl_safefree(backend->ctxt);
+    curlx_safefree(backend->ctxt);
   }
 
   /* free SSPI Schannel API credential handle */
@@ -2563,7 +2569,7 @@
 
   /* free internal buffer for received encrypted data */
   if(backend->encdata.buffer) {
-    Curl_safefree(backend->encdata.buffer);
+    curlx_safefree(backend->encdata.buffer);
     backend->encdata.length = 0;
     backend->encdata.offset = 0;
     backend->encdata_is_incomplete = FALSE;
@@ -2571,7 +2577,7 @@
 
   /* free internal buffer for received decrypted data */
   if(backend->decdata.buffer) {
-    Curl_safefree(backend->decdata.buffer);
+    curlx_safefree(backend->decdata.buffer);
     backend->decdata.length = 0;
     backend->decdata.offset = 0;
   }
diff --git a/Utilities/cmcurl/lib/vtls/schannel_verify.c b/Utilities/cmcurl/lib/vtls/schannel_verify.c
index 414b2e9..9be6fe3 100644
--- a/Utilities/cmcurl/lib/vtls/schannel_verify.c
+++ b/Utilities/cmcurl/lib/vtls/schannel_verify.c
@@ -262,7 +262,7 @@
 
   /*
    * Read the CA file completely into memory before parsing it. This
-   * optimizes for the common case where the CA file will be relatively
+   * optimizes for the common case where the CA file is relatively
    * small ( < 1 MiB ).
    */
   ca_file_handle = curlx_CreateFile(ca_file,
@@ -290,7 +290,7 @@
   }
 
   if(file_size.QuadPart > MAX_CAFILE_SIZE) {
-    failf(data, "schannel: CA file exceeds max size of %u bytes",
+    failf(data, "schannel: CA file exceeds max size of %d bytes",
           MAX_CAFILE_SIZE);
     result = CURLE_SSL_CACERT_BADFILE;
     goto cleanup;
@@ -336,7 +336,7 @@
   if(ca_file_handle != INVALID_HANDLE_VALUE) {
     CloseHandle(ca_file_handle);
   }
-  Curl_safefree(ca_file_buffer);
+  curlx_safefree(ca_file_buffer);
 
   return result;
 }
@@ -366,7 +366,7 @@
 #ifndef CERT_NAME_SEARCH_ALL_NAMES_FLAG
 #define CERT_NAME_SEARCH_ALL_NAMES_FLAG 0x2
 #endif
-    /* CertGetNameString will provide the 8-bit character string without
+    /* CertGetNameString provides the 8-bit character string without
      * any decoding */
     DWORD name_flags =
       CERT_NAME_DISABLE_IE4_UTF8_FLAG | CERT_NAME_SEARCH_ALL_NAMES_FLAG;
@@ -438,13 +438,12 @@
 
 static bool get_num_host_info(struct num_ip_data *ip_blob, LPCSTR hostname)
 {
-  bool result = FALSE;
   struct in_addr ia;
   int res = curlx_inet_pton(AF_INET, hostname, &ia);
   if(res) {
     ip_blob->size = sizeof(struct in_addr);
     memcpy(&ip_blob->bData.ia, &ia, sizeof(struct in_addr));
-    result = TRUE;
+    return TRUE;
   }
   else {
     struct in6_addr ia6;
@@ -452,10 +451,10 @@
     if(res) {
       ip_blob->size = sizeof(struct in6_addr);
       memcpy(&ip_blob->bData.ia6, &ia6, sizeof(struct in6_addr));
-      result = TRUE;
+      return TRUE;
     }
   }
-  return result;
+  return FALSE;
 }
 
 static bool get_alt_name_info(struct Curl_easy *data,
@@ -463,20 +462,19 @@
                               PCERT_ALT_NAME_INFO *alt_name_info,
                               LPDWORD alt_name_info_size)
 {
-  bool result = FALSE;
   PCERT_INFO cert_info = NULL;
   PCERT_EXTENSION extension = NULL;
   CRYPT_DECODE_PARA decode_para = { sizeof(CRYPT_DECODE_PARA), NULL, NULL };
 
   if(!ctx) {
     failf(data, "schannel: Null certificate context.");
-    return result;
+    return FALSE;
   }
 
   cert_info = ctx->pCertInfo;
   if(!cert_info) {
     failf(data, "schannel: Null certificate info.");
-    return result;
+    return FALSE;
   }
 
   extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2,
@@ -484,7 +482,7 @@
                                 cert_info->rgExtension);
   if(!extension) {
     failf(data, "schannel: CertFindExtension() returned no extension.");
-    return result;
+    return FALSE;
   }
 
   if(!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
@@ -496,11 +494,10 @@
                           alt_name_info,
                           alt_name_info_size)) {
     failf(data, "schannel: CryptDecodeObjectEx() returned no alternate name "
-                "information.");
-    return result;
+          "information.");
+    return FALSE;
   }
-  result = TRUE;
-  return result;
+  return TRUE;
 }
 
 /* Verify the server's hostname */
@@ -572,7 +569,7 @@
       goto cleanup;
     }
 
-    /* CertGetNameString guarantees that the returned name will not contain
+    /* CertGetNameString guarantees that the returned name does not contain
      * embedded null bytes. This appears to be undocumented behavior.
      */
     cert_hostname_buff = (LPTSTR)curlx_malloc(len * sizeof(TCHAR));
@@ -650,7 +647,7 @@
 
 cleanup:
   LocalFree(alt_name_info);
-  Curl_safefree(cert_hostname_buff);
+  curlx_safefree(cert_hostname_buff);
 
   if(pCertContextServer)
     CertFreeCertificateContext(pCertContextServer);
@@ -763,7 +760,7 @@
       else
         engine_config.cbSize = sizeof(struct cert_chain_engine_config_win7);
 
-      /* CertCreateCertificateChainEngine will check the expected size of the
+      /* CertCreateCertificateChainEngine checks the expected size of the
        * CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size
        * does not match the expected size. When this occurs, it indicates that
        * CAINFO is not supported on the version of Windows in use.
diff --git a/Utilities/cmcurl/lib/vtls/vtls.c b/Utilities/cmcurl/lib/vtls/vtls.c
index f7201d1..891facf 100644
--- a/Utilities/cmcurl/lib/vtls/vtls.c
+++ b/Utilities/cmcurl/lib/vtls/vtls.c
@@ -50,6 +50,7 @@
 #include "vtls/vtls.h" /* generic SSL protos etc */
 #include "vtls/vtls_int.h"
 #include "vtls/vtls_scache.h"
+#include "vtls/keylog.h"
 
 #include "vtls/openssl.h"        /* OpenSSL versions */
 #include "vtls/gtls.h"           /* GnuTLS versions */
@@ -272,22 +273,22 @@
 
 static void free_primary_ssl_config(struct ssl_primary_config *sslc)
 {
-  Curl_safefree(sslc->CApath);
-  Curl_safefree(sslc->CAfile);
-  Curl_safefree(sslc->issuercert);
-  Curl_safefree(sslc->clientcert);
-  Curl_safefree(sslc->cipher_list);
-  Curl_safefree(sslc->cipher_list13);
-  Curl_safefree(sslc->pinned_key);
-  Curl_safefree(sslc->cert_blob);
-  Curl_safefree(sslc->ca_info_blob);
-  Curl_safefree(sslc->issuercert_blob);
-  Curl_safefree(sslc->curves);
-  Curl_safefree(sslc->signature_algorithms);
-  Curl_safefree(sslc->CRLfile);
+  curlx_safefree(sslc->CApath);
+  curlx_safefree(sslc->CAfile);
+  curlx_safefree(sslc->issuercert);
+  curlx_safefree(sslc->clientcert);
+  curlx_safefree(sslc->cipher_list);
+  curlx_safefree(sslc->cipher_list13);
+  curlx_safefree(sslc->pinned_key);
+  curlx_safefree(sslc->cert_blob);
+  curlx_safefree(sslc->ca_info_blob);
+  curlx_safefree(sslc->issuercert_blob);
+  curlx_safefree(sslc->curves);
+  curlx_safefree(sslc->signature_algorithms);
+  curlx_safefree(sslc->CRLfile);
 #ifdef USE_TLS_SRP
-  Curl_safefree(sslc->username);
-  Curl_safefree(sslc->password);
+  curlx_safefree(sslc->username);
+  curlx_safefree(sslc->password);
 #endif
 }
 
@@ -395,7 +396,7 @@
 CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data,
                                    struct connectdata *conn)
 {
-  /* Clone "primary" SSL configurations from the esay handle to
+  /* Clone "primary" SSL configurations from the easy handle to
    * the connection. They are used for connection cache matching and
    * probably outlive the easy handle */
   if(!clone_ssl_primary_config(&data->set.ssl.primary, &conn->ssl_config))
@@ -520,7 +521,7 @@
 static void cf_ctx_free(struct ssl_connect_data *ctx)
 {
   if(ctx) {
-    Curl_safefree(ctx->negotiated.alpn);
+    curlx_safefree(ctx->negotiated.alpn);
     Curl_bufq_free(&ctx->earlydata);
     curlx_free(ctx->backend);
     curlx_free(ctx);
@@ -787,7 +788,7 @@
       encode = curlx_base64_encode(sha256sumdigest,
                                    CURL_SHA256_DIGEST_LENGTH,
                                    &cert_hash, &cert_hash_len);
-    Curl_safefree(sha256sumdigest);
+    curlx_safefree(sha256sumdigest);
 
     if(encode)
       return encode;
@@ -802,7 +803,7 @@
       pinned_hash_len = end_pos ?
                         (size_t)(end_pos - pinned_hash) : strlen(pinned_hash);
 
-      /* compare base64 sha256 digests" */
+      /* compare base64 sha256 digests */
       if(cert_hash_len == pinned_hash_len &&
          !memcmp(cert_hash, pinned_hash, cert_hash_len)) {
         DEBUGF(infof(data, "public key hash matches pinned value"));
@@ -815,14 +816,14 @@
       /* next one or we are at the end */
       pinned_hash = end_pos ? (end_pos + 1) : NULL;
     }
-    Curl_safefree(cert_hash);
+    curlx_safefree(cert_hash);
   }
   else {
     long filesize;
     size_t size, pem_len;
     CURLcode pem_read;
     struct dynbuf buf;
-    char unsigned *pem_ptr = NULL;
+    unsigned char *pem_ptr = NULL;
     size_t left;
     FILE *fp = curlx_fopen(pinnedpubkey, "rb");
     if(!fp)
@@ -869,7 +870,7 @@
     }
 
     /*
-     * Otherwise we will assume it is PEM and try to decode it after placing
+     * Otherwise we assume it is PEM and try to decode it after placing
      * null-terminator
      */
     pem_read = pubkey_pem_to_der(curlx_dyn_ptr(&buf), &pem_ptr, &pem_len);
@@ -885,7 +886,7 @@
       result = CURLE_OK;
 end:
     curlx_dyn_free(&buf);
-    Curl_safefree(pem_ptr);
+    curlx_safefree(pem_ptr);
     curlx_fclose(fp);
   }
 
@@ -1179,12 +1180,12 @@
 
 void Curl_ssl_peer_cleanup(struct ssl_peer *peer)
 {
-  Curl_safefree(peer->sni);
+  curlx_safefree(peer->sni);
   if(peer->dispname != peer->hostname)
     curlx_free(peer->dispname);
   peer->dispname = NULL;
-  Curl_safefree(peer->hostname);
-  Curl_safefree(peer->scache_key);
+  curlx_safefree(peer->hostname);
+  curlx_safefree(peer->scache_key);
   peer->type = CURL_SSL_PEER_DNS;
 }
 
@@ -1221,7 +1222,7 @@
 CURLcode Curl_ssl_peer_init(struct ssl_peer *peer,
                             struct Curl_cfilter *cf,
                             const char *tls_id,
-                            int transport)
+                            uint8_t transport)
 {
   const char *ehostname, *edispname;
   CURLcode result = CURLE_OUT_OF_MEMORY;
@@ -1248,7 +1249,7 @@
   {
     ehostname = cf->conn->host.name;
     edispname = cf->conn->host.dispname;
-    peer->port = cf->conn->remote_port;
+    peer->port = (uint16_t)cf->conn->remote_port;
   }
 
   /* hostname MUST exist and not be empty */
@@ -1367,6 +1368,13 @@
     if(connssl->state == ssl_connection_complete) {
       connssl->handshake_done = *Curl_pgrs_now(data);
     }
+    if(Curl_tls_keylog_enabled()) {
+      infof(data, "SSLKEYLOGFILE set, all TLS secrets are logged to '%s'",
+            Curl_tls_keylog_file_name());
+#ifdef LIBRESSL_VERSION_NUMBER
+      infof(data, "Note LibreSSL only supports SSLKEYLOGFILE for TLS <= 1.2");
+#endif
+    }
     /* Connection can be deferred when sending early data */
     DEBUGASSERT(connssl->state == ssl_connection_complete ||
                 connssl->state == ssl_connection_deferred);
@@ -1393,7 +1401,7 @@
     if(blen > connssl->earlydata_max)
       blen = connssl->earlydata_max;
     result = Curl_bufq_write(&connssl->earlydata, buf, blen, &nwritten);
-    CURL_TRC_CF(data, cf, "ssl_cf_set_earlydata(len=%zu) -> %zd",
+    CURL_TRC_CF(data, cf, "ssl_cf_set_earlydata(len=%zu) -> %zu",
                 blen, nwritten);
     if(result)
       return result;
@@ -1455,16 +1463,16 @@
 {
   struct ssl_connect_data *connssl = cf->ctx;
   struct cf_call_data save;
-  bool result;
+  bool pending;
 
   CF_DATA_SAVE(save, cf, data);
   if(connssl->ssl_impl->data_pending &&
      connssl->ssl_impl->data_pending(cf, data))
-    result = TRUE;
+    pending = TRUE;
   else
-    result = cf->next->cft->has_data_pending(cf->next, data);
+    pending = cf->next->cft->has_data_pending(cf->next, data);
   CF_DATA_RESTORE(cf, save);
-  return result;
+  return pending;
 }
 
 static CURLcode ssl_cf_send(struct Curl_cfilter *cf,
@@ -1684,7 +1692,7 @@
 
 struct Curl_cftype Curl_cft_ssl_proxy = {
   "SSL-PROXY",
-  CF_TYPE_SSL|CF_TYPE_PROXY,
+  CF_TYPE_SSL | CF_TYPE_PROXY,
   CURL_LOG_LVL_NONE,
   ssl_cf_destroy,
   ssl_cf_connect,
diff --git a/Utilities/cmcurl/lib/vtls/vtls.h b/Utilities/cmcurl/lib/vtls/vtls.h
index b9335bb..6db67cf 100644
--- a/Utilities/cmcurl/lib/vtls/vtls.h
+++ b/Utilities/cmcurl/lib/vtls/vtls.h
@@ -49,8 +49,14 @@
 #define SSLSUPP_ISSUERCERT_BLOB (1 << 14) /* CURLOPT_ISSUERCERT_BLOB */
 
 #ifdef USE_ECH
-#include "curlx/base64.h"
-#define ECH_ENABLED(data) \
+/* CURLECH_ bits for the tls_ech option */
+#define CURLECH_DISABLE    (1 << 0)
+#define CURLECH_GREASE     (1 << 1)
+#define CURLECH_ENABLE     (1 << 2)
+#define CURLECH_HARD       (1 << 3)
+#define CURLECH_CLA_CFG    (1 << 4)
+
+#define CURLECH_ENABLED(data) \
   ((data)->set.tls_ech && !((data)->set.tls_ech & CURLECH_DISABLE))
 #endif /* USE_ECH */
 
@@ -90,8 +96,8 @@
   char *sni;             /* SNI version of hostname or NULL if not usable */
   char *scache_key;      /* for lookups in session cache */
   ssl_peer_type type;    /* type of the peer information */
-  int port;              /* port we are talking to */
-  int transport;         /* one of TRNSPRT_* defines */
+  uint16_t port;         /* port we are talking to */
+  uint8_t transport;     /* one of TRNSPRT_* defines */
 };
 
 CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
@@ -134,7 +140,7 @@
                                 bool proxy);
 
 /* Update certain connection SSL config flags after they have
- * been changed on the easy handle. Will work for `verifypeer`,
+ * been changed on the easy handle. Works for `verifypeer`,
  * `verifyhost` and `verifystatus`. */
 void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy);
 
@@ -144,7 +150,7 @@
 CURLcode Curl_ssl_peer_init(struct ssl_peer *peer,
                             struct Curl_cfilter *cf,
                             const char *tls_id,
-                            int transport);
+                            uint8_t transport);
 /**
  * Free all allocated data and reset peer information.
  */
diff --git a/Utilities/cmcurl/lib/vtls/vtls_scache.c b/Utilities/cmcurl/lib/vtls/vtls_scache.c
index 673abcf..59fa256 100644
--- a/Utilities/cmcurl/lib/vtls/vtls_scache.c
+++ b/Utilities/cmcurl/lib/vtls/vtls_scache.c
@@ -200,7 +200,7 @@
   }
 
   if(ssl->version || ssl->version_max) {
-    r = curlx_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version,
+    r = curlx_dyn_addf(&buf, ":TLSVER-%d-%u", ssl->version,
                        (ssl->version_max >> 16));
     if(r)
       goto out;
@@ -238,11 +238,6 @@
     r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert, &is_local);
     if(r)
       goto out;
-    if(ssl->cert_blob) {
-      r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob);
-      if(r)
-        goto out;
-    }
     if(ssl->ca_info_blob) {
       r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob);
       if(r)
@@ -254,6 +249,11 @@
         goto out;
     }
   }
+  if(ssl->cert_blob) {
+    r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob);
+    if(r)
+      goto out;
+  }
   if(ssl->pinned_key && ssl->pinned_key[0]) {
     r = curlx_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key);
     if(r)
@@ -301,6 +301,7 @@
   size_t peer_count;
   int default_lifetime_secs;
   long age;
+  BIT(is_locked);
 };
 
 static struct Curl_ssl_scache *cf_ssl_scache_get(struct Curl_easy *data)
@@ -401,12 +402,12 @@
     peer->sobj = NULL;
   }
   peer->sobj_free = NULL;
-  Curl_safefree(peer->clientcert);
+  curlx_safefree(peer->clientcert);
 #ifdef USE_TLS_SRP
-  Curl_safefree(peer->srp_username);
-  Curl_safefree(peer->srp_password);
+  curlx_safefree(peer->srp_username);
+  curlx_safefree(peer->srp_password);
 #endif
-  Curl_safefree(peer->ssl_peer_key);
+  curlx_safefree(peer->ssl_peer_key);
   peer->age = 0;
   peer->hmac_set = FALSE;
 }
@@ -583,15 +584,26 @@
 /* Lock shared SSL session data */
 void Curl_ssl_scache_lock(struct Curl_easy *data)
 {
-  if(CURL_SHARE_ssl_scache(data))
-    Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
+  struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
+  if(scache) {
+    if(CURL_SHARE_ssl_scache(data))
+      Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION,
+                      CURL_LOCK_ACCESS_SINGLE);
+    DEBUGASSERT(!scache->is_locked);
+    scache->is_locked = TRUE;
+  }
 }
 
 /* Unlock shared SSL session data */
 void Curl_ssl_scache_unlock(struct Curl_easy *data)
 {
-  if(CURL_SHARE_ssl_scache(data))
-    Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
+  struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
+  if(scache) {
+    DEBUGASSERT(scache->is_locked);
+    scache->is_locked = FALSE;
+    if(CURL_SHARE_ssl_scache(data))
+      Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
+  }
 }
 
 static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer,
@@ -734,12 +746,19 @@
 
   peer = cf_ssl_get_free_peer(scache);
   if(peer) {
+    char buffer[64];
     const char *ccert = conn_config ? conn_config->clientcert : NULL;
     const char *username = NULL, *password = NULL;
 #ifdef USE_TLS_SRP
     username = conn_config ? conn_config->username : NULL;
     password = conn_config ? conn_config->password : NULL;
 #endif
+    if(!ccert && conn_config && conn_config->cert_blob) {
+      /* when using a client cert blob, create a name for it */
+      curl_msnprintf(buffer, sizeof(buffer),
+                     "cert-%p", conn_config->cert_blob->data);
+      ccert = buffer; /* data is strduped by cf_ssl_scache_peer_init */
+    }
     result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert,
                                      username, password, NULL, NULL);
     if(result)
@@ -993,6 +1012,12 @@
 
 #define CURL_SSL_TICKET_MAX   (16 * 1024)
 
+bool Curl_ssl_scache_is_locked(struct Curl_easy *data)
+{
+  struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
+  return scache && scache->is_locked;
+}
+
 static CURLcode cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer *peer)
 {
   CURLcode result;
@@ -1075,49 +1100,47 @@
   struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
   struct Curl_ssl_scache_peer *peer = NULL;
   struct Curl_ssl_session *s = NULL;
-  bool locked = FALSE;
-  CURLcode r;
+  CURLcode result;
 
   if(!scache) {
-    r = CURLE_BAD_FUNCTION_ARGUMENT;
+    result = CURLE_BAD_FUNCTION_ARGUMENT;
     goto out;
   }
   if(!ssl_peer_key && (!shmac || !shmac_len)) {
-    r = CURLE_BAD_FUNCTION_ARGUMENT;
+    result = CURLE_BAD_FUNCTION_ARGUMENT;
     goto out;
   }
 
-  r = Curl_ssl_session_unpack(data, sdata, sdata_len, &s);
-  if(r)
+  result = Curl_ssl_session_unpack(data, sdata, sdata_len, &s);
+  if(result)
     goto out;
 
   Curl_ssl_scache_lock(data);
-  locked = TRUE;
 
   if(ssl_peer_key) {
-    r = cf_ssl_add_peer(data, scache, ssl_peer_key, NULL, &peer);
-    if(r)
+    result = cf_ssl_add_peer(data, scache, ssl_peer_key, NULL, &peer);
+    if(result)
       goto out;
   }
   else if(shmac_len != (sizeof(peer->key_salt) + sizeof(peer->key_hmac))) {
     /* Either salt+hmac was garbled by caller or is from a curl version
      * that does things differently */
-    r = CURLE_BAD_FUNCTION_ARGUMENT;
+    result = CURLE_BAD_FUNCTION_ARGUMENT;
     goto out;
   }
   else {
     const unsigned char *salt = shmac;
     const unsigned char *hmac = shmac + sizeof(peer->key_salt);
 
-    r = cf_ssl_find_peer_by_hmac(scache, salt, hmac, &peer);
-    if(r)
+    result = cf_ssl_find_peer_by_hmac(scache, salt, hmac, &peer);
+    if(result)
       goto out;
     if(!peer) {
       peer = cf_ssl_get_free_peer(scache);
       if(peer) {
-        r = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL,
-                                    NULL, NULL, salt, hmac);
-        if(r)
+        result = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL,
+                                         NULL, NULL, salt, hmac);
+        if(result)
           goto out;
       }
     }
@@ -1133,10 +1156,10 @@
   }
 
 out:
-  if(locked)
+  if(scache && scache->is_locked)
     Curl_ssl_scache_unlock(data);
   Curl_ssl_session_destroy(s);
-  return r;
+  return result;
 }
 
 CURLcode Curl_ssl_session_export(struct Curl_easy *data,
@@ -1156,8 +1179,6 @@
 
   if(!export_fn)
     return CURLE_BAD_FUNCTION_ARGUMENT;
-  if(!scache)
-    return CURLE_OK;
 
   Curl_ssl_scache_lock(data);
 
diff --git a/Utilities/cmcurl/lib/vtls/vtls_scache.h b/Utilities/cmcurl/lib/vtls/vtls_scache.h
index b9db0f1..a6a36f1 100644
--- a/Utilities/cmcurl/lib/vtls/vtls_scache.h
+++ b/Utilities/cmcurl/lib/vtls/vtls_scache.h
@@ -52,7 +52,7 @@
 /* Create a key from peer and TLS configuration information that is
  * unique for how the connection filter wants to establish a TLS
  * connection to the peer.
- * If the filter is a TLS proxy filter, it will use the proxy relevant
+ * If the filter is a TLS proxy filter, it uses the proxy relevant
  * information.
  * @param cf      the connection filter wanting to use it
  * @param peer    the peer the filter wants to talk to
@@ -84,7 +84,7 @@
 /* Unlock session cache mutex */
 void Curl_ssl_scache_unlock(struct Curl_easy *data);
 
-/* Get TLS session object from the cache for the ssl_peer_ey.
+/* Get TLS session object from the cache for the ssl_peer_key.
  * scache mutex must be locked (see Curl_ssl_scache_lock).
  * Caller must make sure that the ownership of returned session object
  * is properly taken (e.g. its refcount is incremented
@@ -112,7 +112,7 @@
  * @param data    the transfer involved
  * @param ssl_peer_key the key for lookup
  * @param sobj    the TLS session object
- * @param sobj_free_cb callback to free the session objectt
+ * @param sobj_free_cb callback to free the session object
  */
 CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf,
                                  struct Curl_easy *data,
@@ -199,6 +199,8 @@
 
 #ifdef USE_SSLS_EXPORT
 
+bool Curl_ssl_scache_is_locked(struct Curl_easy *data);
+
 CURLcode Curl_ssl_session_import(struct Curl_easy *data,
                                  const char *ssl_peer_key,
                                  const unsigned char *shmac, size_t shmac_len,
diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.c b/Utilities/cmcurl/lib/vtls/wolfssl.c
index aa841a7..15c81c2 100644
--- a/Utilities/cmcurl/lib/vtls/wolfssl.c
+++ b/Utilities/cmcurl/lib/vtls/wolfssl.c
@@ -33,8 +33,8 @@
 #include <wolfssl/options.h>
 #include <wolfssl/version.h>
 
-#if LIBWOLFSSL_VERSION_HEX < 0x03004006 /* wolfSSL 3.4.6 (2015) */
-#error "wolfSSL version should be at least 3.4.6"
+#if LIBWOLFSSL_VERSION_HEX < 0x05000000 /* wolfSSL 5.0.0 (2021-11-01) */
+#error "wolfSSL version should be at least 5.0.0"
 #endif
 #if defined(OPENSSL_COEXIST) && LIBWOLFSSL_VERSION_HEX < 0x05007006
 #error "wolfSSL 5.7.6 or newer is required to coexist with OpenSSL"
@@ -56,6 +56,7 @@
 #include "urldata.h"
 #include "curl_trc.h"
 #include "httpsrr.h"
+#include "cf-dns.h"
 #include "vtls/vtls.h"
 #include "vtls/vtls_int.h"
 #include "vtls/vtls_scache.h"
@@ -65,6 +66,9 @@
 #include "curlx/strdup.h"
 #include "curlx/strcopy.h"
 #include "vtls/x509asn1.h"
+#ifdef USE_ECH
+#include "curlx/base64.h"
+#endif
 
 #include <wolfssl/ssl.h>
 #include <wolfssl/error-ssl.h>
@@ -109,7 +113,7 @@
  * Availability note:
  * The TLS 1.3 secret callback (wolfSSL_set_tls13_secret_cb) was added in
  * wolfSSL 4.4.0, but requires the -DHAVE_SECRET_CALLBACK build option. If that
- * option is not set, then TLS 1.3 will not be logged.
+ * option is not set, then TLS 1.3 is not logged.
  * For TLS 1.2 and before, we use wolfSSL_get_keys().
  * wolfSSL_get_client_random and wolfSSL_get_keys require OPENSSL_EXTRA
  * (--enable-opensslextra or --enable-all).
@@ -168,7 +172,6 @@
   unsigned char *ms, *sr, *cr;
   unsigned int msLen, srLen, crLen, i, x = 0;
 
-#if LIBWOLFSSL_VERSION_HEX >= 0x0300d000 /* >= 3.13.0 */
   /* wolfSSL_GetVersion is available since 3.13, we use it instead of
    * SSL_version since the latter relies on OPENSSL_ALL (--enable-opensslall or
    * --enable-all). Failing to perform this check could result in an unusable
@@ -184,7 +187,6 @@
      * is not directly usable. */
     return;
   }
-#endif
 
   if(wolfSSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) !=
      WOLFSSL_SUCCESS) {
@@ -546,7 +548,7 @@
         infof(data, "SSL reusing session with ALPN '%s'",
               scs->alpn ? scs->alpn : "-");
         if(ssl_config->earlydata &&
-           !cf->conn->connect_only &&
+           !cf->conn->bits.connect_only &&
            !strcmp("TLSv1.3", wolfSSL_get_version(wss->ssl))) {
           bool do_early_data = FALSE;
           if(sess_reuse_cb) {
@@ -872,8 +874,7 @@
 }
 #endif
 
-/* 4.2.0 (2019) */
-#if LIBWOLFSSL_VERSION_HEX < 0x04002000 || !defined(OPENSSL_EXTRA)
+#ifndef OPENSSL_EXTRA
 static int wssl_legacy_CTX_set_min_proto_version(WOLFSSL_CTX *ctx, int version)
 {
   int res;
@@ -909,11 +910,11 @@
 }
 #define wolfSSL_CTX_set_min_proto_version wssl_legacy_CTX_set_min_proto_version
 #define wolfSSL_CTX_set_max_proto_version wssl_legacy_CTX_set_max_proto_version
-#endif
+#endif /* OPENSSL_EXTRA */
 
-static CURLcode client_certificate(struct Curl_easy *data,
-                                   struct ssl_config_data *ssl_config,
-                                   struct wssl_ctx *wctx)
+static CURLcode wssl_client_cert(struct Curl_easy *data,
+                                 struct ssl_config_data *ssl_config,
+                                 struct wssl_ctx *wctx)
 {
   /* Load the client certificate, and private key */
 #ifndef NO_FILESYSTEM
@@ -1076,80 +1077,29 @@
 }
 
 #define QUIC_GROUPS "P-256:P-384:P-521"
-
-CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx,
-                            struct Curl_cfilter *cf,
-                            struct Curl_easy *data,
-                            struct ssl_peer *peer,
-                            const struct alpn_spec *alpns_requested,
-                            Curl_wssl_ctx_setup_cb *cb_setup,
-                            void *cb_user_data,
-                            void *ssl_user_data,
-                            Curl_wssl_init_session_reuse_cb *sess_reuse_cb)
-{
-  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
-  struct ssl_primary_config *conn_config;
-  WOLFSSL_METHOD *req_method = NULL;
-  struct alpn_spec alpns;
-  char *curves;
-#ifdef WOLFSSL_HAVE_KYBER
-  word16 pqkem = 0;
-  size_t idx = 0;
-#endif
-  CURLcode result = CURLE_FAILED_INIT;
-  unsigned char transport;
-  int tls_min, tls_max;
-
-  DEBUGASSERT(!wctx->ssl_ctx);
-  DEBUGASSERT(!wctx->ssl);
-  conn_config = Curl_ssl_cf_get_primary_config(cf);
-  if(!conn_config) {
-    result = CURLE_FAILED_INIT;
-    goto out;
-  }
-  Curl_alpn_copy(&alpns, alpns_requested);
-  DEBUGASSERT(cf->next);
-  transport = Curl_conn_cf_get_transport(cf->next, data);
-
-#if LIBWOLFSSL_VERSION_HEX < 0x04002000 /* 4.2.0 (2019) */
-  req_method = wolfSSLv23_client_method();
-#else
-  req_method = wolfTLS_client_method();
-#endif
-  if(!req_method) {
-    failf(data, "wolfSSL: could not create a client method");
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
-
-  if(wctx->ssl_ctx)
-    wolfSSL_CTX_free(wctx->ssl_ctx);
-
-  wctx->ssl_ctx = wolfSSL_CTX_new(req_method);
-  if(!wctx->ssl_ctx) {
-    failf(data, "wolfSSL: could not create a context");
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
-
-  result = ssl_version(data, conn_config, wctx, &tls_min, &tls_max);
-  if(result)
-    goto out;
-
-#ifndef WOLFSSL_TLS13
-  {
-    const char *ciphers = conn_config->cipher_list;
-    if(ciphers) {
-      if(!SSL_CTX_set_cipher_list(wctx->ssl_ctx, ciphers)) {
-        failf(data, "failed setting cipher list: %s", ciphers);
-        result = CURLE_SSL_CIPHER;
-        goto out;
-      }
-      infof(data, "Cipher selection: %s", ciphers);
-    }
-  }
-#else
+#ifdef WOLFSSL_TLS13
 #define MAX_CIPHER_LEN 4096
+#endif
+
+static CURLcode wssl_init_ciphers(struct Curl_easy *data,
+                                  struct wssl_ctx *wctx,
+                                  struct ssl_primary_config *conn_config,
+                                  int tls_min, int tls_max)
+{
+#ifndef WOLFSSL_TLS13
+  const char *ciphers = conn_config->cipher_list;
+  (void)tls_min;
+  (void)tls_max;
+  if(ciphers) {
+    if(!SSL_CTX_set_cipher_list(wctx->ssl_ctx, ciphers)) {
+      failf(data, "failed setting cipher list: %s", ciphers);
+      return CURLE_SSL_CIPHER;
+    }
+    infof(data, "Cipher selection: %s", ciphers);
+  }
+  return CURLE_OK;
+#else
+  CURLcode result = CURLE_OK;
   if(conn_config->cipher_list || conn_config->cipher_list13) {
     const char *ciphers12 = conn_config->cipher_list;
     const char *ciphers13 = conn_config->cipher_list13;
@@ -1173,45 +1123,276 @@
       else
         result = wssl_add_default_ciphers(FALSE, &c);
     }
-    if(result)
-      goto out;
-
-    if(!wolfSSL_CTX_set_cipher_list(wctx->ssl_ctx, curlx_dyn_ptr(&c))) {
-      failf(data, "failed setting cipher list: %s", curlx_dyn_ptr(&c));
-      curlx_dyn_free(&c);
-      result = CURLE_SSL_CIPHER;
-      goto out;
+    if(!result) {
+      if(!wolfSSL_CTX_set_cipher_list(wctx->ssl_ctx, curlx_dyn_ptr(&c))) {
+        failf(data, "failed setting cipher list: %s", curlx_dyn_ptr(&c));
+        result = CURLE_SSL_CIPHER;
+      }
+      else
+        infof(data, "Cipher selection: %s", curlx_dyn_ptr(&c));
     }
-    infof(data, "Cipher selection: %s", curlx_dyn_ptr(&c));
     curlx_dyn_free(&c);
   }
+  return result;
 #endif
+}
 
-  curves = conn_config->curves;
+static CURLcode wssl_init_curves(struct Curl_easy *data,
+                                 struct wssl_ctx *wctx,
+                                 struct ssl_primary_config *conn_config,
+                                 unsigned char transport
+#ifdef WOLFSSL_HAVE_KYBER
+                                 , word16 *out_pqkem
+#endif
+                                 )
+{
+  char *curves = conn_config->curves;
   if(!curves && (transport == TRNSPRT_QUIC))
     curves = (char *)CURL_UNCONST(QUIC_GROUPS);
 
   if(curves) {
 #ifdef WOLFSSL_HAVE_KYBER
+    size_t idx;
     for(idx = 0; gnm[idx].name != NULL; idx++) {
-      if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) {
-        pqkem = gnm[idx].group;
+      if(!strncmp(curves, gnm[idx].name, strlen(gnm[idx].name))) {
+        *out_pqkem = gnm[idx].group;
         break;
       }
     }
 
-    if(pqkem == 0)
+    if(*out_pqkem == 0)
 #endif
     {
       if(!wolfSSL_CTX_set1_curves_list(wctx->ssl_ctx, curves)) {
         failf(data, "failed setting curves list: '%s'", curves);
-        result = CURLE_SSL_CIPHER;
-        goto out;
+        return CURLE_SSL_CIPHER;
+      }
+    }
+  }
+  return CURLE_OK;
+}
+
+static CURLcode wssl_init_ssl_handle(struct wssl_ctx *wctx,
+                                     struct Curl_cfilter *cf,
+                                     struct Curl_easy *data,
+                                     struct ssl_peer *peer,
+                                     struct alpn_spec *alpns,
+                                     void *ssl_user_data,
+                                     unsigned char transport,
+#ifdef WOLFSSL_HAVE_KYBER
+                                     word16 pqkem,
+#endif
+                                     Curl_wssl_init_session_reuse_cb
+                                       *sess_reuse_cb)
+{
+  /* Let's make an SSL structure */
+  wctx->ssl = wolfSSL_new(wctx->ssl_ctx);
+  if(!wctx->ssl) {
+    failf(data, "SSL: could not create a handle");
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+#ifdef HAVE_EX_DATA
+  wolfSSL_set_app_data(wctx->ssl, ssl_user_data);
+#else
+  (void)ssl_user_data;
+#endif
+#ifdef WOLFSSL_QUIC
+  if(transport == TRNSPRT_QUIC)
+    wolfSSL_set_quic_use_legacy_codepoint(wctx->ssl, 0);
+#else
+  (void)transport;
+#endif
+
+#ifdef WOLFSSL_HAVE_KYBER
+  if(pqkem) {
+    if(wolfSSL_UseKeyShare(wctx->ssl, pqkem) !=
+       WOLFSSL_SUCCESS) {
+      failf(data, "unable to use PQ KEM");
+    }
+  }
+#endif
+
+  /* Check if there is a cached ID we can/should use here! */
+  if(Curl_ssl_scache_use(cf, data)) {
+    /* Set session from cache if there is one */
+    (void)wssl_setup_session(cf, data, wctx, alpns, peer->scache_key,
+                             sess_reuse_cb);
+  }
+
+#ifdef HAVE_ALPN
+  if(alpns->count) {
+    struct alpn_proto_buf proto;
+    memset(&proto, 0, sizeof(proto));
+    Curl_alpn_to_proto_str(&proto, alpns);
+
+    if(wolfSSL_UseALPN(wctx->ssl, (char *)proto.data,
+                       (unsigned int)proto.len,
+                       WOLFSSL_ALPN_CONTINUE_ON_MISMATCH)
+       != WOLFSSL_SUCCESS) {
+      failf(data, "SSL: failed setting ALPN protocols");
+      return CURLE_SSL_CONNECT_ERROR;
+    }
+    CURL_TRC_CF(data, cf, "set ALPN: %s", proto.data);
+  }
+#endif /* HAVE_ALPN */
+
+#ifdef OPENSSL_EXTRA
+  if(Curl_tls_keylog_enabled()) {
+    /* Ensure the Client Random is preserved. */
+    wolfSSL_KeepArrays(wctx->ssl);
+#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13)
+    wolfSSL_set_tls13_secret_cb(wctx->ssl, wssl_tls13_secret_callback, NULL);
+#endif
+  }
+#endif /* OPENSSL_EXTRA */
+
+#ifdef HAVE_SECURE_RENEGOTIATION
+  if(wolfSSL_UseSecureRenegotiation(wctx->ssl) != SSL_SUCCESS) {
+    failf(data, "SSL: failed setting secure renegotiation");
+    return CURLE_SSL_CONNECT_ERROR;
+  }
+#endif /* HAVE_SECURE_RENEGOTIATION */
+
+  return CURLE_OK;
+}
+
+#ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG
+static CURLcode wssl_init_ech(struct wssl_ctx *wctx,
+                              struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  int trying_ech_now = 0;
+
+  if(data->set.str[STRING_ECH_PUBLIC]) {
+    infof(data, "ECH: outername not (yet) supported"
+          " with wolfSSL");
+    return CURLE_SSL_CONNECT_ERROR;
+  }
+  if(data->set.tls_ech == CURLECH_GREASE) {
+    infof(data, "ECH: GREASE is done by default by"
+          " wolfSSL: no need to ask");
+  }
+  if(data->set.tls_ech & CURLECH_CLA_CFG &&
+     data->set.str[STRING_ECH_CONFIG]) {
+    char *b64val = data->set.str[STRING_ECH_CONFIG];
+    word32 b64len = 0;
+
+    b64len = (word32)strlen(b64val);
+    if(b64len && wolfSSL_SetEchConfigsBase64(wctx->ssl, b64val,
+                                             b64len) != WOLFSSL_SUCCESS) {
+      if(data->set.tls_ech & CURLECH_HARD)
+        return CURLE_SSL_CONNECT_ERROR;
+    }
+    else {
+      trying_ech_now = 1;
+      infof(data, "ECH: ECHConfig from command line");
+    }
+  }
+  else {
+    const struct Curl_https_rrinfo *rinfo =
+      Curl_conn_dns_get_https(data, cf->sockindex);
+
+    if(rinfo && rinfo->echconfiglist) {
+      const unsigned char *ecl = rinfo->echconfiglist;
+      size_t elen = rinfo->echconfiglist_len;
+
+      infof(data, "ECH: ECHConfig from HTTPS RR");
+      if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32)elen) !=
+         WOLFSSL_SUCCESS) {
+        infof(data, "ECH: wolfSSL_SetEchConfigs failed");
+        if(data->set.tls_ech & CURLECH_HARD) {
+          return CURLE_SSL_CONNECT_ERROR;
+        }
+      }
+      else {
+        trying_ech_now = 1;
+        infof(data, "ECH: imported ECHConfigList of length %zu", elen);
+      }
+    }
+    else {
+      infof(data, "ECH: requested but no ECHConfig available");
+      if(data->set.tls_ech & CURLECH_HARD) {
+        return CURLE_SSL_CONNECT_ERROR;
       }
     }
   }
 
-  result = client_certificate(data, ssl_config, wctx);
+  if(trying_ech_now &&
+     wolfSSL_set_min_proto_version(wctx->ssl, TLS1_3_VERSION) != 1) {
+    infof(data, "ECH: cannot force TLSv1.3 [ERROR]");
+    return CURLE_SSL_CONNECT_ERROR;
+  }
+  return CURLE_OK;
+}
+#endif /* HAVE_WOLFSSL_CTX_GENERATEECHCONFIG */
+
+CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx,
+                            struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            struct ssl_peer *peer,
+                            const struct alpn_spec *alpns_requested,
+                            Curl_wssl_ctx_setup_cb *cb_setup,
+                            void *cb_user_data,
+                            void *ssl_user_data,
+                            Curl_wssl_init_session_reuse_cb *sess_reuse_cb)
+{
+  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+  struct ssl_primary_config *conn_config;
+  WOLFSSL_METHOD *req_method = NULL;
+  struct alpn_spec alpns;
+#ifdef WOLFSSL_HAVE_KYBER
+  word16 pqkem = 0;
+#endif
+  CURLcode result = CURLE_FAILED_INIT;
+  unsigned char transport;
+  int tls_min, tls_max;
+
+  DEBUGASSERT(!wctx->ssl_ctx);
+  DEBUGASSERT(!wctx->ssl);
+  conn_config = Curl_ssl_cf_get_primary_config(cf);
+  if(!conn_config) {
+    result = CURLE_FAILED_INIT;
+    goto out;
+  }
+  Curl_alpn_copy(&alpns, alpns_requested);
+  DEBUGASSERT(cf->next);
+  transport = Curl_conn_cf_get_transport(cf->next, data);
+
+  req_method = wolfTLS_client_method();
+  if(!req_method) {
+    failf(data, "wolfSSL: could not create a client method");
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  if(wctx->ssl_ctx)
+    wolfSSL_CTX_free(wctx->ssl_ctx);
+
+  wctx->ssl_ctx = wolfSSL_CTX_new(req_method);
+  if(!wctx->ssl_ctx) {
+    failf(data, "wolfSSL: could not create a context");
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  result = ssl_version(data, conn_config, wctx, &tls_min, &tls_max);
+  if(result)
+    goto out;
+
+  result = wssl_init_ciphers(data, wctx, conn_config, tls_min, tls_max);
+  if(result)
+    goto out;
+
+  result = wssl_init_curves(data, wctx, conn_config, transport
+#ifdef WOLFSSL_HAVE_KYBER
+                            , &pqkem
+#endif
+    );
+  if(result)
+    goto out;
+
+  result = wssl_client_cert(data, ssl_config, wctx);
   if(result)
     goto out;
 
@@ -1219,9 +1400,8 @@
    * fail to connect if the verification fails, or if it should continue
    * anyway. In the latter case the result of the verification is checked with
    * SSL_get_verify_result() below. */
-  wolfSSL_CTX_set_verify(wctx->ssl_ctx,
-                         conn_config->verifypeer ? WOLFSSL_VERIFY_PEER :
-                         WOLFSSL_VERIFY_NONE, NULL);
+  wolfSSL_CTX_set_verify(wctx->ssl_ctx, conn_config->verifypeer ?
+                         WOLFSSL_VERIFY_PEER : WOLFSSL_VERIFY_NONE, NULL);
 
 #ifdef HAVE_SNI
   if(peer->sni) {
@@ -1268,7 +1448,7 @@
 #ifdef NO_FILESYSTEM
   else if(conn_config->verifypeer) {
     failf(data, "SSL: Certificates cannot be loaded because wolfSSL was built"
-          " with \"no file system\". Either disable peer verification"
+          " with no file system. Either disable peer verification"
           " (insecure) or if you are building an application with libcurl you"
           " can load certificates via CURLOPT_SSL_CTX_FUNCTION.");
     result = CURLE_SSL_CONNECT_ERROR;
@@ -1276,157 +1456,20 @@
   }
 #endif
 
-  /* Let's make an SSL structure */
-  wctx->ssl = wolfSSL_new(wctx->ssl_ctx);
-  if(!wctx->ssl) {
-    failf(data, "SSL: could not create a handle");
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
-
-#ifdef HAVE_EX_DATA
-  wolfSSL_set_app_data(wctx->ssl, ssl_user_data);
-#else
-  (void)ssl_user_data;
-#endif
-#ifdef WOLFSSL_QUIC
-  if(transport == TRNSPRT_QUIC)
-    wolfSSL_set_quic_use_legacy_codepoint(wctx->ssl, 0);
-#endif
-
+  result = wssl_init_ssl_handle(wctx, cf, data, peer, &alpns, ssl_user_data,
+                                transport,
 #ifdef WOLFSSL_HAVE_KYBER
-  if(pqkem) {
-    if(wolfSSL_UseKeyShare(wctx->ssl, pqkem) != WOLFSSL_SUCCESS) {
-      failf(data, "unable to use PQ KEM");
-    }
-  }
+                                pqkem,
 #endif
-
-  /* Check if there is a cached ID we can/should use here! */
-  if(Curl_ssl_scache_use(cf, data)) {
-    /* Set session from cache if there is one */
-    (void)wssl_setup_session(cf, data, wctx, &alpns,
-                             peer->scache_key, sess_reuse_cb);
-  }
-
-#ifdef HAVE_ALPN
-  if(alpns.count) {
-    struct alpn_proto_buf proto;
-    memset(&proto, 0, sizeof(proto));
-    Curl_alpn_to_proto_str(&proto, &alpns);
-
-    if(wolfSSL_UseALPN(wctx->ssl, (char *)proto.data,
-                       (unsigned int)proto.len,
-                       WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != WOLFSSL_SUCCESS) {
-      failf(data, "SSL: failed setting ALPN protocols");
-      result = CURLE_SSL_CONNECT_ERROR;
-      goto out;
-    }
-    CURL_TRC_CF(data, cf, "set ALPN: %s", proto.data);
-  }
-#endif /* HAVE_ALPN */
-
-#ifdef OPENSSL_EXTRA
-  if(Curl_tls_keylog_enabled()) {
-    /* Ensure the Client Random is preserved. */
-    wolfSSL_KeepArrays(wctx->ssl);
-#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13)
-    wolfSSL_set_tls13_secret_cb(wctx->ssl, wssl_tls13_secret_callback, NULL);
-#endif
-  }
-#endif /* OPENSSL_EXTRA */
-
-#ifdef HAVE_SECURE_RENEGOTIATION
-  if(wolfSSL_UseSecureRenegotiation(wctx->ssl) != SSL_SUCCESS) {
-    failf(data, "SSL: failed setting secure renegotiation");
-    result = CURLE_SSL_CONNECT_ERROR;
+                                sess_reuse_cb);
+  if(result)
     goto out;
-  }
-#endif /* HAVE_SECURE_RENEGOTIATION */
 
 #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG
-  if(ECH_ENABLED(data)) {
-    int trying_ech_now = 0;
-
-    if(data->set.str[STRING_ECH_PUBLIC]) {
-      infof(data, "ECH: outername not (yet) supported with wolfSSL");
-      result = CURLE_SSL_CONNECT_ERROR;
+  if(CURLECH_ENABLED(data)) {
+    result = wssl_init_ech(wctx, cf, data);
+    if(result)
       goto out;
-    }
-    if(data->set.tls_ech == CURLECH_GREASE) {
-      infof(data, "ECH: GREASE is done by default by wolfSSL: no need to ask");
-    }
-    if(data->set.tls_ech & CURLECH_CLA_CFG &&
-       data->set.str[STRING_ECH_CONFIG]) {
-      char *b64val = data->set.str[STRING_ECH_CONFIG];
-      word32 b64len = 0;
-
-      b64len = (word32)strlen(b64val);
-      if(b64len &&
-         wolfSSL_SetEchConfigsBase64(wctx->ssl,
-                                     b64val, b64len) != WOLFSSL_SUCCESS) {
-        if(data->set.tls_ech & CURLECH_HARD) {
-          result = CURLE_SSL_CONNECT_ERROR;
-          goto out;
-        }
-      }
-      else {
-        trying_ech_now = 1;
-        infof(data, "ECH: ECHConfig from command line");
-      }
-    }
-    else {
-      struct ssl_connect_data *connssl = cf->ctx;
-      struct Curl_dns_entry *dns = NULL;
-
-      dns = Curl_dnscache_get(data, connssl->peer.hostname, connssl->peer.port,
-                              cf->conn->ip_version);
-      if(!dns) {
-        infof(data, "ECH: requested but no DNS info available");
-        if(data->set.tls_ech & CURLECH_HARD) {
-          result = CURLE_SSL_CONNECT_ERROR;
-          goto out;
-        }
-      }
-      else {
-        struct Curl_https_rrinfo *rinfo = NULL;
-
-        rinfo = dns->hinfo;
-        if(rinfo && rinfo->echconfiglist) {
-          unsigned char *ecl = rinfo->echconfiglist;
-          size_t elen = rinfo->echconfiglist_len;
-
-          infof(data, "ECH: ECHConfig from DoH HTTPS RR");
-          if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32)elen) !=
-             WOLFSSL_SUCCESS) {
-            infof(data, "ECH: wolfSSL_SetEchConfigs failed");
-            if(data->set.tls_ech & CURLECH_HARD) {
-              result = CURLE_SSL_CONNECT_ERROR;
-              goto out;
-            }
-          }
-          else {
-            trying_ech_now = 1;
-            infof(data, "ECH: imported ECHConfigList of length %ld", elen);
-          }
-        }
-        else {
-          infof(data, "ECH: requested but no ECHConfig available");
-          if(data->set.tls_ech & CURLECH_HARD) {
-            result = CURLE_SSL_CONNECT_ERROR;
-            goto out;
-          }
-        }
-        Curl_resolv_unlink(data, &dns);
-      }
-    }
-
-    if(trying_ech_now && wolfSSL_set_min_proto_version(wctx->ssl,
-                                                       TLS1_3_VERSION) != 1) {
-      infof(data, "ECH: cannot force TLSv1.3 [ERROR]");
-      result = CURLE_SSL_CONNECT_ERROR;
-      goto out;
-    }
   }
 #endif /* HAVE_WOLFSSL_CTX_GENERATEECHCONFIG */
 
@@ -1444,6 +1487,21 @@
   return result;
 }
 
+bool Curl_wssl_need_httpsrr(struct Curl_easy *data)
+{
+#ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG
+  if(!CURLECH_ENABLED(data))
+    return FALSE;
+  if((data->set.tls_ech & CURLECH_GREASE) ||
+     (data->set.tls_ech & CURLECH_CLA_CFG))
+   return FALSE;
+  return TRUE;
+#else
+  (void)data;
+  return FALSE;
+#endif
+}
+
 /*
  * This function loads all the client/CA certificates and CRLs. Setup the TLS
  * layer and do all necessary magic.
@@ -1461,9 +1519,8 @@
   if(connssl->state == ssl_connection_complete)
     return CURLE_OK;
 
-  result = Curl_wssl_ctx_init(wssl, cf, data, &connssl->peer,
-                              connssl->alpn, NULL, NULL, cf,
-                              wssl_on_session_reuse);
+  result = Curl_wssl_ctx_init(wssl, cf, data, &connssl->peer, connssl->alpn,
+                              NULL, NULL, cf, wssl_on_session_reuse);
   if(result)
     return result;
 
@@ -1730,7 +1787,7 @@
     }
     else if(DOMAIN_NAME_MISMATCH == detail) {
       /* There is no easy way to override only the CN matching.
-       * This will enable the override of both mismatching SubjectAltNames
+       * This enables the override of both mismatching SubjectAltNames
        * as also mismatching CN fields */
       failf(data, " subject alt name(s) or common name do not match \"%s\"",
             connssl->peer.dispname);
@@ -2014,13 +2071,13 @@
     case WOLFSSL_ERROR_NONE:
     case WOLFSSL_ERROR_WANT_READ:
     case WOLFSSL_ERROR_WANT_WRITE:
-      if(!wssl->io_result && connssl->peer_closed) {
-        CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> CLOSED", blen);
-        return CURLE_OK;
+      if(!wssl->io_result && !connssl->peer_closed) {
+        /* there is data pending, re-invoke wolfSSL_read() */
+        CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen);
+        return CURLE_AGAIN;
       }
-      /* there is data pending, re-invoke wolfSSL_read() */
-      CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen);
-      return CURLE_AGAIN;
+      /* fall through to default error handling below */
+      FALLTHROUGH();
     default:
       if(wssl->io_result == CURLE_AGAIN) {
         CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen);
@@ -2047,11 +2104,7 @@
 
 size_t Curl_wssl_version(char *buffer, size_t size)
 {
-#if LIBWOLFSSL_VERSION_HEX >= 0x03006000
   return curl_msnprintf(buffer, size, "wolfSSL/%s", wolfSSL_lib_version());
-#elif defined(WOLFSSL_VERSION)
-  return curl_msnprintf(buffer, size, "wolfSSL/%s", WOLFSSL_VERSION);
-#endif
 }
 
 static int wssl_init(void)
@@ -2095,13 +2148,9 @@
 void Curl_wssl_report_handshake(struct Curl_easy *data, struct wssl_ctx *wssl)
 {
   (void)wssl;
-#if (LIBWOLFSSL_VERSION_HEX >= 0x03009010)
   infof(data, "SSL connection using %s / %s",
         wolfSSL_get_version(wssl->ssl),
         wolfSSL_get_cipher_name(wssl->ssl));
-#else
-  infof(data, "SSL connected");
-#endif
 }
 
 static CURLcode wssl_connect(struct Curl_cfilter *cf,
@@ -2122,6 +2171,15 @@
   connssl->io_need = CURL_SSL_IO_NEED_NONE;
 
   if(ssl_connect_1 == connssl->connecting_state) {
+#ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG
+    /* if we do ECH and need the HTTPS-RR information for it,
+     * we delay the connect until it arrives or DNS resolve fails. */
+    if(Curl_wssl_need_httpsrr(data) &&
+       !Curl_conn_dns_resolved_https(data, cf->sockindex)) {
+      CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect");
+      return CURLE_OK;
+    }
+#endif /* HAVE_WOLFSSL_CTX_GENERATEECHCONFIG */
     result = wssl_connect_step1(cf, data);
     if(result)
       return result;
@@ -2142,8 +2200,8 @@
   }
 
   if(ssl_connect_3 == connssl->connecting_state) {
-    /* Once the handshake has errored, it stays in that state and will
-     * error again on every call. */
+    /* Once the handshake has errored, it stays in that state and
+     * errors again on every call. */
     if(wssl->hs_result) {
       result = wssl->hs_result;
       goto out;
diff --git a/Utilities/cmcurl/lib/vtls/wolfssl.h b/Utilities/cmcurl/lib/vtls/wolfssl.h
index b097b60..2490bf3 100644
--- a/Utilities/cmcurl/lib/vtls/wolfssl.h
+++ b/Utilities/cmcurl/lib/vtls/wolfssl.h
@@ -29,6 +29,8 @@
 
 #include "urldata.h"
 
+#include <wolfssl/options.h>
+
 struct alpn_spec;
 struct ssl_peer;
 struct Curl_ssl_session;
@@ -65,12 +67,15 @@
                             struct Curl_cfilter *cf,
                             struct Curl_easy *data,
                             struct ssl_peer *peer,
-                            const struct alpn_spec *alpns,
+                            const struct alpn_spec *alpns_requested,
                             Curl_wssl_ctx_setup_cb *cb_setup,
                             void *cb_user_data,
                             void *ssl_user_data,
                             Curl_wssl_init_session_reuse_cb *sess_reuse_cb);
 
+/* Is a resolved HTTPS-RR needed for initializing wolfSSL? */
+bool Curl_wssl_need_httpsrr(struct Curl_easy *data);
+
 CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf,
                                     struct Curl_easy *data,
                                     struct wssl_ctx *wssl);
diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.c b/Utilities/cmcurl/lib/vtls/x509asn1.c
index b687a60..788dfb2 100644
--- a/Utilities/cmcurl/lib/vtls/x509asn1.c
+++ b/Utilities/cmcurl/lib/vtls/x509asn1.c
@@ -51,41 +51,6 @@
 #define CURL_ASN1_MAX                   ((size_t)0x40000)      /* 256K */
 
 #ifdef WANT_EXTRACT_CERTINFO
-/* ASN.1 classes. */
-/* #define CURL_ASN1_UNIVERSAL             0 */
-/* #define CURL_ASN1_APPLICATION           1 */
-/* #define CURL_ASN1_CONTEXT_SPECIFIC      2 */
-/* #define CURL_ASN1_PRIVATE               3 */
-
-/* ASN.1 types. */
-#define CURL_ASN1_BOOLEAN               1
-#define CURL_ASN1_INTEGER               2
-#define CURL_ASN1_BIT_STRING            3
-#define CURL_ASN1_OCTET_STRING          4
-#define CURL_ASN1_NULL                  5
-#define CURL_ASN1_OBJECT_IDENTIFIER     6
-/* #define CURL_ASN1_OBJECT_DESCRIPTOR     7 */
-/* #define CURL_ASN1_INSTANCE_OF           8 */
-/* #define CURL_ASN1_REAL                  9 */
-#define CURL_ASN1_ENUMERATED            10
-/* #define CURL_ASN1_EMBEDDED              11 */
-#define CURL_ASN1_UTF8_STRING           12
-/* #define CURL_ASN1_RELATIVE_OID          13 */
-/* #define CURL_ASN1_SEQUENCE              16 */
-/* #define CURL_ASN1_SET                   17 */
-#define CURL_ASN1_NUMERIC_STRING        18
-#define CURL_ASN1_PRINTABLE_STRING      19
-#define CURL_ASN1_TELETEX_STRING        20
-/* #define CURL_ASN1_VIDEOTEX_STRING       21 */
-#define CURL_ASN1_IA5_STRING            22
-#define CURL_ASN1_UTC_TIME              23
-#define CURL_ASN1_GENERALIZED_TIME      24
-/* #define CURL_ASN1_GRAPHIC_STRING        25 */
-#define CURL_ASN1_VISIBLE_STRING        26
-/* #define CURL_ASN1_GENERAL_STRING        27 */
-#define CURL_ASN1_UNIVERSAL_STRING      28
-/* #define CURL_ASN1_CHARACTER_STRING      29 */
-#define CURL_ASN1_BMP_STRING            30
 
 /* ASN.1 OID table entry. */
 struct Curl_OID {
@@ -160,10 +125,6 @@
  * Please note there is no pretension here to rewrite a full SSL library.
  */
 
-static const char *getASN1Element(struct Curl_asn1Element *elem,
-                                  const char *beg, const char *end)
-  WARN_UNUSED_RESULT;
-
 #define CURL_ASN1_MAX_RECURSIONS    16
 
 static const char *getASN1Element_(struct Curl_asn1Element *elem,
@@ -199,32 +160,35 @@
   b = (unsigned char)*beg++;
   if(!(b & 0x80))
     len = b;
-  else if(!(b &= 0x7F)) {
-    /* Unspecified length. Since we have all the data, we can determine the
-       effective length by skipping element until an end element is found. */
-    if(!elem->constructed)
-      return NULL;
-    elem->beg = beg;
-    while(beg < end && *beg) {
-      beg = getASN1Element_(&lelem, beg, end, lvl + 1);
-      if(!beg)
-        return NULL;
-    }
-    if(beg >= end)
-      return NULL;
-    elem->end = beg;
-    return beg + 1;
-  }
-  else if((unsigned)b > (size_t)(end - beg))
-    return NULL; /* Does not fit in source. */
   else {
-    /* Get long length. */
-    len = 0;
-    do {
-      if(len & 0xFF000000L)
-        return NULL;  /* Lengths > 32 bits are not supported. */
-      len = (len << 8) | (unsigned char) *beg++;
-    } while(--b);
+    b &= 0x7F;
+    if(!b) {
+      /* Unspecified length. Since we have all the data, we can determine the
+         effective length by skipping element until an end element is found. */
+      if(!elem->constructed)
+        return NULL;
+      elem->beg = beg;
+      while(beg < end && *beg) {
+        beg = getASN1Element_(&lelem, beg, end, lvl + 1);
+        if(!beg)
+          return NULL;
+      }
+      if(beg >= end)
+        return NULL;
+      elem->end = beg;
+      return beg + 1;
+    }
+    else if((unsigned)b > (size_t)(end - beg))
+      return NULL; /* Does not fit in source. */
+    else {
+      /* Get long length. */
+      len = 0;
+      do {
+        if(len & 0xFF000000L)
+          return NULL;  /* Lengths > 32 bits are not supported. */
+        len = (len << 8) | (unsigned char) *beg++;
+      } while(--b);
+    }
   }
   if(len > (size_t)(end - beg))
     return NULL;  /* Element data does not fit in source. */
@@ -233,8 +197,13 @@
   return elem->end;
 }
 
-static const char *getASN1Element(struct Curl_asn1Element *elem,
-                                  const char *beg, const char *end)
+/*
+ * @unittest 1657
+ */
+UNITTEST const char *getASN1Element(struct Curl_asn1Element *elem,
+                                    const char *beg, const char *end);
+UNITTEST const char *getASN1Element(struct Curl_asn1Element *elem,
+                                    const char *beg, const char *end)
 {
   return getASN1Element_(elem, beg, end, 0);
 }
@@ -255,17 +224,6 @@
   return NULL;
 }
 
-#ifdef UNITTESTS
-/* used by unit1657.c */
-CURLcode Curl_x509_getASN1Element(struct Curl_asn1Element *elem,
-                                  const char *beg, const char *end)
-{
-  if(getASN1Element(elem, beg, end))
-    return CURLE_OK;
-  return CURLE_BAD_FUNCTION_ARGUMENT;
-}
-#endif
-
 /*
  * Convert an ASN.1 Boolean value into its string representation.
  *
@@ -275,9 +233,13 @@
 static CURLcode bool2str(struct dynbuf *store,
                          const char *beg, const char *end)
 {
+  uint8_t val;
   if(end - beg != 1)
     return CURLE_BAD_FUNCTION_ARGUMENT;
-  return curlx_dyn_add(store, *beg ? "TRUE" : "FALSE");
+  val = (uint8_t)*beg;
+  if((val != 0xff) && (val != 0x00))
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  return curlx_dyn_add(store, val ? "TRUE" : "FALSE");
 }
 
 /*
@@ -300,7 +262,12 @@
 {
   /* Convert an ASN.1 bit string to a printable string. */
 
-  if(++beg > end)
+  if(beg < end) {
+    uint8_t unused_bits = (uint8_t)*beg++;
+    if(unused_bits > 7)
+      return CURLE_BAD_FUNCTION_ARGUMENT;
+  }
+  else
     return CURLE_BAD_FUNCTION_ARGUMENT;
   return octet2str(store, beg, end);
 }
@@ -312,8 +279,11 @@
  */
 static CURLcode int2str(struct dynbuf *store, const char *beg, const char *end)
 {
-  unsigned int val = 0;
+  uint32_t uval = 0;
+  int32_t sval = 0;
   size_t n = end - beg;
+  size_t i;
+  const unsigned char *p = (const unsigned char *)beg;
 
   if(!n)
     return CURLE_BAD_FUNCTION_ARGUMENT;
@@ -321,14 +291,20 @@
   if(n > 4)
     return octet2str(store, beg, end);
 
-  /* Represent integers <= 32-bit as a single value. */
-  if(*beg & 0x80)
-    val = ~val;
+  if(p[0] & 0x80)
+    uval = ~0U;
 
-  do
-    val = (val << 8) | *(const unsigned char *)beg++;
-  while(beg < end);
-  return curlx_dyn_addf(store, "%s%x", val >= 10 ? "0x" : "", val);
+  for(i = 0; i < n; i++)
+    uval = (uval << 8) | p[i];
+
+  sval = (int32_t)uval;
+
+  /* small values are easier to read as decimals */
+  if(sval > -10000 && sval < 10000)
+    return curlx_dyn_addf(store, "%d", sval);
+
+  /* represent larger values as hex */
+  return curlx_dyn_addf(store, "0x%x", uval);
 }
 
 /*
@@ -417,33 +393,66 @@
 }
 
 /*
- * Convert an ASN.1 OID into its dotted string representation.
+ * Convert an ASN.1 OID into its dotted string representation. Each field is
+ * limited to unsigned 32-bit values.
+ *
+ * A minor limitation is in the second number, which can be no larger than 32
+ * bits - 80 == 4294967215.
  *
  * Return error code.
+ *
+ * @unittest 1666
  */
-static CURLcode encodeOID(struct dynbuf *store,
-                          const char *beg, const char *end)
+UNITTEST CURLcode encodeOID(struct dynbuf *store,
+                            const char *beg, const char *end);
+UNITTEST CURLcode encodeOID(struct dynbuf *store,
+                            const char *beg, const char *end)
 {
-  unsigned int x;
-  unsigned int y;
+  uint32_t x;
+  uint32_t y;
   CURLcode result = CURLE_OK;
 
-  /* Process the first two numbers. */
-  y = *(const unsigned char *)beg++;
-  x = y / 40;
-  y -= x * 40;
+  if(end <= beg)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
 
-  result = curlx_dyn_addf(store, "%u.%u", x, y);
+  /* Process the first two numbers. The initial digit cannot be larger than
+     2 */
+  y = *(const unsigned char *)beg++;
+  if(y <= 80)
+    x = y / 40;
+  else
+    x = 2;
+  result = curlx_dyn_addf(store, "%u", x);
+  if(result)
+    return result;
+
+  if(y & 0x80) {
+    uint32_t t = (y & 0x7f); /* the second number */
+    if(!t)
+      /* reject leading 0x80 padding */
+      return CURLE_BAD_FUNCTION_ARGUMENT;
+    do {
+      if((t & 0xFE000000) || (beg >= end))
+        return CURLE_BAD_FUNCTION_ARGUMENT;
+      y = *(const unsigned char *)beg++;
+      t = (t << 7) | (y & 0x7F);
+    } while(y & 0x80);
+    y = t;
+  }
+
+  result = curlx_dyn_addf(store, ".%u", y - (x * 40));
   if(result)
     return result;
 
   /* Process the trailing numbers. */
-  while(beg < end) {
+  while((beg < end) && !result) {
     x = 0;
+    y = 0;
     do {
-      if(x & 0xFF000000)
-        return CURLE_OK;
-      else if(beg == end)
+      /* 0x80 as the first value is invalid since it only adds padding */
+      if((y & 0x80) && !x)
+        return CURLE_BAD_FUNCTION_ARGUMENT;
+      else if((x & 0xFE000000) || (beg == end))
         return CURLE_BAD_FUNCTION_ARGUMENT;
       y = *(const unsigned char *)beg++;
       x = (x << 7) | (y & 0x7F);
@@ -460,32 +469,33 @@
  */
 
 static CURLcode OID2str(struct dynbuf *store,
-                        const char *beg, const char *end, bool symbolic)
+                        const char *beg, const char *end)
 {
   CURLcode result = CURLE_OK;
   if(beg < end) {
-    if(symbolic) {
-      struct dynbuf buf;
-      curlx_dyn_init(&buf, CURL_X509_STR_MAX);
-      result = encodeOID(&buf, beg, end);
+    struct dynbuf buf;
+    curlx_dyn_init(&buf, CURL_X509_STR_MAX);
+    result = encodeOID(&buf, beg, end);
 
-      if(!result) {
-        const struct Curl_OID *op = searchOID(curlx_dyn_ptr(&buf));
-        if(op)
-          result = curlx_dyn_add(store, op->textoid);
-        else
-          result = curlx_dyn_add(store, curlx_dyn_ptr(&buf));
-      }
-      curlx_dyn_free(&buf);
+    if(!result) {
+      const struct Curl_OID *op = searchOID(curlx_dyn_ptr(&buf));
+      if(op)
+        result = curlx_dyn_add(store, op->textoid);
+      else
+        result = curlx_dyn_add(store, curlx_dyn_ptr(&buf));
     }
-    else
-      result = encodeOID(store, beg, end);
+    curlx_dyn_free(&buf);
   }
   return result;
 }
 
-static CURLcode GTime2str(struct dynbuf *store,
-                          const char *beg, const char *end)
+/*
+ * @unittest 1656
+ */
+UNITTEST CURLcode GTime2str(struct dynbuf *store,
+                            const char *beg, const char *end);
+UNITTEST CURLcode GTime2str(struct dynbuf *store,
+                            const char *beg, const char *end)
 {
   const char *tzp;
   const char *fracp;
@@ -562,15 +572,6 @@
                         sep, (int)tzl, tzp);
 }
 
-#ifdef UNITTESTS
-/* used by unit1656.c */
-CURLcode Curl_x509_GTime2str(struct dynbuf *store,
-                             const char *beg, const char *end)
-{
-  return GTime2str(store, beg, end);
-}
-#endif
-
 /*
  * Convert an ASN.1 UTC time to a printable string.
  *
@@ -604,11 +605,9 @@
     tzp = "GMT";
     end = tzp + 3;
   }
-  else
-    tzp++;
 
   tzl = end - tzp;
-  return curlx_dyn_addf(store, "%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s",
+  return curlx_dyn_addf(store, "%d%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s",
                         20 - (*beg >= '5'), beg, beg + 2, beg + 4,
                         beg + 6, beg + 8, sec,
                         (int)tzl, tzp);
@@ -618,17 +617,19 @@
  * Convert an ASN.1 element to a printable string.
  *
  * Return error
+ *
+ * @unittest 1667
  */
-static CURLcode ASN1tostr(struct dynbuf *store,
-                          struct Curl_asn1Element *elem, int type)
+UNITTEST CURLcode ASN1tostr(struct dynbuf *store,
+                            struct Curl_asn1Element *elem);
+UNITTEST CURLcode ASN1tostr(struct dynbuf *store,
+                            struct Curl_asn1Element *elem)
 {
   CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT;
+  int type = elem->tag;
   if(elem->constructed)
     return result; /* No conversion of structured elements. */
 
-  if(!type)
-    type = elem->tag;   /* Type not forced: use element tag as type. */
-
   switch(type) {
   case CURL_ASN1_BOOLEAN:
     result = bool2str(store, elem->beg, elem->end);
@@ -647,7 +648,7 @@
     result = curlx_dyn_addn(store, "", 1);
     break;
   case CURL_ASN1_OBJECT_IDENTIFIER:
-    result = OID2str(store, elem->beg, elem->end, TRUE);
+    result = OID2str(store, elem->beg, elem->end);
     break;
   case CURL_ASN1_UTC_TIME:
     result = UTime2str(store, elem->beg, elem->end);
@@ -712,7 +713,7 @@
         goto error;
       }
       curlx_dyn_reset(&temp);
-      result = ASN1tostr(&temp, &oid, 0);
+      result = ASN1tostr(&temp, &oid);
       if(result)
         goto error;
 
@@ -747,7 +748,7 @@
         goto error;
 
       /* Generate value. */
-      result = ASN1tostr(store, &value, 0);
+      result = ASN1tostr(store, &value);
       if(result)
         goto error;
       curlx_dyn_reset(&temp);
@@ -905,7 +906,7 @@
     if(!p)
       return CURLE_BAD_FUNCTION_ARGUMENT;
   }
-  return OID2str(store, oid.beg, oid.end, TRUE);
+  return OID2str(store, oid.beg, oid.end);
 }
 
 /*
@@ -956,7 +957,7 @@
 
   /* Generate a certificate information record for the public key. */
 
-  result = ASN1tostr(&out, elem, 0);
+  result = ASN1tostr(&out, elem);
   if(!result) {
     if(data->set.ssl.certinfo)
       result = ssl_push_certinfo_dyn(data, certnum, label, &out);
@@ -1144,7 +1145,7 @@
   }
 
   /* Serial number. */
-  result = ASN1tostr(&out, &cert.serialNumber, 0);
+  result = ASN1tostr(&out, &cert.serialNumber);
   if(result)
     goto done;
   if(data->set.ssl.certinfo) {
@@ -1167,7 +1168,7 @@
   curlx_dyn_reset(&out);
 
   /* Start Date. */
-  result = ASN1tostr(&out, &cert.notBefore, 0);
+  result = ASN1tostr(&out, &cert.notBefore);
   if(result)
     goto done;
   if(data->set.ssl.certinfo) {
@@ -1178,7 +1179,7 @@
   curlx_dyn_reset(&out);
 
   /* Expire Date. */
-  result = ASN1tostr(&out, &cert.notAfter, 0);
+  result = ASN1tostr(&out, &cert.notAfter);
   if(result)
     goto done;
   if(data->set.ssl.certinfo) {
@@ -1209,7 +1210,7 @@
   curlx_dyn_reset(&out);
 
   /* Signature. */
-  result = ASN1tostr(&out, &cert.signature, 0);
+  result = ASN1tostr(&out, &cert.signature);
   if(result)
     goto done;
   if(data->set.ssl.certinfo) {
diff --git a/Utilities/cmcurl/lib/vtls/x509asn1.h b/Utilities/cmcurl/lib/vtls/x509asn1.h
index 829dfd8..dda55ac 100644
--- a/Utilities/cmcurl/lib/vtls/x509asn1.h
+++ b/Utilities/cmcurl/lib/vtls/x509asn1.h
@@ -26,12 +26,51 @@
  ***************************************************************************/
 #include "curl_setup.h"
 
+/* forward-declare to make it visible in all builds */
+struct Curl_asn1Element;
+
 #if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || \
   defined(USE_MBEDTLS) || defined(USE_RUSTLS)
 
 #include "cfilters.h"
 #include "urldata.h"
 
+/* ASN.1 classes. */
+/* #define CURL_ASN1_UNIVERSAL             0 */
+/* #define CURL_ASN1_APPLICATION           1 */
+/* #define CURL_ASN1_CONTEXT_SPECIFIC      2 */
+/* #define CURL_ASN1_PRIVATE               3 */
+
+/* ASN.1 types. */
+#define CURL_ASN1_BOOLEAN               1
+#define CURL_ASN1_INTEGER               2
+#define CURL_ASN1_BIT_STRING            3
+#define CURL_ASN1_OCTET_STRING          4
+#define CURL_ASN1_NULL                  5
+#define CURL_ASN1_OBJECT_IDENTIFIER     6
+/* #define CURL_ASN1_OBJECT_DESCRIPTOR     7 */
+/* #define CURL_ASN1_INSTANCE_OF           8 */
+/* #define CURL_ASN1_REAL                  9 */
+#define CURL_ASN1_ENUMERATED            10
+/* #define CURL_ASN1_EMBEDDED              11 */
+#define CURL_ASN1_UTF8_STRING           12
+/* #define CURL_ASN1_RELATIVE_OID          13 */
+/* #define CURL_ASN1_SEQUENCE              16 */
+/* #define CURL_ASN1_SET                   17 */
+#define CURL_ASN1_NUMERIC_STRING        18
+#define CURL_ASN1_PRINTABLE_STRING      19
+#define CURL_ASN1_TELETEX_STRING        20
+/* #define CURL_ASN1_VIDEOTEX_STRING       21 */
+#define CURL_ASN1_IA5_STRING            22
+#define CURL_ASN1_UTC_TIME              23
+#define CURL_ASN1_GENERALIZED_TIME      24
+/* #define CURL_ASN1_GRAPHIC_STRING        25 */
+#define CURL_ASN1_VISIBLE_STRING        26
+/* #define CURL_ASN1_GENERAL_STRING        27 */
+#define CURL_ASN1_UNIVERSAL_STRING      28
+/* #define CURL_ASN1_CHARACTER_STRING      29 */
+#define CURL_ASN1_BMP_STRING            30
+
 /*
  * Types.
  */
@@ -75,20 +114,6 @@
                                const char *beg, const char *end);
 CURLcode Curl_verifyhost(struct Curl_cfilter *cf, struct Curl_easy *data,
                          const char *beg, const char *end);
-
-#ifdef UNITTESTS
-#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_MBEDTLS) || \
-  defined(USE_RUSTLS)
-
-/* used by unit1656.c */
-CURLcode Curl_x509_GTime2str(struct dynbuf *store,
-                             const char *beg, const char *end);
-/* used by unit1657.c */
-CURLcode Curl_x509_getASN1Element(struct Curl_asn1Element *elem,
-                                  const char *beg, const char *end);
-#endif /* USE_GNUTLS || USE_SCHANNEL || USE_MBEDTLS || RUSTLS */
-#endif /* UNITTESTS */
-
 #endif /* USE_GNUTLS || USE_WOLFSSL || USE_SCHANNEL || USE_MBEDTLS ||
           USE_RUSTLS */
 #endif /* HEADER_CURL_X509ASN1_H */
diff --git a/Utilities/cmcurl/lib/ws.c b/Utilities/cmcurl/lib/ws.c
index 6046561..d7840f1 100644
--- a/Utilities/cmcurl/lib/ws.c
+++ b/Utilities/cmcurl/lib/ws.c
@@ -116,8 +116,8 @@
  * and keep track of boundaries. */
 struct websocket {
   struct Curl_easy *data; /* used for write callback handling */
-  struct ws_decoder dec;  /* decode of we frames */
-  struct ws_encoder enc;  /* decode of we frames */
+  struct ws_decoder dec;  /* decode of ws frames */
+  struct ws_encoder enc;  /* encode of ws frames */
   struct bufq recvbuf;    /* raw data from the server */
   struct bufq sendbuf;    /* raw data to be sent to the server */
   struct curl_ws_frame recvframe;  /* the current WS FRAME received */
@@ -564,7 +564,7 @@
     dec->state = WS_DEC_INIT;
     break;
   default:
-    /* we covered all enums above, but some code analyzers are whimps */
+    /* we covered all enums above, but some code analyzers are wimps */
     result = CURLE_FAILED_INIT;
   }
   return result;
@@ -758,7 +758,7 @@
   }
 
   if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) {
-    failf(data, "[WS] decode ending with %zd frame bytes remaining",
+    failf(data, "[WS] decode ending with %zu frame bytes remaining",
           Curl_bufq_len(&ctx->buf));
     return CURLE_RECV_ERROR;
   }
@@ -1049,9 +1049,9 @@
     if((curl_off_t)buflen >
        (ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) {
       /* too large buflen beyond payload length of frame */
-      failf(data, "[WS] unaligned frame size (sending %zu instead of %"
-                  FMT_OFF_T ")",
-            buflen, ws->enc.payload_remain + ws->sendbuf_payload);
+      failf(data, "[WS] unaligned frame size (sending %zu instead of "
+            "%" FMT_OFF_T ")", buflen,
+            (curl_off_t)(ws->enc.payload_remain + ws->sendbuf_payload));
       return CURLE_BAD_FUNCTION_ARGUMENT;
     }
   }
@@ -1399,7 +1399,7 @@
     if(result)
       goto out;
     DEBUGASSERT(nread == nwritten);
-    k->keepon &= ~KEEP_RECV; /* read no more content */
+    CURL_REQ_CLEAR_RECV(data); /* read no more content */
   }
   else { /* !connect_only */
     if(data->set.method == HTTPREQ_PUT) {
@@ -1409,7 +1409,7 @@
         goto out;
 
       if(!data->set.ws_raw_mode) {
-        /* Add our client readerr encoding WS BINARY frames */
+        /* Add our client reader encoding WS BINARY frames */
         result = Curl_creader_create(&ws_enc_reader, data, &ws_cr_encode,
                                      CURL_CR_CONTENT_ENCODE);
         if(result)
@@ -1423,10 +1423,10 @@
       /* start over with sending */
       data->req.eos_read = FALSE;
       data->req.upload_done = FALSE;
-      k->keepon |= KEEP_SEND;
+      CURL_REQ_SET_SEND(data);
     }
 
-    /* And pass any additional data to the writers */
+    /* Then pass any additional data to the writers */
     if(nread) {
       result = Curl_client_write(data, CLIENTWRITE_BODY, mem, nread);
       if(result)
@@ -1527,16 +1527,16 @@
   return curl_easy_recv(data, buf, buflen, pnread);
 }
 
-CURLcode curl_ws_recv(CURL *d, void *buffer,
-                      size_t buflen, size_t *nread,
+CURLcode curl_ws_recv(CURL *curl, void *buffer,
+                      size_t buflen, size_t *recv,
                       const struct curl_ws_frame **metap)
 {
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   struct connectdata *conn;
   struct websocket *ws;
   struct ws_collect ctx;
 
-  *nread = 0;
+  *recv = 0;
   *metap = NULL;
   if(!GOOD_EASY_HANDLE(data) || (buflen && !buffer))
     return CURLE_BAD_FUNCTION_ARGUMENT;
@@ -1609,10 +1609,10 @@
   update_meta(ws, ctx.frame_age, ctx.frame_flags, ctx.payload_offset,
               ctx.payload_len, ctx.bufidx);
   *metap = &ws->recvframe;
-  *nread = ws->recvframe.len;
+  *recv = ws->recvframe.len;
   CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %"
               FMT_OFF_T ", %" FMT_OFF_T " left)",
-              buflen, *nread, ws->recvframe.offset,
+              buflen, *recv, ws->recvframe.offset,
               ws->recvframe.bytesleft);
   /* all's well, try to send any pending control. we do not know
    * when the application will call `curl_ws_send()` again. */
@@ -1689,6 +1689,9 @@
   CURLcode result = CURLE_OK;
   size_t nwritten;
 
+  if(!data)
+    return result;
+
   (void)ws;
   while(buflen) {
     result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten);
@@ -1746,6 +1749,8 @@
     if(result)
       return result;
     result = ws_send_raw_blocking(data, ws, buffer, buflen);
+    if(!result)
+      *pnwritten = buflen;
   }
   else {
     /* We need any pending data to be sent or EAGAIN this call. */
@@ -1760,7 +1765,7 @@
   return result;
 }
 
-CURLcode curl_ws_send(CURL *d, const void *buffer_arg,
+CURLcode curl_ws_send(CURL *curl, const void *buffer_arg,
                       size_t buflen, size_t *sent,
                       curl_off_t fragsize,
                       unsigned int flags)
@@ -1768,7 +1773,7 @@
   struct websocket *ws;
   const uint8_t *buffer = buffer_arg;
   CURLcode result = CURLE_OK;
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   size_t ndummy;
   size_t *pnsent = sent ? sent : &ndummy;
 
@@ -1848,11 +1853,11 @@
   return Curl_http_setup_conn(data, conn);
 }
 
-const struct curl_ws_frame *curl_ws_meta(CURL *d)
+const struct curl_ws_frame *curl_ws_meta(CURL *curl)
 {
   /* we only return something for websocket, called from within the callback
      when not using raw mode */
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
   if(GOOD_EASY_HANDLE(data) && Curl_is_in_callback(data) &&
      data->conn && !data->set.ws_raw_mode) {
     struct websocket *ws;
@@ -1863,16 +1868,17 @@
   return NULL;
 }
 
-CURL_EXTERN CURLcode curl_ws_start_frame(CURL *d,
+CURL_EXTERN CURLcode curl_ws_start_frame(CURL *curl,
                                          unsigned int flags,
                                          curl_off_t frame_len)
 {
   struct websocket *ws;
   CURLcode result = CURLE_OK;
-  struct Curl_easy *data = d;
+  struct Curl_easy *data = curl;
 
   if(!GOOD_EASY_HANDLE(data))
     return CURLE_BAD_FUNCTION_ARGUMENT;
+
   if(data->set.ws_raw_mode) {
     failf(data, "cannot curl_ws_start_frame() with CURLWS_RAW_MODE enabled");
     return CURLE_FAILED_INIT;
@@ -1893,12 +1899,6 @@
     goto out;
   }
 
-  if(data->set.ws_raw_mode) {
-    failf(data, "[WS] cannot start frame in raw mode");
-    result = CURLE_SEND_ERROR;
-    goto out;
-  }
-
   if(ws->enc.payload_remain) {
     failf(data, "[WS] previous frame not finished");
     result = CURLE_SEND_ERROR;
@@ -1915,7 +1915,7 @@
   return result;
 }
 
-static const struct Curl_protocol Curl_protocol_ws = {
+const struct Curl_protocol Curl_protocol_ws = {
   ws_setup_conn,                        /* setup_connection */
   Curl_http,                            /* do_it */
   Curl_http_done,                       /* done */
@@ -1930,7 +1930,7 @@
   ZERO_NULL,                            /* disconnect */
   Curl_http_write_resp,                 /* write_resp */
   Curl_http_write_resp_hd,              /* write_resp_hd */
-  ZERO_NULL,                            /* connection_check */
+  ZERO_NULL,                            /* connection_is_dead */
   ZERO_NULL,                            /* attach connection */
   Curl_http_follow,                     /* follow */
 };
@@ -1938,13 +1938,13 @@
 #else
 
 CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
-                      size_t *nread,
+                      size_t *recv,
                       const struct curl_ws_frame **metap)
 {
   (void)curl;
   (void)buffer;
   (void)buflen;
-  (void)nread;
+  (void)recv;
   (void)metap;
   return CURLE_NOT_BUILT_IN;
 }
@@ -1980,32 +1980,3 @@
 }
 
 #endif /* !CURL_DISABLE_WEBSOCKETS */
-
-const struct Curl_scheme Curl_scheme_ws = {
-  "WS",                                 /* scheme */
-#ifdef CURL_DISABLE_WEBSOCKETS
-  ZERO_NULL,
-#else
-  &Curl_protocol_ws,
-#endif
-  CURLPROTO_WS,                         /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_CREDSPERREQUEST |             /* flags */
-  PROTOPT_USERPWDCTRL,
-  PORT_HTTP                             /* defport */
-}
-;
-
-const struct Curl_scheme Curl_scheme_wss = {
-  "WSS",                                /* scheme */
-#if defined(CURL_DISABLE_WEBSOCKETS) || !defined(USE_SSL)
-  ZERO_NULL,
-#else
-  &Curl_protocol_ws,
-#endif
-  CURLPROTO_WSS,                        /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */
-  PROTOPT_USERPWDCTRL,
-  PORT_HTTPS                            /* defport */
-};
diff --git a/Utilities/cmcurl/lib/ws.h b/Utilities/cmcurl/lib/ws.h
index 757eae0..71b5b3f 100644
--- a/Utilities/cmcurl/lib/ws.h
+++ b/Utilities/cmcurl/lib/ws.h
@@ -25,11 +25,10 @@
  ***************************************************************************/
 #include "curl_setup.h"
 
-extern const struct Curl_scheme Curl_scheme_ws;
-extern const struct Curl_scheme Curl_scheme_wss;
-
 #if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP)
 
+extern const struct Curl_protocol Curl_protocol_ws;
+
 /* meta key for storing protocol meta at connection */
 #define CURL_META_PROTO_WS_CONN   "meta:proto:ws:conn"