diff --git a/.circleci/config.yml b/.circleci/config.yml index 2f16bebe3b45..db79f85fcc57 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,6 +23,7 @@ ########################################################################### # View these jobs in the browser: https://app.circleci.com/pipelines/github/curl/curl +# Images: https://circleci.com/developer/images?imageType=machine # Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/reference/configuration-reference/ version: 2.1 @@ -45,9 +46,7 @@ commands: steps: - run: command: | - sudo apt-get update && sudo apt-get install -y libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev python3-pip - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt + sudo apt-get update && sudo apt-get install -y libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev configure: steps: @@ -104,7 +103,6 @@ commands: steps: - run: command: | - source ~/venv/bin/activate # Revert a CircleCI-specific local setting that makes test 1459 # return 67 (CURLE_LOGIN_DENIED) instead of the # expected 60 (CURLE_PEER_FAILED_VERIFICATION). @@ -114,7 +112,7 @@ commands: executors: ubuntu: machine: - image: ubuntu-2204:2026.05.1 + image: ubuntu-2204:2026.05.1 # manually bumped jobs: basic: @@ -157,7 +155,7 @@ jobs: arm: machine: - image: ubuntu-2204:2026.05.1 + image: ubuntu-2204:2026.05.1 # manually bumped resource_class: arm.medium steps: - checkout @@ -168,7 +166,7 @@ jobs: arm-cares: machine: - image: ubuntu-2204:2026.05.1 + image: ubuntu-2204:2026.05.1 # manually bumped resource_class: arm.medium steps: - checkout diff --git a/.clang-tidy.yml b/.clang-tidy.yml index 3ea2c6a88a73..6685094fd333 100644 --- a/.clang-tidy.yml +++ b/.clang-tidy.yml @@ -48,7 +48,8 @@ Checks: - readability-redundant-control-flow - readability-redundant-declaration - readability-redundant-function-ptr-dereference - - readability-redundant-parentheses + # readability-redundant-nested-if # would need local suppression in a handful of cases + # - readability-redundant-parentheses - readability-redundant-preprocessor - readability-suspicious-call-argument - readability-uppercase-literal-suffix diff --git a/.github/actions/pkg-install/action.yml b/.github/actions/pkg-install/action.yml new file mode 100644 index 000000000000..5999994045bd --- /dev/null +++ b/.github/actions/pkg-install/action.yml @@ -0,0 +1,68 @@ +# Copyright (C) Daniel Stenberg, , et al. +# +# SPDX-License-Identifier: curl + +# https://docs.github.com/en/actions/tutorials/create-actions/create-a-composite-action +name: 'Install packages' +description: 'Installs packages for local workflows' + +inputs: + apt: + description: 'Optional space-separated list of apt package names' + brew: + description: 'Optional space-separated list of Homebrew package names' + pip: + description: 'Optional pip command-line options' + +runs: + using: 'composite' + steps: + - name: 'install (apt)' + if: ${{ inputs.apt }} + shell: bash + env: + PACKAGES: ${{ inputs.apt }} + run: | + sudo rm -f /etc/apt/apt.conf.d/20apt-esm-hook.conf || true + ls -l /etc/apt/sources.list.d + sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print + sudo sed -i '/\/archive\.ubuntu\.com/d' /etc/apt/apt-mirrors.txt + sudo sed -i '/\/security\.ubuntu\.com/d' /etc/apt/apt-mirrors.txt + # sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt + cat /etc/apt/apt-mirrors.txt + [[ "${PACKAGES}" = *':i386'* ]] && sudo dpkg --add-architecture i386 + sudo apt-get -o Dpkg::Use-Pty=0 update + sudo apt-get -o Dpkg::Use-Pty=0 install ${PACKAGES} + + - name: 'install (brew)' + if: ${{ inputs.brew }} + shell: bash + env: + HOMEBREW_NO_INSTALL_CLEANUP: '1' + PACKAGES: ${{ inputs.brew }} + run: | + if [[ "$(uname)" != *'Darwin'* ]]; then + brewroot='/home/linuxbrew/.linuxbrew/bin/' + fi + # Run this command with retries because of spurious failures seen while running the tests. + # shellcheck disable=SC2181 + while [[ $? == 0 ]]; do + for i in 1 2 3; do + if ${brewroot}brew install ${PACKAGES}; then + break 2 + else + echo "Error: wait to try again: $i" + sleep 10 + fi + done + false Too many retries + done + + - name: 'install (pip)' + if: ${{ inputs.pip }} + shell: bash + env: + PACKAGES: ${{ inputs.pip }} + run: | + [ -d ~/venv ] || python3 -m venv ~/venv + ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary ${PACKAGES} diff --git a/.github/egress-firewall.yaml b/.github/egress-firewall.yaml new file mode 100644 index 000000000000..1cf7ba127a7e --- /dev/null +++ b/.github/egress-firewall.yaml @@ -0,0 +1,15 @@ +# Copyright (C) Viktor Szakats +# +# SPDX-License-Identifier: curl + +# https://github.com/github-early-access/actions-native-egress-firewall + +# Defaults: +# - github.com +# - [...]blob.core.windows.net +# - codeload.github.com +# - [...]actions.githubusercontent.com + +mode: enforce +allow: + - api.github.com diff --git a/.github/labeler.yml b/.github/labeler.yml index 706c2d1d0648..f06ef69df698 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -454,14 +454,6 @@ script: scripts/**\ }" -SMB: - - all: - - changed-files: - - any-glob-to-all-files: "{\ - lib/smb.*,\ - tests/smbserver.py\ - }" - SMTP: - all: - changed-files: @@ -564,7 +556,7 @@ Windows: appveyor.*,\ CMake/win32-cache.cmake,\ lib/*win32*,\ - lib/curlx/fopen.*,\ + lib/curlx/win32-fopen.*,\ lib/curlx/multibyte.*,\ lib/curlx/winapi.*,\ lib/libcurl.def,\ diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index ab30d2a82701..000000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (C) Daniel Stenberg, , et al. -# -# SPDX-License-Identifier: curl - -# Configuration for lock-threads - https://github.com/dessant/lock-threads - -# Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 90 -# Comment to post before locking. Set to `false` to disable -lockComment: false -# Limit to only `issues` or `pulls` -# only: issues diff --git a/renovate.json b/.github/renovate.json similarity index 83% rename from renovate.json rename to .github/renovate.json index 86a85e41eb98..a11837b183f0 100644 --- a/renovate.json +++ b/.github/renovate.json @@ -65,6 +65,16 @@ "schedule": [ "* * 10 * *" ] + }, + { + "description": "Schedule package updates on the 10th of January", + "matchSourceUrls": [ + "**/amissl/**" + ], + "groupName": "yearly updates by URL", + "schedule": [ + "* * 10 1 *" + ] } ], "customManagers": [ @@ -79,19 +89,6 @@ ], "versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}", "extractVersionTemplate": "{{#if extractVersion}}{{{extractVersion}}}{{else}}^v?(?.+)${{/if}}" - }, - { - "customType": "regex", - "managerFilePatterns": [ - "/^\\.github/workflows/linux\\.yml$/" - ], - "matchStrings": [ - "OPENLDAP_VERSION: (?.*)\\s" - ], - "datasourceTemplate": "github-tags", - "depNameTemplate": "openldap/openldap", - "versioningTemplate": "semver", - "extractVersionTemplate": "^OPENLDAP_REL_ENG_(?\\d+)_(?\\d+)_(?\\d+)$" } ] } diff --git a/.github/scripts/cmp-config.pl b/.github/scripts/cmp-config.pl index a0d33498a75f..b11cd00e1349 100755 --- a/.github/scripts/cmp-config.pl +++ b/.github/scripts/cmp-config.pl @@ -38,6 +38,7 @@ my %remove = ( '#define CURL_EXTERN_SYMBOL' => 1, '#define CURL_OS "Linux"' => 1, + '#define CURL_OS "aarch64-unknown-linux-gnu"' => 1, '#define CURL_OS "x86_64-pc-linux-gnu"' => 1, '#define GETHOSTNAME_TYPE_ARG2 int' => 1, '#define GETHOSTNAME_TYPE_ARG2 size_t' => 1, diff --git a/.github/scripts/distfiles.sh b/.github/scripts/distfiles.sh index 1fb7d7e31034..963258409798 100755 --- a/.github/scripts/distfiles.sh +++ b/.github/scripts/distfiles.sh @@ -15,7 +15,6 @@ gitonly=".git* ^appveyor.* ^GIT-INFO.md ^README.md -^renovate.json ^REUSE.toml ^SECURITY.md ^LICENSES/* diff --git a/.github/scripts/pyspelling.words b/.github/scripts/pyspelling.words index ce0f4cbfca3b..41c48d85ced1 100644 --- a/.github/scripts/pyspelling.words +++ b/.github/scripts/pyspelling.words @@ -393,7 +393,6 @@ IMAP imap IMAPS imaps -impacket implementers INI init @@ -792,9 +791,7 @@ slist sln Slowloris SMB -smb SMBS -smbs SMBv SMTP smtp diff --git a/.github/scripts/requirements-docs.txt b/.github/scripts/requirements-docs.txt index 6850461de7bc..6379c6ef352c 100644 --- a/.github/scripts/requirements-docs.txt +++ b/.github/scripts/requirements-docs.txt @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: curl -pyspelling==2.12.1 +pyspelling==2.13 diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index e51ba9850e17..9548cd23d233 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -4,6 +4,5 @@ cmakelang==0.6.13 codespell==2.4.3 -pyrefly==1.2.0 reuse==6.2.0 -ruff==0.16.5 +ruff==0.17.0 diff --git a/.github/scripts/shellcheck-ci.sh b/.github/scripts/shellcheck-ci.sh index 87e03b9d12b8..fa04fe273aa6 100755 --- a/.github/scripts/shellcheck-ci.sh +++ b/.github/scripts/shellcheck-ci.sh @@ -7,16 +7,18 @@ set -eu +cd -- "$(dirname "${0}")"/../.. + export SHELLCHECK_OPTS='--exclude=1090,1091,2086,2153 --enable=avoid-nullary-conditions,deprecate-which' # GHA -git ls-files '.github/workflows/*.yml' | while read -r f; do +git ls-files '.github/actions/**/*.yml' '.github/workflows/*.yml' | while read -r f; do echo "Verifying ${f}..." { echo '#!/usr/bin/env bash' echo 'set -eu' yq eval '.. | select(has("run") and (.run | type == "!!str")) | .run + "\ntrue\n"' "${f}" - } | sed -E 's|\$\{\{ .+ \}\}|GHA_EXPRESSION|g' | shellcheck - + } | shellcheck - done # Circle CI diff --git a/.github/scripts/verify-examples.pl b/.github/scripts/verify-examples.pl index 8aeda5ef7d03..fe576afd4df7 100755 --- a/.github/scripts/verify-examples.pl +++ b/.github/scripts/verify-examples.pl @@ -27,7 +27,7 @@ use warnings; my @files = @ARGV; -my $cfile = "test.c"; +my $cfile = 'test-' . (defined $ENV{'CC'} ? $ENV{'CC'} : 'gcc') . '.c'; my $check = "./scripts/checksrc.pl"; my $error = 0; @@ -42,14 +42,14 @@ sub testcompile { if(defined $ENV{'CFLAGS'}) { $cflags .= ' ' . $ENV{'CFLAGS'}; } - my $rc = system($cc . ' -c test.c -I include -W -Wall -pedantic -Werror ' . $cflags . ' ' . + my $rc = system($cc . ' -c ' . $cfile . ' -I include -W -Wall -pedantic -Werror ' . $cflags . ' ' . '-Wno-unused-parameter -Wno-unused-but-set-variable ' . '-DCURL_ALLOW_OLD_MULTI_SOCKET -DCURL_DISABLE_DEPRECATION') >> 8; return $rc; } sub checksrc { - my $rc = system($check, ('test.c')) >> 8; + my $rc = system($check, ($cfile)) >> 8; return $rc; } diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 1c2b57c2cf90..000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (C) Daniel Stenberg, , et al. -# -# SPDX-License-Identifier: curl - -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 180 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 14 -# Issues with these labels are never considered stale -exemptLabels: - - pinned - - security -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It is closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/appveyor-status.yml b/.github/workflows/appveyor-status.yml index c821771fc43a..5dc51a4b3464 100644 --- a/.github/workflows/appveyor-status.yml +++ b/.github/workflows/appveyor-status.yml @@ -12,6 +12,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none jobs: split: diff --git a/.github/workflows/checkdocs.yml b/.github/workflows/checkdocs.yml index 7eca7305ac86..d6031d59598a 100644 --- a/.github/workflows/checkdocs.yml +++ b/.github/workflows/checkdocs.yml @@ -35,6 +35,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' @@ -49,69 +50,75 @@ jobs: with: persist-credentials: false + - name: 'trim headers off all *.md files' + background: true + run: git ls-files '*.md' -z | xargs -0 -n1 .github/scripts/trimmarkdownheader.pl + - name: 'install prereqs' - run: | - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r .github/scripts/requirements-proselint.txt + uses: $/.github/actions/pkg-install + with: + pip: -r .github/scripts/requirements-proselint.txt - name: 'trim headers off all *.md files' - run: git ls-files '*.md' -z | xargs -0 -n1 .github/scripts/trimmarkdownheader.pl + wait-all: - - name: 'check prose' + - name: 'check special prose' # For CHECKSRC and files with aggressive exclamation mark needs + background: true run: | - cat < ~/.proselintrc.json + cat < ~/.proselintrc-special.json { "checks": { "annotations.misc": false, "lexical_illusions": false, - "misc.annotations": false, - "redundancy.misc.garner": false, - "security.password": false, - "spelling.ve_of": false, "typography.diacritical_marks": false, + "typography.punctuation.exclamation": false, "typography.symbols": false } } JSON source ~/venv/bin/activate - git ls-files README '*.md' -z | grep -Evz '(CHECKSRC|DISTROS|CURLOPT_INTERFACE|interface)\.md' | xargs -0 proselint check -- + proselint check --config ~/.proselintrc-special.json docs/internals/CHECKSRC.md docs/libcurl/opts/CURLOPT_INTERFACE.md docs/cmdline-opts/interface.md - - name: 'check special prose' # For CHECKSRC and files with aggressive exclamation mark needs + - name: 'check prose' run: | - cat < ~/.proselintrc.json + cat < ~/.proselintrc-base.json { "checks": { "annotations.misc": false, "lexical_illusions": false, + "misc.annotations": false, + "redundancy.misc.garner": false, + "security.password": false, + "spelling.ve_of": false, "typography.diacritical_marks": false, - "typography.punctuation.exclamation": false, "typography.symbols": false } } JSON source ~/venv/bin/activate - proselint check docs/internals/CHECKSRC.md docs/libcurl/opts/CURLOPT_INTERFACE.md docs/cmdline-opts/interface.md + git ls-files README '*.md' -z | grep -Evz '(CHECKSRC|DISTROS|CURLOPT_INTERFACE|interface)\.md' | xargs -0 proselint check --config ~/.proselintrc-base.json -- pyspelling: name: 'pyspelling' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: 'trim all *.md files in docs/' + background: true run: .github/scripts/cleancmd.pl 'docs/*.md' - - name: 'install' + - name: 'install prereqs' timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install aspell aspell-en - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r .github/scripts/requirements-docs.txt + uses: $/.github/actions/pkg-install + with: + apt: aspell aspell-en + pip: -r .github/scripts/requirements-docs.txt + + - name: 'trim all *.md files in docs/' + wait-all: - name: 'check spelling' run: | @@ -131,9 +138,11 @@ jobs: persist-credentials: false - name: 'verify synopsis' + background: true run: .github/scripts/verify-synopsis.pl docs/libcurl/curl*.md - name: 'verify examples (gcc)' + background: true run: | cmake -B bld-gcc -G Ninja -D_CURL_PREFILL=ON -DCURL_USE_LIBPSL=OFF -DCURL_WERROR=ON -D_CURL_SAVE_PICKY_OPTIONS=ON export CFLAGS; CFLAGS="$(cat bld-gcc/picky-options.txt)" diff --git a/.github/workflows/checksrc.yml b/.github/workflows/checksrc.yml index 2d8bb26a3541..b9035b933fde 100644 --- a/.github/workflows/checksrc.yml +++ b/.github/workflows/checksrc.yml @@ -30,10 +30,10 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' jobs: checksrc: @@ -55,62 +55,77 @@ jobs: with: persist-credentials: false + - name: 'cmake options in documentation' + background: true + run: scripts/cmakeopts.sh + + - name: 'labelercheck' + background: true + run: .github/scripts/labelercheck.sh + + - name: 'perlcheck' + background: true + run: scripts/perlcheck.sh + + - name: 'install brew packages' + background: true + timeout-minutes: 2 + uses: $/.github/actions/pkg-install + with: + brew: pyrefly typos-cli + - name: 'install prereqs' - run: | - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary \ - -r .github/scripts/requirements.txt \ - -r tests/http/requirements.txt \ - -r tests/requirements.txt + uses: $/.github/actions/pkg-install + with: + pip: | + -r .github/scripts/requirements.txt + -r tests/http/requirements.txt - name: 'REUSE check' + background: true run: | source ~/venv/bin/activate reuse lint - name: 'cmakelint' + background: true run: | source ~/venv/bin/activate scripts/cmakelint.sh - - name: 'cmake options in documentation' - run: scripts/cmakeopts.sh - - - name: 'labelercheck' - run: .github/scripts/labelercheck.sh - - - name: 'perlcheck' - run: scripts/perlcheck.sh - - name: 'ruff' + background: true run: | source ~/venv/bin/activate scripts/pythonlint.sh + - name: 'wait-all' + wait-all: + - name: 'pyrefly' + background: true run: | - source ~/venv/bin/activate + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" cd tests/http/ pyrefly check --python-version=3.8 --preset=basic --check-unannotated-defs --summary - name: filter off code from all C and H files run: | - git ls-files -z '**.[ch]' | xargs -0 -r -n1 ./.github/scripts/c-strip - - - name: 'codespell' - run: | - source ~/venv/bin/activate - codespell --version - .github/scripts/codespell.sh + git ls-files -z '**.[ch]' | xargs -0 -r -n1 .github/scripts/c-strip - name: 'typos' - timeout-minutes: 2 + background: true run: | - /home/linuxbrew/.linuxbrew/bin/brew install typos-cli eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" typos --version .github/scripts/typos.sh + - name: 'codespell' + run: | + source ~/venv/bin/activate + codespell --version + .github/scripts/codespell.sh + pytype: name: 'pytype' runs-on: ubuntu-24.04-arm # pytype is discontinued and requires python 3.8-3.12 @@ -120,12 +135,12 @@ jobs: persist-credentials: false - name: 'install prereqs' - run: | - python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary pytype==2024.10.11 \ - -r .github/scripts/requirements.txt \ - -r tests/http/requirements.txt \ - -r tests/requirements.txt + uses: $/.github/actions/pkg-install + with: + pip: | + pytype==2024.10.11 + -r .github/scripts/requirements.txt + -r tests/http/requirements.txt - name: 'check' run: | @@ -139,12 +154,9 @@ jobs: steps: - name: 'install pmccabe' timeout-minutes: 2 - run: | - ls -l /etc/apt/sources.list.d - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install pmccabe + uses: $/.github/actions/pkg-install + with: + apt: pmccabe - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -163,11 +175,9 @@ jobs: steps: - name: 'install prereqs' timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install libxml2-utils + uses: $/.github/actions/pkg-install + with: + apt: libxml2-utils - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -183,53 +193,54 @@ jobs: steps: - name: 'install prereqs' timeout-minutes: 2 - run: /home/linuxbrew/.linuxbrew/bin/brew install actionlint shellcheck zizmor + uses: $/.github/actions/pkg-install + with: + brew: shellcheck zizmor - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - name: 'zizmor/actionlint (prepare)' + - name: 'zizmor (prepare)' # Replace custom bash-like shell designators with the standard one to make linters process them run: sed -i.bak -E 's/shell\x3a .+ zizmor\x3a ignore.+$/shell\x3a bash/g' .github/workflows/*.yml - name: 'zizmor GHA' + background: true env: GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" - zizmor --persona pedantic .github/workflows/*.yml .github/dependabot.yml + git ls-files '.github/actions/**/*.yml' '.github/dependabot.yml' '.github/workflows/*.yml' -z | xargs -0 -r zizmor --persona pedantic -- - name: 'zizmor GHA (auditor, warning-only)' + background: true env: GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" - zizmor --persona auditor .github/workflows/*.yml .github/dependabot.yml || true - - - name: 'actionlint' - run: | - eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" - export SHELLCHECK_OPTS='--exclude=1090,1091,2086,2153 --enable=avoid-nullary-conditions,deprecate-which' - actionlint --version - actionlint --ignore matrix --ignore ubuntu-26.04 .github/workflows/*.yml + git ls-files '.github/actions/**/*.yml' '.github/dependabot.yml' '.github/workflows/*.yml' -z | xargs -0 -r zizmor --persona auditor -- || true - name: 'shellcheck CI' + background: true run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" shellcheck --version .github/scripts/shellcheck-ci.sh - name: 'shellcheck' + background: true run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" shellcheck --version .github/scripts/shellcheck.sh - name: 'spacecheck' + background: true run: scripts/spacecheck.pl - name: 'yamlcheck' + background: true run: .github/scripts/yamlcheck.sh - name: 'badwords' diff --git a/.github/workflows/checkurls.yml b/.github/workflows/checkurls.yml index 1842b975f42b..7a1de25ef26b 100644 --- a/.github/workflows/checkurls.yml +++ b/.github/workflows/checkurls.yml @@ -22,6 +22,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 03aa9ba9d6e4..df04be0ebc85 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,16 +34,16 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' jobs: gha_python: if: ${{ github.repository_owner == 'curl' || github.event_name != 'schedule' }} name: 'GHA and Python' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm permissions: security-events: write # To create/update security events steps: @@ -52,18 +52,18 @@ jobs: persist-credentials: false - name: 'initialize' - uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: languages: actions, python queries: security-extended - name: 'perform analysis' - uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 c: if: ${{ github.repository_owner == 'curl' || github.event_name != 'schedule' }} name: 'C' - runs-on: ${{ matrix.platform == 'Linux' && 'ubuntu-26.04' || 'windows-2022' }} + runs-on: ${{ matrix.platform == 'Linux' && 'ubuntu-26.04-arm' || 'windows-2022' }} permissions: security-events: write # To create/update security events strategy: @@ -76,15 +76,10 @@ jobs: - name: 'install prereqs' if: ${{ matrix.platform == 'Linux' }} timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libpsl-dev libbrotli-dev libidn2-dev libssh2-1-dev libssh-dev \ - libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev libwolfssl-dev - /home/linuxbrew/.linuxbrew/bin/brew install ca-certificates || true - /home/linuxbrew/.linuxbrew/bin/brew install c-ares gsasl libnghttp3 libngtcp2 mbedtls rustls-ffi + uses: $/.github/actions/pkg-install + with: + apt: libpsl-dev libbrotli-dev libidn2-dev libssh2-1-dev libssh-dev libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev libwolfssl-dev + brew: c-ares gsasl libnghttp3 libngtcp2 mbedtls rustls-ffi - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -96,7 +91,7 @@ jobs: - name: 'initialize' # https://github.com/github/codeql-action/blob/main/init/action.yml - uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: languages: cpp build-mode: manual @@ -143,4 +138,4 @@ jobs: - name: 'perform analysis' # https://github.com/github/codeql-action/blob/main/analyze/action.yml - uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 diff --git a/.github/workflows/configure-vs-cmake.yml b/.github/workflows/configure-vs-cmake.yml index 1195878e0915..3620e29d16dc 100644 --- a/.github/workflows/configure-vs-cmake.yml +++ b/.github/workflows/configure-vs-cmake.yml @@ -37,16 +37,16 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: CURL_CI: github DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' jobs: check-linux: name: 'Linux' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -76,33 +76,23 @@ jobs: done - name: 'compare generated curl_config.h files' - run: ./.github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h + run: .github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h - name: 'compare generated libcurl.pc files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc + run: .github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc - name: 'compare generated curl-config files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config + run: .github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config check-macos: name: 'macOS' runs-on: macos-latest steps: - - name: 'install packages' + - name: 'install prereqs' timeout-minutes: 2 - run: | - # shellcheck disable=SC2181 - while [[ $? == 0 ]]; do - for i in 1 2 3; do - if brew install automake libtool; then - break 2 - else - echo "Error: wait to try again: $i" - sleep 10 - fi - done - false Too many retries - done + uses: $/.github/actions/pkg-install + with: + brew: automake libtool - name: 'toolchain versions' run: echo '::group::brew packages installed'; ls -l /opt/homebrew/opt; echo '::endgroup::' @@ -141,27 +131,25 @@ jobs: pccritic --min-score 98 bld-cm/libcurl.pc - name: 'compare generated curl_config.h files' - run: ./.github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h + run: .github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h - name: 'compare generated libcurl.pc files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc + run: .github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc - name: 'compare generated curl-config files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config + run: .github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config check-windows: name: 'Windows' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm env: TRIPLET: 'x86_64-w64-mingw32' steps: - - name: 'install packages' + - name: 'install prereqs' timeout-minutes: 1 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install gcc-mingw-w64-x86-64-win32 + uses: $/.github/actions/pkg-install + with: + apt: gcc-mingw-w64-x86-64-win32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -195,10 +183,10 @@ jobs: done - name: 'compare generated curl_config.h files' - run: ./.github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h + run: .github/scripts/cmp-config.pl bld-am/lib/curl_config.h bld-cm/lib/curl_config.h - name: 'compare generated libcurl.pc files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc + run: .github/scripts/cmp-pkg-config.sh bld-am/libcurl.pc bld-cm/libcurl.pc - name: 'compare generated curl-config files' - run: ./.github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config + run: .github/scripts/cmp-pkg-config.sh bld-am/curl-config bld-cm/curl-config diff --git a/.github/workflows/curl-for-win.yml b/.github/workflows/curl-for-win.yml index b84241f3e426..b915a4e0ed9d 100644 --- a/.github/workflows/curl-for-win.yml +++ b/.github/workflows/curl-for-win.yml @@ -32,6 +32,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: CW_NOGET: 'curl trurl' @@ -43,7 +44,7 @@ env: jobs: linux-glibc-gcc: name: 'Linux gcc glibc (amd64, arm64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -58,7 +59,6 @@ jobs: export CW_CONFIG='-main-werror-unitybatch-nocertdata-linux-a64-x64-gcc' export CW_REVISION="${GITHUB_SHA}" . ./_versions.sh - export CW_CCSUFFIX='-15' export CW_GCCSUFFIX='-12' sudo podman image trust set --type reject default sudo podman image trust set --type accept docker.io/library @@ -72,7 +72,7 @@ jobs: linux-glibc-gcc-minimal: # use gcc to minimize installed packages name: 'Linux gcc glibc minimal (amd64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -99,7 +99,7 @@ jobs: linux-musl-llvm: name: 'Linux llvm MUSL (amd64, riscv64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -148,7 +148,7 @@ jobs: win-llvm: name: 'Windows llvm (x64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 env: CW_PKG_NODELETE: '1' @@ -181,10 +181,11 @@ jobs: name: 'curl-windows-snapshot-tool' retention-days: 5 path: curl-*-*-*/curl* + archive: false win-gcc-zlibold-x64: name: 'Windows gcc zlib-classic (x64)' - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/distcheck.yml b/.github/workflows/distcheck.yml index f8936b1cbe6a..390a7309b1f8 100644 --- a/.github/workflows/distcheck.yml +++ b/.github/workflows/distcheck.yml @@ -20,11 +20,11 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: - CURL_TEST_MIN: 1500 + CURL_TEST_MIN: 1600 DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' MAKEFLAGS: -j 5 jobs: @@ -169,7 +169,7 @@ jobs: verify-out-of-tree-cmake: name: 'CM out-of-tree !perl' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm timeout-minutes: 5 needs: maketgz-and-verify-in-tree steps: @@ -191,7 +191,7 @@ jobs: verify-in-tree-cmake: name: 'CM in-tree !perl' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm timeout-minutes: 5 needs: maketgz-and-verify-in-tree steps: @@ -230,7 +230,7 @@ jobs: reproducible-releases: name: 'reproducible releases' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -262,7 +262,7 @@ jobs: MAKEFLAGS: ${{ contains(matrix.image, 'macos') && '-j 4' || '-j 5' }} MATRIX_IMAGE: '${{ matrix.image }}' TESTOPTS: ${{ contains(matrix.image, 'macos') && '-D_CURL_PREFILL=ON' || '' }} ${{ contains(matrix.image, 'windows') && '-DCMAKE_UNITY_BUILD_BATCH_SIZE=30' || '' }} - OLD_CMAKE_VERSION: 3.19.8 + OLD_CMAKE_VERSION: 3.19.8 # manually bumped OLD_CMAKE_SHA256_LINUX_ARM: 807f5afb2a560e00af9640e496d5673afefc2888bf0ed076412884a5ebb547a1 OLD_CMAKE_SHA256_MACOS_UNI: 0976d23d982af05dcbfb3aa34fcb62ead43bea27f0e3bb95222f2a78161423f2 OLD_CMAKE_SHA256_WIN_INTEL: 2a30877a3d6b50da305b289f4d1c03befdfaeb2edba02a563c681e883d810380 @@ -271,39 +271,41 @@ jobs: matrix: image: [ubuntu-26.04-arm, macos-latest, windows-2022] steps: - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 if: ${{ contains(matrix.image, 'windows') }} with: - msystem: mingw64 + msystem: ucrt64 release: false update: false cache: false path-type: inherit install: >- - perl mingw-w64-x86_64-zlib mingw-w64-x86_64-zstd mingw-w64-x86_64-libpsl mingw-w64-x86_64-libssh2 mingw-w64-x86_64-nghttp2 mingw-w64-x86_64-openssl + perl mingw-w64-ucrt-x86_64-zlib mingw-w64-ucrt-x86_64-zstd mingw-w64-ucrt-x86_64-libpsl + mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-nghttp2 mingw-w64-ucrt-x86_64-openssl - name: 'install prereqs' - timeout-minutes: 3 + if: ${{ !contains(matrix.image, 'windows') }} + timeout-minutes: 2 + uses: $/.github/actions/pkg-install + with: + apt: ${{ contains(matrix.image, 'ubuntu') && 'libpsl-dev libssl-dev' || '' }} + brew: ${{ contains(matrix.image, 'macos') && 'libpsl openssl' || '' }} + + - name: 'install prereqs (cmake)' + timeout-minutes: 2 run: | + cd ~ if [[ "${MATRIX_IMAGE}" = *'windows'* ]]; then - cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-win64-x64.zip" --output pkg.bin sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${OLD_CMAKE_SHA256_WIN_INTEL}" && unzip -q pkg.bin && rm -f pkg.bin printf '%s' ~/cmake-"${OLD_CMAKE_VERSION}"-win64-x64/bin/cmake.exe > ~/old-cmake-path.txt elif [[ "${MATRIX_IMAGE}" = *'ubuntu'* ]]; then - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install libpsl-dev libssl-dev - cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-Linux-aarch64.tar.gz" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OLD_CMAKE_SHA256_LINUX_ARM}" && tar -xzf pkg.bin && rm -f pkg.bin printf '%s' ~/cmake-"${OLD_CMAKE_VERSION}"-Linux-aarch64/bin/cmake > ~/old-cmake-path.txt else - brew install libpsl openssl - cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ --location --proto-redir =https "https://github.com/Kitware/CMake/releases/download/v${OLD_CMAKE_VERSION}/cmake-${OLD_CMAKE_VERSION}-macos-universal.tar.gz" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OLD_CMAKE_SHA256_MACOS_UNI}" && tar -xzf pkg.bin && rm -f pkg.bin @@ -330,7 +332,7 @@ jobs: if: ${{ contains(matrix.image, 'windows') }} run: | export TEST_CMAKE_FLAGS_PROVIDER='-DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON -DCURL_ZSTD=OFF' - TEST_CMAKE_FLAGS_PROVIDER+=' -DNGHTTP2_INCLUDE_DIR=C:/msys64/mingw64/include -DNGHTTP2_LIBRARY=C:/msys64/mingw64/lib/libnghttp2.dll.a' + TEST_CMAKE_FLAGS_PROVIDER+=' -DNGHTTP2_INCLUDE_DIR=/ucrt64/include -DNGHTTP2_LIBRARY=/ucrt64/lib/libnghttp2.dll.a' export TEST_CMAKE_FLAGS_CONSUMER="${TEST_CMAKE_FLAGS_PROVIDER}" ./tests/cmake/test.sh find_package ${TESTOPTS} -DCURL_USE_OPENSSL=ON @@ -356,7 +358,7 @@ jobs: fi if [[ "${MATRIX_IMAGE}" = *'windows'* ]]; then export TEST_CMAKE_GENERATOR='MSYS Makefiles' - export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=C:/msys64/mingw64' + export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=/ucrt64' fi ./tests/cmake/test.sh add_subdirectory ${TESTOPTS} -DCURL_USE_OPENSSL=ON @@ -369,7 +371,7 @@ jobs: fi if [[ "${MATRIX_IMAGE}" = *'windows'* ]]; then export TEST_CMAKE_GENERATOR='MSYS Makefiles' - export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=C:/msys64/mingw64' + export TEST_CMAKE_FLAGS='-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DOPENSSL_ROOT_DIR=/ucrt64' fi ./tests/cmake/test.sh find_package ${TESTOPTS} -DCURL_USE_OPENSSL=ON diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml deleted file mode 100644 index 7ac58d902b2a..000000000000 --- a/.github/workflows/fuzz.yml +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (C) Daniel Stenberg, , et al. -# -# SPDX-License-Identifier: curl - -name: 'Fuzzer' - -'on': - push: - branches: - - master - - 'curl-*' - - '*/ci' - paths-ignore: - - '**/*.md' - - '**/CMakeLists.txt' - - '.circleci/**' - - 'appveyor.*' - - 'CMake/**' - - 'Dockerfile' - - 'projects/**' - - 'tests/data/**' - pull_request: - branches: - - master - - 'curl-*' - paths-ignore: - - '**/*.md' - - '**/CMakeLists.txt' - - '.circleci/**' - - 'appveyor.*' - - 'CMake/**' - - 'Dockerfile' - - 'projects/**' - - 'tests/data/**' - -concurrency: - # Hard-coded workflow name to avoid colliding with curl-fuzzer's group - group: curl-fuzz-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -permissions: {} - -env: - DO_NOT_TRACK: '1' - -jobs: - Fuzzing: - uses: curl/curl-fuzzer/.github/workflows/ci.yml@master # zizmor: ignore[unpinned-uses] diff --git a/.github/workflows/http3-linux.yml b/.github/workflows/http3-linux.yml index ca2bad2ea053..0e9e40db17e4 100644 --- a/.github/workflows/http3-linux.yml +++ b/.github/workflows/http3-linux.yml @@ -32,248 +32,248 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: write env: MAKEFLAGS: -j 5 CURL_CI: github - CURL_TEST_MIN: 1850 + CURL_TEST_MIN: 2000 DO_NOT_TRACK: '1' - # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com - AWSLC_VERSION: 5.5.0 - # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com - BORINGSSL_VERSION: 0.20260730.0 - # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver registryUrl=https://github.com - NETTLE_VERSION: 3.10.2 - # renovate: datasource=github-tags depName=gnutls/gnutls versioning=semver extractVersion=^nettle_?(?.+)_release_.+$ registryUrl=https://github.com - GNUTLS_VERSION: 3.8.11 - # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com - LIBRESSL_VERSION: 4.3.2 - # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com - OPENSSL_VERSION: 4.0.2 - # manually bumped - OPENSSL_PREV_VERSION: 3.6.2 - OPENSSL_PREV_SHA256: aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f - # renovate: datasource=github-tags depName=cloudflare/quiche versioning=semver registryUrl=https://github.com - QUICHE_VERSION: 0.29.3 - # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com - WOLFSSL_VERSION: 5.9.2 - # renovate: datasource=github-tags depName=ngtcp2/nghttp3 versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=awslabs/aws-lc + AWSLC_VERSION: 5.11.0 + # renovate: datasource=github-tags depName=google/boringssl + BORINGSSL_VERSION: 0.20260929.0 + # renovate: datasource=github-tags depName=gnutls/nettle versioning=semver-coerced extractVersion=^nettle_?(?.+)_release_.+$ + NETTLE_VERSION: '4.0' + # renovate: datasource=github-tags depName=gnutls/gnutls + GNUTLS_VERSION: 3.8.13 + # renovate: datasource=github-tags depName=libressl/portable + LIBRESSL_VERSION: 4.3.3 + # renovate: datasource=github-releases depName=openssl/openssl extractVersion=^openssl-(?.+)$ + OPENSSL_VERSION: 4.0.3 + # renovate: datasource=github-releases depName=openssl/openssl extractVersion=^openssl-(?3\.5\..+)$ + OPENSSL_PREV_VERSION: 3.5.9 + # renovate: datasource=github-tags depName=cloudflare/quiche + QUICHE_VERSION: 0.30.0 + # renovate: datasource=github-tags depName=wolfSSL/wolfssl extractVersion=^v?(?.+)-stable$ + WOLFSSL_VERSION: 5.9.4 + # renovate: datasource=github-tags depName=ngtcp2/nghttp3 NGHTTP3_VERSION: 1.18.0 - # renovate: datasource=github-tags depName=ngtcp2/ngtcp2 versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=ngtcp2/ngtcp2 NGTCP2_VERSION: 1.25.0 - # renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=nghttp2/nghttp2 NGHTTP2_VERSION: 1.70.0 - # no tagged releases + # manually bumped (repo not offering tagged releases) H2O_VERSION: 11b0cfa2771e3ccad4a852e72473e4e278ab1de7 # 2026-05-28 H2O_SHA256: 5ae1bd7b09970d7d49c41fa68193e24da04c2a7ac5581fbe2affc79200b0721f jobs: build-cache: - name: 'Build caches' - runs-on: ubuntu-26.04 - + name: 'Build caches ${{ matrix.image }}' + runs-on: ${{ matrix.image }} + strategy: + matrix: + image: [ubuntu-26.04, ubuntu-26.04-arm] steps: - name: 'cache awslc' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-awslc + id: cache1-awslc env: - cache-name: cache-awslc + cache-name: cache1-awslc with: - path: ~/awslc/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }} + path: ~/pkg/awslc + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }}-${{ runner.arch }} - name: 'cache boringssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-boringssl + id: cache1-boringssl env: - cache-name: cache-boringssl + cache-name: cache1-boringssl with: - path: ~/boringssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/boringssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} - name: 'cache nettle' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nettle + id: cache1-nettle env: - cache-name: cache-nettle + cache-name: cache1-nettle with: - path: ~/nettle/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NETTLE_VERSION }} + path: ~/pkg/nettle + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NETTLE_VERSION }}-${{ runner.arch }} - name: 'cache gnutls' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-gnutls + id: cache1-gnutls env: - cache-name: cache-gnutls + cache-name: cache1-gnutls with: - path: ~/gnutls/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }} + path: ~/pkg/gnutls + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }}-${{ runner.arch }} - name: 'cache libressl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-libressl + id: cache1-libressl env: - cache-name: cache-libressl + cache-name: cache1-libressl with: - path: ~/libressl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} + path: ~/pkg/libressl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ runner.arch }} - name: 'cache openssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-http3-no-deprecated + id: cache2-openssl-http3-no-deprecated env: - cache-name: cache-openssl-http3-no-deprecated + cache-name: cache2-openssl-http3-no-deprecated with: - path: ~/openssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} + path: ~/pkg/openssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }}-${{ runner.arch }} - name: 'cache openssl-prev' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-prev-http3 + id: cache2-openssl-prev-http3 env: - cache-name: cache-openssl-prev-http3 + cache-name: cache2-openssl-prev-http3 with: - path: ~/openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/openssl-prev + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} - name: 'cache wolfssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl + id: cache1-wolfssl env: - cache-name: cache-wolfssl + cache-name: cache1-wolfssl with: - path: ~/wolfssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/wolfssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }} - name: 'cache nghttp3' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp3 + id: cache1-nghttp3 env: - cache-name: cache-nghttp3 + cache-name: cache1-nghttp3 with: - path: ~/nghttp3/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP3_VERSION }} + path: ~/pkg/nghttp3 + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP3_VERSION }}-${{ runner.arch }} - name: 'cache ngtcp2' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2 + id: cache1-ngtcp2 env: - cache-name: cache-ngtcp2 + cache-name: cache1-ngtcp2 with: - path: ~/ngtcp2/build + path: ~/pkg/ngtcp2 key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_VERSION }}-\ - ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}" + ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }}" - name: 'cache ngtcp2 boringssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2-boringssl + id: cache1-ngtcp2-boringssl env: - cache-name: cache-ngtcp2-boringssl + cache-name: cache1-ngtcp2-boringssl with: - path: ~/ngtcp2-boringssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/ngtcp2-boringssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} - name: 'cache ngtcp2 openssl-prev' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2-openssl-prev + id: cache1-ngtcp2-openssl-prev env: - cache-name: cache-ngtcp2-openssl-prev + cache-name: cache1-ngtcp2-openssl-prev with: - path: ~/ngtcp2-openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/ngtcp2-openssl-prev + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} - name: 'cache nghttp2' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp2 + id: cache1-nghttp2 env: - cache-name: cache-nghttp2 + cache-name: cache1-nghttp2 with: - path: ~/nghttp2/build + path: ~/pkg/nghttp2 key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.OPENSSL_VERSION }}-\ - ${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }}" + ${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }}-${{ runner.arch }}" - name: 'cache h2o' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-h2o + id: cache1-h2o env: - cache-name: cache-h2o + cache-name: cache1-h2o with: - path: ~/h2o/build - key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}" + path: ~/pkg/h2o + key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }}" - id: settings if: >- - ${{ !steps.cache-awslc.outputs.cache-hit || - !steps.cache-boringssl.outputs.cache-hit || - !steps.cache-nettle.outputs.cache-hit || - !steps.cache-gnutls.outputs.cache-hit || - !steps.cache-libressl.outputs.cache-hit || - !steps.cache-openssl-http3-no-deprecated.outputs.cache-hit || - !steps.cache-openssl-prev-http3.outputs.cache-hit || - !steps.cache-wolfssl.outputs.cache-hit || - !steps.cache-nghttp3.outputs.cache-hit || - !steps.cache-ngtcp2-boringssl.outputs.cache-hit || - !steps.cache-ngtcp2-openssl-prev.outputs.cache-hit || - !steps.cache-ngtcp2.outputs.cache-hit || - !steps.cache-nghttp2.outputs.cache-hit || - !steps.cache-h2o.outputs.cache-hit }} + ${{ !steps.cache1-awslc.outputs.cache-hit || + !steps.cache1-boringssl.outputs.cache-hit || + !steps.cache1-nettle.outputs.cache-hit || + !steps.cache1-gnutls.outputs.cache-hit || + !steps.cache1-libressl.outputs.cache-hit || + !steps.cache2-openssl-http3-no-deprecated.outputs.cache-hit || + !steps.cache2-openssl-prev-http3.outputs.cache-hit || + !steps.cache1-wolfssl.outputs.cache-hit || + !steps.cache1-nghttp3.outputs.cache-hit || + !steps.cache1-ngtcp2-boringssl.outputs.cache-hit || + !steps.cache1-ngtcp2-openssl-prev.outputs.cache-hit || + !steps.cache1-ngtcp2.outputs.cache-hit || + !steps.cache1-nghttp2.outputs.cache-hit || + !steps.cache1-h2o.outputs.cache-hit }} run: echo 'needs-build=true' >> "$GITHUB_OUTPUT" - name: 'install build prereqs' if: ${{ steps.settings.outputs.needs-build }} timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libtool autoconf automake pkgconf \ - libbrotli-dev libzstd-dev zlib1g-dev \ - libev-dev \ - libuv1-dev \ - libc-ares-dev \ - libp11-kit-dev autopoint bison gperf gtk-doc-tools libtasn1-bin # for GnuTLS + uses: $/.github/actions/pkg-install + with: + apt: >- + libtool autoconf automake pkgconf + libbrotli-dev libzstd-dev zlib1g-dev + libev-dev + libuv1-dev + libc-ares-dev + libp11-kit-dev autopoint bison gperf gtk-doc-tools libtasn1-bin - name: 'build awslc' - if: ${{ !steps.cache-awslc.outputs.cache-hit }} + if: ${{ !steps.cache1-awslc.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "aws-lc-${AWSLC_VERSION}" - cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TOOL=OFF -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/awslc/build + cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DBUILD_TOOL=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/awslc cmake --build . cmake --install . - name: 'build boringssl' - if: ${{ !steps.cache-boringssl.outputs.cache-hit }} + if: ${{ !steps.cache1-boringssl.outputs.cache-hit }} run: | - mkdir boringssl-src - cd boringssl-src + cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - "https://boringssl.googlesource.com/boringssl/+archive/${BORINGSSL_VERSION}.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin - cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl/build + cd "boringssl-${BORINGSSL_VERSION}" + cmake -B . -G Ninja -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/boringssl cmake --build . cmake --install . - name: 'build nettle' - if: ${{ !steps.cache-nettle.outputs.cache-hit }} + if: ${{ !steps.cache1-nettle.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://ftpmirror.gnu.org/nettle/nettle-${NETTLE_VERSION}.tar.gz" --output pkg.bin + "https://ftp.gnu.org/gnu/nettle/nettle-${NETTLE_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "nettle-${NETTLE_VERSION}" autoreconf -fi - ./configure --disable-dependency-tracking --prefix=/home/runner/nettle/build \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/nettle \ --disable-static --disable-openssl --disable-documentation make install - name: 'build gnutls' - if: ${{ !steps.cache-gnutls.outputs.cache-hit }} + if: ${{ !steps.cache1-gnutls.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ @@ -282,121 +282,123 @@ jobs: cd "gnutls-${GNUTLS_VERSION}" autoreconf -fi # required: libp11-kit-dev libev-dev autopoint bison gperf gtk-doc-tools libtasn1-bin - ./configure --disable-dependency-tracking --prefix=/home/runner/gnutls/build \ - PKG_CONFIG_PATH=/home/runner/nettle/build/lib64/pkgconfig \ - LDFLAGS=-Wl,-rpath,/home/runner/nettle/build/lib64 \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/gnutls \ + PKG_CONFIG_PATH=/home/runner/pkg/nettle/lib/pkgconfig \ + LDFLAGS=-Wl,-rpath,/home/runner/pkg/nettle/lib \ --with-included-libtasn1 --with-included-unistring \ - --disable-guile --disable-doc --disable-tests --disable-tools + --disable-doc --disable-tests --disable-tools make install - name: 'build libressl' - if: ${{ !steps.cache-libressl.outputs.cache-hit }} + if: ${{ !steps.cache1-libressl.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl/build + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/libressl cmake --build . cmake --install . - name: 'build openssl' - if: ${{ !steps.cache-openssl-http3-no-deprecated.outputs.cache-hit }} + if: ${{ !steps.cache2-openssl-http3-no-deprecated.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "openssl-${OPENSSL_VERSION}" https://github.com/openssl/openssl cd openssl - ./config --prefix="$PWD"/build --libdir=lib no-makedepend no-apps no-docs no-tests no-deprecated + # Prefer clang to avoid test 2500-2503 failing valgrind with gcc-15/16 arm64 non-debug OpenSSL 3/4 builds + CC=clang ./config --prefix=/home/runner/pkg/openssl --libdir=lib no-makedepend no-apps no-docs no-tests no-deprecated make make -j1 install_sw - name: 'build openssl-prev' - if: ${{ !steps.cache-openssl-prev-http3.outputs.cache-hit }} + if: ${{ !steps.cache2-openssl-prev-http3.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_PREV_VERSION}/openssl-${OPENSSL_PREV_VERSION}.tar.gz" --output pkg.bin - sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${OPENSSL_PREV_SHA256}" && tar -xzf pkg.bin && rm -f pkg.bin + sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "openssl-${OPENSSL_PREV_VERSION}" - ./config --prefix=/home/runner/openssl-prev/build --libdir=lib no-makedepend no-apps no-docs no-tests + # Prefer clang to avoid test 2500-2503 failing valgrind with gcc-15/16 arm64 non-debug OpenSSL 3/4 builds + CC=clang ./config --prefix=/home/runner/pkg/openssl-prev --libdir=lib no-makedepend no-apps no-docs no-tests make make -j1 install_sw - name: 'build wolfssl' - if: ${{ !steps.cache-wolfssl.outputs.cache-hit }} + if: ${{ !steps.cache1-wolfssl.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${WOLFSSL_VERSION}-stable" https://github.com/wolfSSL/wolfssl cd wolfssl ./autogen.sh - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-all --enable-quic \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/wolfssl --enable-all --enable-quic \ --disable-benchmark --disable-crypttests --disable-examples make make install - name: 'build nghttp3' - if: ${{ !steps.cache-nghttp3.outputs.cache-hit }} + if: ${{ !steps.cache1-nghttp3.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGHTTP3_VERSION}" https://github.com/ngtcp2/nghttp3 cd nghttp3 git submodule update --init --depth 1 autoreconf -fi - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/nghttp3 --enable-lib-only make make install - name: 'build ngtcp2' - if: ${{ !steps.cache-ngtcp2.outputs.cache-hit }} + if: ${{ !steps.cache1-ngtcp2.outputs.cache-hit }} # building twice to get crypto libs for ossl, libressl and awslc installed run: | cd ~ git clone --quiet --depth 1 --branch "v${NGTCP2_VERSION}" https://github.com/ngtcp2/ngtcp2 cd ngtcp2 autoreconf -fi - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only \ - PKG_CONFIG_PATH=/home/runner/libressl/build/lib/pkgconfig \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/ngtcp2 --enable-lib-only \ + PKG_CONFIG_PATH=/home/runner/pkg/libressl/lib/pkgconfig \ --with-openssl make install make clean - export PKG_CONFIG_PATH=/home/runner/openssl/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/nettle/build/lib64/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/gnutls/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/wolfssl/build/lib/pkgconfig - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only \ + export PKG_CONFIG_PATH=/home/runner/pkg/openssl/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/nettle/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/gnutls/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/wolfssl/lib/pkgconfig + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/ngtcp2 --enable-lib-only \ --with-openssl --with-gnutls --with-wolfssl --with-boringssl \ - BORINGSSL_LIBS='-L/home/runner/awslc/build/lib -lssl -lcrypto' \ - BORINGSSL_CFLAGS='-I/home/runner/awslc/build/include' + BORINGSSL_LIBS='-L/home/runner/pkg/awslc/lib -lssl -lcrypto' \ + BORINGSSL_CFLAGS='-I/home/runner/pkg/awslc/include' make install - name: 'build ngtcp2 openssl-prev' - if: ${{ !steps.cache-ngtcp2-openssl-prev.outputs.cache-hit }} + if: ${{ !steps.cache1-ngtcp2-openssl-prev.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGTCP2_VERSION}" https://github.com/ngtcp2/ngtcp2 ngtcp2-openssl-prev cd ngtcp2-openssl-prev autoreconf -fi - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only \ - PKG_CONFIG_PATH=/home/runner/openssl-prev/build/lib/pkgconfig \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/ngtcp2-openssl-prev --enable-lib-only \ + PKG_CONFIG_PATH=/home/runner/pkg/openssl-prev/lib/pkgconfig \ --with-openssl make install - name: 'build ngtcp2 boringssl' - if: ${{ !steps.cache-ngtcp2-boringssl.outputs.cache-hit }} + if: ${{ !steps.cache1-ngtcp2-boringssl.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGTCP2_VERSION}" https://github.com/ngtcp2/ngtcp2 ngtcp2-boringssl cd ngtcp2-boringssl autoreconf -fi - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-lib-only \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/ngtcp2-boringssl --enable-lib-only \ --with-openssl=no --with-boringssl \ - BORINGSSL_LIBS='-L/home/runner/boringssl/build/lib -lssl -lcrypto' \ - BORINGSSL_CFLAGS='-I/home/runner/boringssl/build/include' + BORINGSSL_LIBS='-L/home/runner/pkg/boringssl/lib -lssl -lcrypto' \ + BORINGSSL_CFLAGS='-I/home/runner/pkg/boringssl/include' make install - name: 'build nghttp2' - if: ${{ !steps.cache-nghttp2.outputs.cache-hit }} + if: ${{ !steps.cache1-nghttp2.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "v${NGHTTP2_VERSION}" https://github.com/nghttp2/nghttp2 @@ -405,146 +407,153 @@ jobs: autoreconf -fi # required (for nghttpx application): libc-ares-dev libev-dev zlib1g-dev # optional (for nghttpx application): libbrotli-dev - export PKG_CONFIG_PATH=/home/runner/openssl/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/nghttp3/build/lib/pkgconfig - PKG_CONFIG_PATH+=:/home/runner/ngtcp2/build/lib/pkgconfig - ./configure --disable-dependency-tracking --prefix="$PWD"/build --enable-app --enable-http3 \ - LDFLAGS=-Wl,-rpath,/home/runner/openssl/build/lib \ + export PKG_CONFIG_PATH=/home/runner/pkg/openssl/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/nghttp3/lib/pkgconfig + PKG_CONFIG_PATH+=:/home/runner/pkg/ngtcp2/lib/pkgconfig + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/nghttp2 --enable-app --enable-http3 \ + LDFLAGS=-Wl,-rpath,/home/runner/pkg/openssl/lib \ --with-libbrotlienc --with-libbrotlidec make install - name: 'build h2o' - if: ${{ !steps.cache-h2o.outputs.cache-hit }} + if: ${{ !steps.cache1-h2o.outputs.cache-hit }} run: | cd ~ curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/h2o/h2o/archive/${H2O_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${H2O_SHA256}" && tar -xzf pkg.bin && rm -f pkg.bin cd "h2o-${H2O_VERSION}" - cmake -B . -G Ninja -DWITHOUT_LIBS=ON -DOPENSSL_ROOT_DIR=/home/runner/openssl-prev/build -DCMAKE_INSTALL_PREFIX=/home/runner/h2o/build + cmake -B . -G Ninja -DWITHOUT_LIBS=ON -DOPENSSL_ROOT_DIR=/home/runner/pkg/openssl-prev -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/h2o cmake --build . cmake --install . linux: name: ${{ matrix.build.generate && 'CM' || 'AM' }} ${{ matrix.build.name }} needs: build-cache - runs-on: ubuntu-26.04 + runs-on: ${{ matrix.build.image || 'ubuntu-26.04' }} timeout-minutes: 10 env: CURL_TRACE_PKG_CONFIG: '1' MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }} - MATRIX_INSTALL_PACKAGES: '${{ matrix.build.install_packages }}' strategy: fail-fast: false matrix: build: - name: 'awslc' install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/awslc/build/lib - PKG_CONFIG_PATH: /home/runner/awslc/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/awslc/lib + PKG_CONFIG_PATH: /home/runner/pkg/awslc/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig # Intentionally using bare '--with-ngtcp2' + 'PKG_CONFIG_PATH' to test this way of configuration, in addition to '--with-ngtcp2=' in other jobs. configure: >- - --with-openssl=/home/runner/awslc/build --with-ngtcp2 --enable-ssls-export + --with-openssl=/home/runner/pkg/awslc --with-ngtcp2 --enable-ssls-export - name: 'awslc' - PKG_CONFIG_PATH: /home/runner/awslc/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/awslc/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/awslc/build -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF + -DOPENSSL_ROOT_DIR=/home/runner/pkg/awslc -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_UNITY_BUILD=ON -DCURL_DROP_UNUSED=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'boringssl' + image: ubuntu-26.04-arm install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/boringssl/build/lib - PKG_CONFIG_PATH: /home/runner/boringssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/boringssl/lib + PKG_CONFIG_PATH: /home/runner/pkg/boringssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig configure: >- - --with-openssl=/home/runner/boringssl/build --with-ngtcp2=/home/runner/ngtcp2-boringssl/build --enable-ssls-export + --with-openssl=/home/runner/pkg/boringssl --with-ngtcp2=/home/runner/pkg/ngtcp2-boringssl --enable-ssls-export - name: 'boringssl' + image: ubuntu-26.04-arm PKG_CONFIG_PATH: "\ - /home/runner/boringssl/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/ngtcp2-boringssl/build/lib/pkgconfig:\ - /home/runner/nghttp2/build/lib/pkgconfig" + /home/runner/pkg/boringssl/lib/pkgconfig:\ + /home/runner/pkg/nghttp3/lib/pkgconfig:\ + /home/runner/pkg/ngtcp2-boringssl/lib/pkgconfig:\ + /home/runner/pkg/nghttp2/lib/pkgconfig" generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/boringssl/build -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF + -DOPENSSL_ROOT_DIR=/home/runner/pkg/boringssl -DUSE_NGTCP2=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_UNITY_BUILD=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'gnutls' install_packages: libp11-kit-dev libssh-dev install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/gnutls/build/lib -Wl,-rpath,/home/runner/nettle/build/lib64 -Wl,-rpath,/home/runner/ngtcp2/build/lib - PKG_CONFIG_PATH: /home/runner/nettle/build/lib64/pkgconfig:/home/runner/gnutls/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/gnutls/lib -Wl,-rpath,/home/runner/pkg/nettle/lib -Wl,-rpath,/home/runner/pkg/ngtcp2/lib + PKG_CONFIG_PATH: /home/runner/pkg/nettle/lib/pkgconfig:/home/runner/pkg/gnutls/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig configure: >- - --with-gnutls=/home/runner/gnutls/build --with-ngtcp2=/home/runner/ngtcp2/build --with-libssh --enable-ssls-export --enable-httpsig + --with-gnutls=/home/runner/pkg/gnutls --with-ngtcp2=/home/runner/pkg/ngtcp2 --with-libssh --enable-ssls-export --enable-httpsig --disable-ntlm - name: 'gnutls' + tflags: '--min=1900' install_packages: libp11-kit-dev libssh-dev - LDFLAGS: -Wl,-rpath,/home/runner/gnutls/build/lib + LDFLAGS: -Wl,-rpath,/home/runner/pkg/gnutls/lib PKG_CONFIG_PATH: "\ - /home/runner/nettle/build/lib64/pkgconfig:\ - /home/runner/gnutls/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/ngtcp2/build/lib/pkgconfig:\ - /home/runner/nghttp2/build/lib/pkgconfig" + /home/runner/pkg/nettle/lib/pkgconfig:\ + /home/runner/pkg/gnutls/lib/pkgconfig:\ + /home/runner/pkg/nghttp3/lib/pkgconfig:\ + /home/runner/pkg/ngtcp2/lib/pkgconfig:\ + /home/runner/pkg/nghttp2/lib/pkgconfig" generate: >- -DCURL_USE_GNUTLS=ON -DUSE_NGTCP2=ON -DCURL_USE_LIBSSH=ON - -DCMAKE_UNITY_BUILD=ON -DCURL_DISABLE_HTTPSIG=OFF + -DCMAKE_UNITY_BUILD=ON -DCURL_DISABLE_HTTPSIG=OFF -DCURL_ENABLE_NTLM=OFF - name: 'libressl' install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/libressl/build/lib - PKG_CONFIG_PATH: /home/runner/libressl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/libressl/lib + PKG_CONFIG_PATH: /home/runner/pkg/libressl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig # Intentionally using '--with-ngtcp2=' to test this way of configuration, in addition to bare '--with-ngtcp2' + 'PKG_CONFIG_PATH' in other jobs. configure: >- - --with-openssl=/home/runner/libressl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ssls-export + --with-openssl=/home/runner/pkg/libressl --with-ngtcp2=/home/runner/pkg/ngtcp2 --enable-ssls-export --enable-unity - name: 'libressl' - PKG_CONFIG_PATH: /home/runner/libressl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/libressl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/libressl/build -DUSE_NGTCP2=ON -DCURL_DISABLE_HTTPSIG=OFF + -DOPENSSL_ROOT_DIR=/home/runner/pkg/libressl -DUSE_NGTCP2=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'openssl' - tflags: '--min=1700' - LDFLAGS: -Wl,-rpath,/home/runner/openssl/build/lib - PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + tflags: '--min=1850' + LDFLAGS: -Wl,-rpath,/home/runner/pkg/openssl/lib + PKG_CONFIG_PATH: /home/runner/pkg/openssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig configure: >- - --with-openssl=/home/runner/openssl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ech --enable-ssls-export --enable-proxy-http3 --enable-httpsig + --with-openssl=/home/runner/pkg/openssl --with-ngtcp2=/home/runner/pkg/ngtcp2 --enable-ech --enable-ssls-export --enable-proxy-http3 --enable-httpsig - name: 'openssl' install_steps: skipall - PKG_CONFIG_PATH: /home/runner/openssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/openssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/openssl/build -DUSE_NGTCP2=ON + -DOPENSSL_ROOT_DIR=/home/runner/pkg/openssl -DUSE_NGTCP2=ON -DCURL_DISABLE_LDAP=ON -DUSE_ECH=ON -DUSE_PROXY_HTTP3=ON -DCMAKE_UNITY_BUILD=ON - name: 'openssl-prev' + image: ubuntu-26.04-arm install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/openssl-prev/build/lib + LDFLAGS: -Wl,-rpath,/home/runner/pkg/openssl-prev/lib PKG_CONFIG_PATH: "\ - /home/runner/openssl-prev/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/nghttp2-openssl-prev/build/lib/pkgconfig" + /home/runner/pkg/openssl-prev/lib/pkgconfig:\ + /home/runner/pkg/nghttp3/lib/pkgconfig:\ + /home/runner/pkg/nghttp2/lib/pkgconfig" configure: >- - --with-openssl=/home/runner/openssl-prev/build --with-ngtcp2=/home/runner/ngtcp2-openssl-prev/build --enable-ssls-export + --with-openssl=/home/runner/pkg/openssl-prev --with-ngtcp2=/home/runner/pkg/ngtcp2-openssl-prev --enable-ssls-export - name: 'openssl-prev' - tflags: '--min=1700' + image: ubuntu-26.04-arm + tflags: '--min=2000' PKG_CONFIG_PATH: "\ - /home/runner/openssl-prev/build/lib/pkgconfig:\ - /home/runner/nghttp3/build/lib/pkgconfig:\ - /home/runner/ngtcp2-openssl-prev/build/lib/pkgconfig:\ - /home/runner/nghttp2/build/lib/pkgconfig" + /home/runner/pkg/openssl-prev/lib/pkgconfig:\ + /home/runner/pkg/nghttp3/lib/pkgconfig:\ + /home/runner/pkg/nghttp2/lib/pkgconfig:\ + /home/runner/pkg/ngtcp2-openssl-prev/lib/pkgconfig" generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/openssl-prev/build -DUSE_NGTCP2=ON + -DOPENSSL_ROOT_DIR=/home/runner/pkg/openssl-prev -DUSE_NGTCP2=ON -DCURL_DISABLE_LDAP=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'quiche' + install_packages: clang install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/quiche/target/release - PKG_CONFIG_PATH: /home/runner/nghttp2/build/lib/pkgconfig + CC: clang + CFLAGS: -fPIC + LDFLAGS: -Wl,-rpath,/home/runner/quiche/target/release -lstdc++ + PKG_CONFIG_PATH: /home/runner/pkg/nghttp2/lib/pkgconfig configure: >- --with-openssl=/home/runner/quiche/boringssl --with-quiche=/home/runner/quiche/target/release @@ -552,25 +561,27 @@ jobs: --enable-unity - name: 'quiche' - PKG_CONFIG_PATH: /home/runner/nghttp2/build/lib/pkgconfig:/home/runner/quiche/target/release + install_packages: clang + CC: clang + PKG_CONFIG_PATH: /home/runner/pkg/nghttp2/lib/pkgconfig:/home/runner/quiche/target/release generate: >- - -DOPENSSL_ROOT_DIR=/home/runner/quiche/boringssl + -DOPENSSL_ROOT_DIR=/home/runner/quiche/boringssl -DOPENSSL_USE_STATIC_LIBS=ON -DUSE_QUICHE=ON -DCURL_CA_FALLBACK=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'wolfssl' install_packages: libssh2-1-dev install_steps: skipall - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl/build/lib - PKG_CONFIG_PATH: /home/runner/wolfssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/wolfssl/lib + PKG_CONFIG_PATH: /home/runner/pkg/wolfssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig configure: >- - --with-wolfssl=/home/runner/wolfssl/build --with-ngtcp2=/home/runner/ngtcp2/build --enable-ech --with-libssh2 --enable-ssls-export + --with-wolfssl=/home/runner/pkg/wolfssl --with-ngtcp2=/home/runner/pkg/ngtcp2 --enable-ech --with-libssh2 --enable-ssls-export --enable-unity - name: 'wolfssl' install_packages: libssh2-1-dev tflags: '--min=1900' - PKG_CONFIG_PATH: /home/runner/wolfssl/build/lib/pkgconfig:/home/runner/nghttp3/build/lib/pkgconfig:/home/runner/ngtcp2/build/lib/pkgconfig:/home/runner/nghttp2/build/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/wolfssl/lib/pkgconfig:/home/runner/pkg/nghttp3/lib/pkgconfig:/home/runner/pkg/ngtcp2/lib/pkgconfig:/home/runner/pkg/nghttp2/lib/pkgconfig generate: >- -DCURL_USE_WOLFSSL=ON -DUSE_NGTCP2=ON -DUSE_ECH=ON -DCURL_DISABLE_HTTPSIG=OFF @@ -578,199 +589,198 @@ jobs: steps: - name: 'install prereqs' timeout-minutes: 2 - env: - INSTALL_PACKAGES: >- + uses: $/.github/actions/pkg-install + with: + apt: >- + libtool autoconf automake pkgconf + libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev libidn2-0-dev libldap-dev libuv1-dev valgrind ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && 'stunnel4 ' || '' }} ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && 'apache2 apache2-dev libnghttp2-dev vsftpd dante-server libev-dev' || '' }} - - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libtool autoconf automake pkgconf \ - libpsl-dev libbrotli-dev libzstd-dev zlib1g-dev libidn2-0-dev libldap-dev libuv1-dev valgrind \ - ${INSTALL_PACKAGES} \ - ${MATRIX_INSTALL_PACKAGES} + ${{ matrix.build.install_packages }} - name: 'cache awslc' if: ${{ contains(matrix.build.name, 'awslc') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-awslc + id: cache1-awslc env: - cache-name: cache-awslc + cache-name: cache1-awslc with: - path: ~/awslc/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }} + path: ~/pkg/awslc + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache boringssl' if: ${{ contains(matrix.build.name, 'boringssl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-boringssl + id: cache1-boringssl env: - cache-name: cache-boringssl + cache-name: cache1-boringssl with: - path: ~/boringssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/boringssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache nettle' if: ${{ contains(matrix.build.name, 'gnutls') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nettle + id: cache1-nettle env: - cache-name: cache-nettle + cache-name: cache1-nettle with: - path: ~/nettle/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NETTLE_VERSION }} + path: ~/pkg/nettle + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NETTLE_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache gnutls' if: ${{ contains(matrix.build.name, 'gnutls') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-gnutls + id: cache1-gnutls env: - cache-name: cache-gnutls + cache-name: cache1-gnutls with: - path: ~/gnutls/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }} + path: ~/pkg/gnutls + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.GNUTLS_VERSION }}-${{ env.NETTLE_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache libressl' if: ${{ contains(matrix.build.name, 'libressl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-libressl + id: cache1-libressl env: - cache-name: cache-libressl + cache-name: cache1-libressl with: - path: ~/libressl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} + path: ~/pkg/libressl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache openssl' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-http3-no-deprecated + id: cache2-openssl-http3-no-deprecated env: - cache-name: cache-openssl-http3-no-deprecated + cache-name: cache2-openssl-http3-no-deprecated with: - path: ~/openssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} + path: ~/pkg/openssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache openssl-prev' if: ${{ contains(matrix.build.name, 'openssl-prev') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-prev-http3 + id: cache2-openssl-prev-http3 env: - cache-name: cache-openssl-prev-http3 + cache-name: cache2-openssl-prev-http3 with: - path: ~/openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/openssl-prev + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache wolfssl' if: ${{ contains(matrix.build.name, 'wolfssl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl + id: cache1-wolfssl env: - cache-name: cache-wolfssl + cache-name: cache1-wolfssl with: - path: ~/wolfssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/wolfssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache nghttp3' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp3 + id: cache1-nghttp3 env: - cache-name: cache-nghttp3 + cache-name: cache1-nghttp3 with: - path: ~/nghttp3/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP3_VERSION }} + path: ~/pkg/nghttp3 + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP3_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache ngtcp2' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2 + id: cache1-ngtcp2 env: - cache-name: cache-ngtcp2 + cache-name: cache1-ngtcp2 with: - path: ~/ngtcp2/build + path: ~/pkg/ngtcp2 key: "${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_VERSION }}-\ - ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}" + ${{ env.LIBRESSL_VERSION }}-${{ env.AWSLC_VERSION }}-${{ env.NETTLE_VERSION }}-${{ env.GNUTLS_VERSION }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }}" fail-on-cache-miss: true - name: 'cache ngtcp2 boringssl' if: ${{ contains(matrix.build.name, 'boringssl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2-boringssl + id: cache1-ngtcp2-boringssl env: - cache-name: cache-ngtcp2-boringssl + cache-name: cache1-ngtcp2-boringssl with: - path: ~/ngtcp2-boringssl/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/ngtcp2-boringssl + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache ngtcp2 openssl-prev' if: ${{ contains(matrix.build.name, 'openssl-prev') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-ngtcp2-openssl-prev + id: cache1-ngtcp2-openssl-prev env: - cache-name: cache-ngtcp2-openssl-prev + cache-name: cache1-ngtcp2-openssl-prev with: - path: ~/ngtcp2-openssl-prev/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/ngtcp2-openssl-prev + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGTCP2_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache nghttp2' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp2 + id: cache1-nghttp2 env: - cache-name: cache-nghttp2 + cache-name: cache1-nghttp2 with: - path: ~/nghttp2/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.OPENSSL_VERSION }}-${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }} + path: ~/pkg/nghttp2 + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.OPENSSL_VERSION }}-${{ env.NGTCP2_VERSION }}-${{ env.NGHTTP3_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache h2o' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-h2o + id: cache1-h2o env: - cache-name: cache-h2o + cache-name: cache1-h2o with: - path: ~/h2o/build - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }} + path: ~/pkg/h2o + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.H2O_VERSION }}-${{ env.OPENSSL_PREV_VERSION }}-${{ runner.arch }} fail-on-cache-miss: true - name: 'cache quiche' if: ${{ contains(matrix.build.name, 'quiche') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-quiche + id: cache1-quiche env: - cache-name: cache-quiche + cache-name: cache1-quiche with: path: ~/quiche - key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.QUICHE_VERSION }} + key: ${{ runner.os }}-http3-build-${{ env.cache-name }}-${{ env.QUICHE_VERSION }}-${{ runner.arch }} - name: 'build quiche and boringssl' - if: ${{ contains(matrix.build.name, 'quiche') && !steps.cache-quiche.outputs.cache-hit }} + if: ${{ contains(matrix.build.name, 'quiche') && !steps.cache1-quiche.outputs.cache-hit }} run: | cd ~ git clone --quiet --depth 1 --branch "${QUICHE_VERSION}" --recursive https://github.com/cloudflare/quiche cd quiche - cargo build -v --package quiche --release --features ffi,pkg-config-meta,qlog --verbose + if [ "$(uname -m)" = 'aarch64' ]; then + export CARGO_PROFILE_RELEASE_OPT_LEVEL=0 # to avoid test 2500-2503 failing valgrind with arm64 non-debug quiche 0.30.0 builds + fi + cargo build --package quiche --release --features ffi,pkg-config-meta,qlog -vv ln -s libquiche.so target/release/libquiche.so.0 cd .. mkdir -p quiche/boringssl/lib find quiche/target/release \( -name libcrypto.a -o -name libssl.a \) -exec ln -vnf -- '{}' quiche/boringssl/lib \; - find quiche/target/release/build/boring-sys-*/out/boringssl/src -maxdepth 1 \( -name include \) -exec ln -vsf -- '../../{}' quiche/boringssl \; + find quiche/target/release/build/boring-sys-*/out/boringssl -maxdepth 1 \( -name include \) -exec ln -vsf -- '../../{}' quiche/boringssl \; + # Reduce cached blob size + rm -rf quiche/target/release/build/boring-sys-*/out/build/CMakeFiles + rm -rf quiche/target/release/deps + du -t 4MB quiche - # include dir - # /home/runner/quiche/boringssl/include - # lib dir - # /home/runner/quiche/boringssl/lib + # include dir: /home/runner/quiche/boringssl/include + # lib dir: /home/runner/quiche/boringssl/lib - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -782,29 +792,33 @@ jobs: - name: 'configure' env: + CC: '${{ matrix.build.CC }}' + CFLAGS: '${{ matrix.build.CFLAGS }}' LDFLAGS: '${{ matrix.build.LDFLAGS }}' + MATRIX_NAME: '${{ matrix.build.name }}' MATRIX_CONFIGURE: '${{ matrix.build.configure }}' MATRIX_GENERATE: '${{ matrix.build.generate }}' MATRIX_PKG_CONFIG_PATH: '${{ matrix.build.PKG_CONFIG_PATH }}' run: | [ -n "${MATRIX_PKG_CONFIG_PATH}" ] && export PKG_CONFIG_PATH="${MATRIX_PKG_CONFIG_PATH}" if [ "${MATRIX_BUILD}" = 'cmake' ]; then - [[ "${MATRIX_GENERATE}" = *'boringssl'* ]] && options=" -DBORINGSSL_VERSION=${BORINGSSL_VERSION}" + [[ "${MATRIX_NAME}" = *'quiche'* ]] && sudo apt-get -o Dpkg::Use-Pty=0 purge libssl-dev + [[ "${MATRIX_NAME}" = *'boringssl'* ]] && options=" -DBORINGSSL_VERSION=${BORINGSSL_VERSION}" cmake -B bld -G Ninja \ -DCMAKE_C_COMPILER_TARGET="$(uname -m)-unknown-linux-gnu" -DBUILD_STATIC_LIBS=ON \ -DCURL_WERROR=ON -DENABLE_DEBUG=ON \ -DCURL_USE_LIBUV=ON -DCURL_ENABLE_NTLM=ON \ - -DTEST_NGHTTPX=/home/runner/nghttp2/build/bin/nghttpx \ - -DH2O=/home/runner/h2o/build/bin/h2o \ - -DHTTPD_NGHTTPX=/home/runner/nghttp2/build/bin/nghttpx \ + -DTEST_NGHTTPX=/home/runner/pkg/nghttp2/bin/nghttpx \ + -DH2O=/home/runner/pkg/h2o/bin/h2o \ + -DHTTPD_NGHTTPX=/home/runner/pkg/nghttp2/bin/nghttpx \ ${MATRIX_GENERATE} ${options} else - [[ "${MATRIX_CONFIGURE}" = *'boringssl'* ]] && export CPPFLAGS="-DCURL_BORINGSSL_VERSION=\\\"${BORINGSSL_VERSION}\\\"" + [[ "${MATRIX_NAME}" = *'boringssl'* ]] && export CPPFLAGS="-DCURL_BORINGSSL_VERSION=\\\"${BORINGSSL_VERSION}\\\"" mkdir bld && cd bld && ../configure --enable-warnings --enable-werror --enable-debug --disable-static \ --disable-dependency-tracking --enable-option-checking=fatal \ --with-libuv --enable-ntlm \ - --with-test-h2o=/home/runner/h2o/build/bin/h2o \ - --with-test-nghttpx=/home/runner/nghttp2/build/bin/nghttpx \ + --with-test-h2o=/home/runner/pkg/h2o/bin/h2o \ + --with-test-nghttpx=/home/runner/pkg/nghttp2/bin/nghttpx \ ${MATRIX_CONFIGURE} fi @@ -836,8 +850,8 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- bld/src/curl --disable -V - name: 'build tests' @@ -849,21 +863,12 @@ jobs: make -C bld V=1 -C tests fi - - name: 'install test prereqs' - if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} - run: | - python3 -m venv ~/venv - if bld/src/curl --disable -V 2>/dev/null | grep smb; then - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi - - name: 'run tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} env: TFLAGS: '${{ matrix.build.tflags }}' run: | TFLAGS+=' -n' - source ~/venv/bin/activate if [ "${MATRIX_BUILD}" = 'cmake' ]; then cmake --build bld --verbose --target test-ci else @@ -874,7 +879,6 @@ jobs: if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} run: | export TFLAGS='-j6 --min=4 HTTP/3' - source ~/venv/bin/activate if [ "${MATRIX_BUILD}" = 'cmake' ]; then cmake --build bld --verbose --target test-ci else @@ -883,9 +887,9 @@ jobs: - name: 'install pytest prereqs' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} - run: | - [ -d ~/venv ] || python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/http/requirements.txt + uses: $/.github/actions/pkg-install + with: + pip: -r tests/http/requirements.txt - name: 'run pytest (event based)' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index fa06c826ac5a..7e314c19ba27 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -17,6 +17,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: DO_NOT_TRACK: '1' @@ -24,7 +25,7 @@ env: jobs: label: name: 'Labeler' - runs-on: ubuntu-slim + runs-on: ubuntu-24.04-firewall permissions: contents: read # To comply with https://github.com/actions/labeler documentation pull-requests: write # To edit labels on PRs diff --git a/.github/workflows/linux-old.yml b/.github/workflows/linux-old.yml index 7fe01f69b436..a41a1f10a676 100644 --- a/.github/workflows/linux-old.yml +++ b/.github/workflows/linux-old.yml @@ -46,11 +46,12 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: MAKEFLAGS: -j 5 CURL_CI: github - CURL_TEST_MIN: 1560 + CURL_TEST_MIN: 1680 DEBIAN_FRONTEND: noninteractive DO_NOT_TRACK: '1' @@ -58,7 +59,7 @@ jobs: cmake-autotools: name: 'autotools & cmake' runs-on: ubuntu-latest - container: debian:stretch-20220622-slim@sha256:c5cd3ffceeb25b683bf5111ea89bf8049a177e00fb237235d48076a19cc80097 + container: debian:stretch-20220622-slim@sha256:c5cd3ffceeb25b683bf5111ea89bf8049a177e00fb237235d48076a19cc80097 # manually bumped (oldest supported Debian major version, rule disabled) steps: - name: 'install prereqs' @@ -83,7 +84,7 @@ jobs: - name: 'install prereqs (cmake)' env: - CMAKE_VERSION: 3.18.0 # Earliest version supported by curl + CMAKE_VERSION: 3.18.0 # manually bumped (lowest minor version supported by curl) CMAKE_SHA256: 4d9a9d3351161073a67e49366d701b6fa4b0343781982dc5eef08a02a750d403 run: | cd ~ diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a2d610219f56..93a9fd143860 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -30,36 +30,35 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: write env: MAKEFLAGS: -j 5 CURL_CI: github - CURL_TEST_MIN: 1660 + CURL_TEST_MIN: 1800 DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' - # renovate: datasource=github-tags depName=awslabs/aws-lc versioning=semver registryUrl=https://github.com - AWSLC_VERSION: 5.5.0 - # renovate: datasource=github-tags depName=google/boringssl versioning=semver registryUrl=https://github.com - BORINGSSL_VERSION: 0.20260730.0 - # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced registryUrl=https://github.com - FIL_C_VERSION: 0.684 - # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com - LIBRESSL_VERSION: 4.3.2 - # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=awslabs/aws-lc + AWSLC_VERSION: 5.11.0 + # renovate: datasource=github-tags depName=google/boringssl + BORINGSSL_VERSION: 0.20260929.0 + # renovate: datasource=github-releases depName=pizlonator/fil-c versioning=semver-coerced + FIL_C_VERSION: 0.686 + # renovate: datasource=github-tags depName=libressl/portable + LIBRESSL_VERSION: 4.3.3 + # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls MBEDTLS_VERSION: 4.2.0 - # manually bumped + # renovate: datasource=github-tags depName=Mbed-TLS/mbedtls extractVersion=^v?(?3\..+)$ MBEDTLS_PREV_VERSION: 3.6.7 - MBEDTLS_PREV_SHA256: a7e8bcbec0e6f761b4af24f25677626b35f762f68eef79c08677a363212d11f6 - # renovate: datasource=github-tags depName=nghttp2/nghttp2 versioning=semver registryUrl=https://github.com + # renovate: datasource=github-tags depName=nghttp2/nghttp2 NGHTTP2_VERSION: 1.70.0 - # handled in renovate.json - OPENLDAP_VERSION: 2.6.10 - # renovate: datasource=github-releases depName=openssl/openssl versioning=semver extractVersion=^openssl-(?.+)$ registryUrl=https://github.com - OPENSSL_VERSION: 4.0.2 - # renovate: datasource=github-tags depName=rustls/rustls-ffi versioning=semver registryUrl=https://github.com - RUSTLS_VERSION: 0.15.3 - # renovate: datasource=github-tags depName=wolfSSL/wolfssl versioning=semver extractVersion=^v?(?.+)-stable$ registryUrl=https://github.com - WOLFSSL_VERSION: 5.9.2 + # renovate: datasource=github-tags depName=openldap/openldap versioning=regex:^(?\d+)_(?\d+)_(?\d+)$ extractVersion=^OPENLDAP_REL_ENG_(?.+)$ + OPENLDAP_VERSION: 2_7_1 + # renovate: datasource=github-releases depName=openssl/openssl extractVersion=^openssl-(?.+)$ + OPENSSL_VERSION: 4.0.3 + # renovate: datasource=github-tags depName=rustls/rustls-ffi + RUSTLS_VERSION: 0.15.4 + # renovate: datasource=github-tags depName=wolfSSL/wolfssl extractVersion=^v?(?.+)-stable$ + WOLFSSL_VERSION: 5.9.4 jobs: linux: @@ -76,56 +75,56 @@ jobs: matrix: build: - name: 'awslc' - install_steps: awslc pytest - LDFLAGS: -Wl,-rpath,/home/runner/awslc/lib - configure: --with-openssl=/home/runner/awslc --enable-ech --enable-ntlm --enable-smb + install_steps: awslc pytest test-linter + LDFLAGS: -Wl,-rpath,/home/runner/pkg/awslc/lib + configure: --with-openssl=/home/runner/pkg/awslc --enable-ech --enable-ntlm - name: 'awslc' install_packages: libidn2-dev install_steps: awslc clean - generate: -DOPENSSL_ROOT_DIR=/home/runner/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF -DCURL_DROP_UNUSED=ON -DCURL_PATCHSTAMP=test-patch -DCURL_ENABLE_NTLM=ON + generate: -DOPENSSL_ROOT_DIR=/home/runner/pkg/awslc -DUSE_ECH=ON -DCMAKE_UNITY_BUILD=OFF -DCURL_DROP_UNUSED=ON -DCURL_PATCHSTAMP=test-patch -DCURL_ENABLE_NTLM=ON - name: 'boringssl' install_steps: boringssl pytest - generate: -DOPENSSL_ROOT_DIR=/home/runner/boringssl -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON + generate: -DOPENSSL_ROOT_DIR=/home/runner/pkg/boringssl -DOPENSSL_USE_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DUSE_ECH=ON -DCURL_ENABLE_NTLM=ON - name: 'libressl krb5' image: ubuntu-24.04-arm install_packages: libidn2-dev libnghttp2-dev libldap-dev libkrb5-dev - install_steps: libressl-c-arm pytest codeset-test1 - LDFLAGS: -Wl,-rpath,/home/runner/libressl/lib - configure: --with-openssl=/home/runner/libressl --with-gssapi --enable-debug + install_steps: libressl-c pytest codeset-test1 + LDFLAGS: -Wl,-rpath,/home/runner/pkg/libressl/lib + configure: --with-openssl=/home/runner/pkg/libressl --with-gssapi --enable-debug - name: 'libressl krb5 valgrind 1' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev libkrb5-dev valgrind - install_steps: libressl-c-arm + install_steps: libressl-c tflags: '--subset=0/2' - generate: -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON + generate: -DOPENSSL_ROOT_DIR=/home/runner/pkg/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON - name: 'libressl krb5 valgrind 2' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev libkrb5-dev valgrind - install_steps: libressl-c-arm + install_steps: libressl-c tflags: '--subset=1/2' - generate: -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON + generate: -DOPENSSL_ROOT_DIR=/home/runner/pkg/libressl -DCURL_USE_GSSAPI=ON -DENABLE_DEBUG=ON -DCURL_LIBCURL_VERSIONED_SYMBOLS=ON -DCURL_ENABLE_NTLM=ON - name: 'libressl clang' image: ubuntu-24.04-arm install_packages: clang - install_steps: libressl-c-arm + install_steps: libressl-c CC: clang - LDFLAGS: -Wl,-rpath,/home/runner/libressl/lib - configure: --with-openssl=/home/runner/libressl --enable-debug + LDFLAGS: -Wl,-rpath,/home/runner/pkg/libressl/lib + configure: --with-openssl=/home/runner/pkg/libressl --enable-debug - name: 'libressl Fil-C' install_steps: filc libressl-filc nghttp2-filc pytest tflags: '!776' # adds 1-9 minutes to the test run step, and fails consistently CC: /home/runner/filc/build/bin/filcc - PKG_CONFIG_PATH: /home/runner/nghttp2/lib/pkgconfig + PKG_CONFIG_PATH: /home/runner/pkg/nghttp2/lib/pkgconfig generate: >- -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_DISABLE_TYPECHECK=ON - -DOPENSSL_ROOT_DIR=/home/runner/libressl -DCURL_USE_LIBPSL=OFF + -DOPENSSL_ROOT_DIR=/home/runner/pkg/libressl -DCURL_USE_LIBPSL=OFF -DCURL_ZLIB=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_DISABLE_LDAP=ON -DUSE_LIBIDN2=OFF -DCURL_USE_LIBSSH2=OFF -DCURL_ENABLE_NTLM=ON @@ -133,105 +132,113 @@ jobs: - name: 'mbedtls gss valgrind 1' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libidn2-dev libldap-dev libgss-dev valgrind - install_steps: mbedtls-latest-arm + install_steps: mbedtls-latest tflags: '--subset=0/2' - LDFLAGS: -Wl,-rpath,/home/runner/mbedtls/lib - PKG_CONFIG_PATH: /home/runner/mbedtls/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/mbedtls/lib + PKG_CONFIG_PATH: /home/runner/pkg/mbedtls/lib/pkgconfig generate: -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_USE_GSSAPI=ON -DCURL_DROP_UNUSED=ON - name: 'mbedtls gss valgrind 2' image: ubuntu-24.04-arm install_packages: libnghttp2-dev libidn2-dev libldap-dev libgss-dev valgrind - install_steps: mbedtls-latest-arm + install_steps: mbedtls-latest tflags: '--subset=1/2' - LDFLAGS: -Wl,-rpath,/home/runner/mbedtls/lib - PKG_CONFIG_PATH: /home/runner/mbedtls/lib/pkgconfig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/mbedtls/lib + PKG_CONFIG_PATH: /home/runner/pkg/mbedtls/lib/pkgconfig generate: -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_USE_GSSAPI=ON - name: 'mbedtls clang' install_packages: libssh-dev libnghttp2-dev libldap-dev clang - install_steps: mbedtls-latest-intel pytest + install_steps: mbedtls-latest pytest CC: clang - LDFLAGS: -Wl,-rpath,/home/runner/mbedtls/lib - configure: --with-mbedtls=/home/runner/mbedtls --with-libssh --enable-debug --with-fish-functions-dir --with-zsh-functions-dir + LDFLAGS: -Wl,-rpath,/home/runner/pkg/mbedtls/lib + configure: --with-mbedtls=/home/runner/pkg/mbedtls --with-libssh --enable-debug --with-fish-functions-dir --with-zsh-functions-dir - name: 'mbedtls-prev' install_packages: libssh2-1-dev libnghttp2-dev libuv1-dev install_steps: mbedtls-prev pytest - PKG_CONFIG_PATH: /home/runner/mbedtls-prev/lib/pkgconfig # Requires v3.6.0 + PKG_CONFIG_PATH: /home/runner/pkg/mbedtls-prev/lib/pkgconfig # Requires v3.6.0 generate: -DCURL_USE_MBEDTLS=ON -DCURL_USE_LIBUV=ON -DENABLE_DEBUG=ON - name: 'mbedtls-pkg MultiSSL !pc' install_packages: libnghttp2-dev libmbedtls-dev - install_steps: mbedtls-latest-intel skipall + install_steps: mbedtls-latest skipall generate: >- -DCURL_USE_MBEDTLS=ON -DENABLE_DEBUG=ON -DCURL_DEFAULT_SSL_BACKEND=mbedtls - -DMBEDTLS_INCLUDE_DIR=/home/runner/mbedtls/include - -DMBEDTLS_LIBRARY=/home/runner/mbedtls/lib/libmbedtls.a - -DMBEDX509_LIBRARY=/home/runner/mbedtls/lib/libmbedx509.a - -DMBEDCRYPTO_LIBRARY=/home/runner/mbedtls/lib/libmbedcrypto.a + -DMBEDTLS_INCLUDE_DIR=/home/runner/pkg/mbedtls/include + -DMBEDTLS_LIBRARY=/home/runner/pkg/mbedtls/lib/libmbedtls.a + -DMBEDX509_LIBRARY=/home/runner/pkg/mbedtls/lib/libmbedx509.a + -DMBEDCRYPTO_LIBRARY=/home/runner/pkg/mbedtls/lib/libmbedcrypto.a -DCURL_USE_PKGCONFIG=OFF -DCURL_USE_OPENSSL=ON -DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF -DCURL_COMPLETION_FISH=ON -DCURL_COMPLETION_ZSH=ON - name: 'rustls valgrind 1' + image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev valgrind install_steps: rust rustls + PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig tflags: '--subset=0/2' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - name: 'rustls valgrind 2' + image: ubuntu-24.04-arm install_packages: libnghttp2-dev libldap-dev valgrind install_steps: rust rustls + PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig tflags: '--subset=1/2' generate: -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DENABLE_DEBUG=ON - name: 'rustls' install_packages: libnghttp2-dev libldap-dev install_steps: rust rustls skiprun pytest + LDFLAGS: -Wl,-rpath,/home/runner/rustls/lib + PKG_CONFIG_PATH: /home/runner/rustls/lib/pkgconfig configure: --with-rustls --enable-ech --enable-debug - name: 'wolfssl-all' image: ubuntu-26.04-arm - install_steps: wolfssl-all-arm - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-all/lib - configure: --with-wolfssl=/home/runner/wolfssl-all --enable-ech --enable-debug + install_steps: wolfssl-all + LDFLAGS: -Wl,-rpath,/home/runner/pkg/wolfssl-all/lib + configure: --with-wolfssl=/home/runner/pkg/wolfssl-all --enable-ech --enable-debug - name: 'wolfssl-opensslextra valgrind 1' image: ubuntu-26.04-arm install_packages: valgrind - install_steps: wolfssl-opensslextra-arm + install_steps: wolfssl-opensslextra tflags: '--subset=0/2' - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib - configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/wolfssl-opensslextra/lib + configure: --with-wolfssl=/home/runner/pkg/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig - name: 'wolfssl-opensslextra valgrind 2' image: ubuntu-26.04-arm install_packages: valgrind - install_steps: wolfssl-opensslextra-arm + install_steps: wolfssl-opensslextra tflags: '--subset=1/2' - LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib - configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig + LDFLAGS: -Wl,-rpath,/home/runner/pkg/wolfssl-opensslextra/lib + configure: --with-wolfssl=/home/runner/pkg/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig - name: 'openssl default' + image: ubuntu-24.04 install_steps: pytest configure: --with-openssl --enable-debug --disable-unity - - name: 'openssl libssh2 sync-resolver valgrind 1 +analyzer' + - name: 'openssl libssh2 sync-resolver valgrind 1' image: ubuntu-26.04-arm install_packages: gcc-16 libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind CC: gcc-16 tflags: '--subset=0/2' - generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_GCC_ANALYZER=ON -DCURL_ENABLE_NTLM=ON + generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON - - name: 'openssl libssh2 sync-resolver valgrind 2' + - name: 'openssl libssh2 sync-resolver valgrind 2 +analyzer' image: ubuntu-26.04-arm install_packages: gcc-16 libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev valgrind CC: gcc-16 tflags: '--subset=1/2' - generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON + generate: -DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON -DCURL_GCC_ANALYZER=ON - name: 'openssl intel C89' + image: ubuntu-24.04 install_packages: libssh-dev install_steps: pytest configure: CFLAGS=-std=gnu89 --with-openssl --with-libssh --enable-debug @@ -243,24 +250,27 @@ jobs: configure: CFLAGS=-std=gnu89 --with-openssl --with-libssh2 --enable-debug --disable-verbose - name: 'openssl -O3 libssh valgrind 1' + image: ubuntu-24.04-arm install_packages: libssh-dev valgrind CFLAGS: -O3 tflags: '--subset=0/2' generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=50 -DCURL_ENABLE_NTLM=ON - name: 'openssl -O3 libssh valgrind 2' + image: ubuntu-24.04-arm install_packages: libssh-dev valgrind CFLAGS: -O3 tflags: '--subset=1/2' generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=50 -DCURL_ENABLE_NTLM=ON - name: 'openssl clang krb5 openldap static' + image: ubuntu-24.04 install_steps: openldap-static install_packages: libidn2-dev libkrb5-dev clang libssl-dev CC: clang configure: >- --enable-static --disable-shared --with-openssl --with-gssapi --enable-debug --disable-docs - --disable-manual --with-ldap=/home/runner/openldap-static --with-ldap-lib=ldap --with-lber-lib=lber + --disable-manual --with-ldap=/home/runner/pkg/openldap-static --with-ldap-lib=ldap --with-lber-lib=lber - name: 'openssl clang krb5 LTO' image: ubuntu-24.04-arm @@ -271,27 +281,30 @@ jobs: - name: 'openssl !ipv6 !--libcurl !--digest-auth' image: ubuntu-24.04-arm - configure: --with-openssl --disable-ipv6 --enable-debug --disable-unity --disable-libcurl-option --disable-digest-auth --enable-ntlm --enable-smb + configure: --with-openssl --disable-ipv6 --enable-debug --disable-unity --disable-libcurl-option --disable-digest-auth --enable-ntlm - - name: 'curl_global_init_mem debug valgrind' + - name: 'curl_global_init_mem debug valgrind 1' image: ubuntu-24.04-arm install_packages: valgrind - tflags: '--min=1700' - configure: >- - --enable-init-mem-debug - --with-openssl --disable-debug --enable-unity - --enable-ntlm --enable-smb + tflags: '--subset=0/2' + configure: --enable-init-mem-debug --with-openssl --disable-debug --enable-ntlm + + - name: 'curl_global_init_mem debug valgrind 2' + image: ubuntu-24.04-arm + install_packages: valgrind + tflags: '--subset=1/2' + configure: --enable-init-mem-debug --with-openssl --disable-debug --enable-ntlm - name: 'openssl https-only' image: ubuntu-24.04-arm - tflags: '--min=1260' + tflags: '--min=1360' configure: >- --with-openssl --enable-debug --disable-unity --disable-dict --disable-gopher --disable-ldap --disable-telnet --disable-imap --disable-pop3 --disable-smtp --disable-rtsp --without-libssh2 --without-libssh - --disable-tftp --disable-ftp --disable-file --disable-smb + --disable-tftp --disable-ftp --disable-file --enable-ntlm - name: 'openssl torture 1' @@ -307,6 +320,7 @@ jobs: generate: -DCURL_USE_OPENSSL=ON -DENABLE_DEBUG=ON -DENABLE_ARES=ON -DCURL_ENABLE_NTLM=ON -DCURL_DISABLE_HTTPSIG=OFF - name: 'openssl i686' + image: ubuntu-24.04 install_packages: gcc-14-i686-linux-gnu libssl-dev:i386 libssh2-1-dev:i386 libidn2-dev:i386 libc-ares-dev:i386 zlib1g-dev:i386 CC: i686-linux-gnu-gcc-14 LDFLAGS: -L/usr/lib/i386-linux-gnu @@ -318,26 +332,26 @@ jobs: - name: '!ssl !http !smtp !imap' image: ubuntu-26.04-arm - tflags: '--min=500' + tflags: '--min=520' configure: --without-ssl --enable-debug --disable-http --disable-smtp --disable-imap --disable-unity --enable-httpsig - name: 'clang-tidy' - image: ubuntu-26.04 + image: ubuntu-26.04-arm install_packages: clang-22 clang-tidy-22 libssl-dev libidn2-dev libssh2-1-dev libnghttp2-dev libldap-dev libkrb5-dev libgnutls28-dev - install_steps: skiprun mbedtls-latest-intel rustls wolfssl-opensslextra-intel + install_steps: skiprun mbedtls-latest rustls wolfssl-opensslextra install_steps_brew: openssl@4 gsasl CC: clang-22 CFLAGS: -Wunused-macros LDFLAGS: >- - -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib - -Wl,-rpath,/home/runner/mbedtls/lib + -Wl,-rpath,/home/runner/pkg/wolfssl-opensslextra/lib + -Wl,-rpath,/home/runner/pkg/mbedtls/lib -Wl,-rpath,/home/runner/rustls/lib -Wl,-rpath,/home/linuxbrew/.linuxbrew/opt/openssl@4/lib -Wl,-rpath,/home/linuxbrew/.linuxbrew/opt/gsasl/lib PKG_CONFIG_PATH: "\ - /home/runner/wolfssl-opensslextra/lib/pkgconfig:\ - /home/runner/mbedtls/lib/pkgconfig:\ + /home/runner/pkg/wolfssl-opensslextra/lib/pkgconfig:\ + /home/runner/pkg/mbedtls/lib/pkgconfig:\ /home/runner/rustls/lib/pkgconfig:\ /home/linuxbrew/.linuxbrew/opt/gsasl/lib/pkgconfig" generate: >- @@ -348,7 +362,7 @@ jobs: -DCURL_CLANG_TIDY=ON -DCLANG_TIDY=/usr/bin/clang-tidy-22 - name: 'clang-tidy H3 c-ares !examples' - image: ubuntu-26.04 + image: ubuntu-26.04-arm install_packages: clang-22 clang-tidy-22 libidn2-dev libssh-dev libnghttp2-dev install_steps: skiprun install_steps_brew: libngtcp2 libnghttp3 c-ares @@ -392,6 +406,7 @@ jobs: install_packages: clang-22 libubsan1 libasan8 libtsan2 install_steps: pytest install_steps_brew: openssl libssh2 libngtcp2 libnghttp3 c-ares + tflags: '!664 !665' # MD5 support is disabled in Homebrew since libssh2 1.11.1_6 CC: clang-22 CFLAGS: >- -fsanitize=address,bounds,leak,signed-integer-overflow,undefined @@ -418,7 +433,7 @@ jobs: CC: clang-22 CFLAGS: -fsanitize=thread LDFLAGS: -fsanitize=thread - generate: -DCMAKE_BUILD_TYPE=Debug -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/home/runner/openssl -DUSE_ECH=ON + generate: -DCMAKE_BUILD_TYPE=Debug -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/home/runner/pkg/openssl -DUSE_ECH=ON - name: 'memory-sanitizer' image: ubuntu-26.04 @@ -429,12 +444,13 @@ jobs: LDFLAGS: -fsanitize=memory LIBS: -ldl configure: --without-ssl --without-zlib --without-brotli --without-zstd --without-libpsl --without-nghttp2 --enable-debug - tflags: '--min=1540' + tflags: '--min=1640' - name: 'event-based' + image: ubuntu-24.04 install_packages: libssh-dev - configure: --enable-debug --enable-static --disable-shared --disable-threaded-resolver --with-libssh --with-openssl --enable-ntlm --enable-smb - tflags: '-n --test-event --min=1420' + configure: --enable-debug --enable-static --disable-shared --disable-threaded-resolver --with-libssh --with-openssl --enable-ntlm + tflags: '-n --test-event --min=1520' - name: 'duphandle' image: ubuntu-26.04-arm @@ -443,63 +459,50 @@ jobs: tflags: '-n --test-duphandle' - name: 'IntelC openssl' - install_packages: libssl-dev + install_packages: libssl-dev libssh2-1-dev libnghttp2-dev libidn2-dev libldap-dev libkrb5-dev install_steps: intelc CC: icc - configure: --enable-debug --with-openssl + configure: --enable-debug --with-openssl --with-libssh2 --with-gssapi --enable-httpsig - - name: 'Slackware !ssl gssapi gcc' - # Flags used to build the curl Slackware package, except OpenSSL 1.1.0: + - name: 'Slackware GnuTLS gssapi gcc' + # Flags used to build the curl Slackware package: # https://ftpmirror.infania.net/slackware/slackware64-current/source/n/curl/curl.SlackBuild - configure: --enable-debug --without-ssl --with-libssh2 --with-gssapi --enable-ares --without-ca-bundle --with-ca-path=/etc/ssl/certs + # Current curl no longer supports OpenSSL 1.1.0, thus build with GnuTLS instead: + configure: --enable-debug --with-gnutls --with-libssh2 --with-gssapi --enable-ares --without-ca-bundle --with-ca-path=/etc/ssl/certs # Docker Hub image that `container-job` executes in - container: andy5995/slackware-build-essential:15.0@sha256:f4f2242999038a2c2deb4e5727187caaae92502a7daf8353068932621e1ec92f + container: andy5995/slackware-build-essential:15.0@sha256:c5aeede463b4a52b5f4650078418718f2447bdb23f9a4cac0d5fb262e7b8dc84 # manually bumped - name: 'Alpine MUSL https-rr' configure: --enable-debug --with-ssl --with-libssh2 --with-libidn2 --with-gssapi --enable-ldap --with-libpsl --enable-httpsrr --enable-ares --enable-threaded-resolver - container: alpine:3.23@sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11 # 3.23.4 + container: alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 # 3.24.2 (manually bumped) - name: 'Alpine MUSL https-rr c-ares' configure: --enable-debug --with-ssl --with-libssh2 --with-libidn2 --with-gssapi --enable-ldap --with-libpsl --enable-httpsrr --enable-ares --disable-threaded-resolver - container: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc # 3.20.10 + container: alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc # 3.20.10 (manually bumped, a previous minor version) steps: - name: 'install prereqs' if: ${{ matrix.build.container == null && !contains(matrix.build.name, 'i686') }} timeout-minutes: 3 - env: - INSTALL_PACKAGES_BREW: '${{ matrix.build.install_steps_brew }}' - INSTALL_PACKAGES: >- + uses: $/.github/actions/pkg-install + with: + brew: ${{ matrix.build.install_steps_brew }} + apt: >- + libtool autoconf automake pkgconf + libpsl-dev zlib1g-dev libbrotli-dev libzstd-dev ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && 'stunnel4' || '' }} ${{ contains(matrix.build.install_steps, 'pytest') && 'apache2 apache2-dev libnghttp2-dev vsftpd dante-server' || '' }} - - run: | - ls -l /etc/apt/sources.list.d - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libtool autoconf automake pkgconf \ - libpsl-dev zlib1g-dev libbrotli-dev libzstd-dev \ - ${INSTALL_PACKAGES} \ - ${MATRIX_INSTALL_PACKAGES} - if [ -n "${INSTALL_PACKAGES_BREW}" ]; then - /home/linuxbrew/.linuxbrew/bin/brew install ca-certificates || true - /home/linuxbrew/.linuxbrew/bin/brew install ${INSTALL_PACKAGES_BREW} - fi + ${{ matrix.build.install_packages }} - name: 'install prereqs (i686)' if: ${{ contains(matrix.build.name, 'i686') }} timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo dpkg --add-architecture i386 - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install \ - libtool autoconf automake pkgconf stunnel4 \ - libpsl-dev:i386 libbrotli-dev:i386 libzstd-dev:i386 \ - ${MATRIX_INSTALL_PACKAGES} + uses: $/.github/actions/pkg-install + with: + apt: >- + libtool autoconf automake pkgconf stunnel4 + libpsl-dev:i386 libbrotli-dev:i386 libzstd-dev:i386 + ${{ matrix.build.install_packages }} - name: 'install prereqs (alpine)' if: ${{ startsWith(matrix.build.container, 'alpine') }} @@ -513,51 +516,52 @@ jobs: if: ${{ contains(matrix.build.install_steps, 'filc') }} run: | cd /home/runner + arch="$(uname -m)" curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/pizlonator/fil-c/releases/download/v${FIL_C_VERSION}/filc-${FIL_C_VERSION}-linux-x86_64.tar.xz" --output pkg.bin - sha256sum pkg.bin && tar -xJf pkg.bin && rm -f pkg.bin && mv "filc-${FIL_C_VERSION}-linux-x86_64" filc + --location --proto-redir =https "https://github.com/pizlonator/fil-c/releases/download/v${FIL_C_VERSION}/filc-${FIL_C_VERSION}-linux-${arch}.tar.xz" --output pkg.bin + sha256sum pkg.bin && tar -xJf pkg.bin && rm -f pkg.bin && mv "filc-${FIL_C_VERSION}-linux-${arch}" filc cd filc ./setup.sh - - name: 'cache libressl (c-arm)' - if: ${{ contains(matrix.build.install_steps, 'libressl-c-arm') }} + - name: 'cache libressl (c)' + if: ${{ contains(matrix.build.install_steps, 'libressl-c') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-libressl-c-arm + id: cache1-libressl-c env: - cache-name: cache-libressl-c-arm + cache-name: cache1-libressl-c with: - path: ~/libressl - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }} + path: ~/pkg/libressl + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ runner.arch }} - - name: 'build libressl (c-arm)' - if: ${{ contains(matrix.build.install_steps, 'libressl-c-arm') && !steps.cache-libressl-c-arm.outputs.cache-hit }} + - name: 'build libressl (c)' + if: ${{ contains(matrix.build.install_steps, 'libressl-c') && !steps.cache1-libressl-c.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl -DCURL_ENABLE_NTLM=ON + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/libressl cmake --build . cmake --install . - name: 'cache libressl (filc)' if: ${{ contains(matrix.build.install_steps, 'libressl-filc') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-libressl-filc + id: cache1-libressl-filc env: - cache-name: cache-libressl-filc + cache-name: cache1-libressl-filc with: - path: ~/libressl - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ env.FIL_C_VERSION }} + path: ~/pkg/libressl + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.LIBRESSL_VERSION }}-${{ env.FIL_C_VERSION }}-${{ runner.arch }} - name: 'build libressl (filc)' - if: ${{ contains(matrix.build.install_steps, 'libressl-filc') && !steps.cache-libressl-filc.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'libressl-filc') && !steps.cache1-libressl-filc.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/libressl \ + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/libressl \ -DCMAKE_C_COMPILER=/home/runner/filc/build/bin/filcc -DENABLE_ASM=OFF cmake --build . cmake --install . @@ -565,107 +569,84 @@ jobs: - name: 'cache nghttp2 (filc)' if: ${{ contains(matrix.build.install_steps, 'nghttp2-filc') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-nghttp2-filc + id: cache1-nghttp2-filc env: - cache-name: cache-nghttp2-filc + cache-name: cache1-nghttp2-filc with: - path: ~/nghttp2 - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.FIL_C_VERSION }} + path: ~/pkg/nghttp2 + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.NGHTTP2_VERSION }}-${{ env.FIL_C_VERSION }}-${{ runner.arch }} - name: 'build nghttp2 (filc)' - if: ${{ contains(matrix.build.install_steps, 'nghttp2-filc') && !steps.cache-nghttp2-filc.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'nghttp2-filc') && !steps.cache1-nghttp2-filc.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/nghttp2/nghttp2/releases/download/v${NGHTTP2_VERSION}/nghttp2-${NGHTTP2_VERSION}.tar.xz" --output pkg.bin sha256sum pkg.bin && tar -xJf pkg.bin && rm -f pkg.bin cd "nghttp2-${NGHTTP2_VERSION}" - cmake -B . -G Ninja -DENABLE_LIB_ONLY=ON -DBUILD_TESTING=OFF -DENABLE_DOC=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/nghttp2 \ + cmake -B . -G Ninja -DENABLE_LIB_ONLY=ON -DBUILD_TESTING=OFF -DENABLE_DOC=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/nghttp2 \ -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF \ -DCMAKE_C_COMPILER=/home/runner/filc/build/bin/filcc cmake --build . cmake --install . - - name: 'cache wolfssl (all-arm)' - if: ${{ contains(matrix.build.install_steps, 'wolfssl-all-arm') }} + - name: 'cache wolfssl (all)' + if: ${{ contains(matrix.build.install_steps, 'wolfssl-all') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl-all-arm + id: cache1-wolfssl-all env: - cache-name: cache-wolfssl-all-arm + cache-name: cache1-wolfssl-all with: - path: ~/wolfssl-all - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/wolfssl-all + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }} - - name: 'build wolfssl (all-arm)' # does not support `OPENSSL_COEXIST` - if: ${{ contains(matrix.build.install_steps, 'wolfssl-all-arm') && !steps.cache-wolfssl-all-arm.outputs.cache-hit }} + - name: 'build wolfssl (all)' # does not support `OPENSSL_COEXIST` + if: ${{ contains(matrix.build.install_steps, 'wolfssl-all') && !steps.cache1-wolfssl-all.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "wolfssl-${WOLFSSL_VERSION}-stable" ./autogen.sh - ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-all \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/wolfssl-all \ --enable-tls13 --enable-harden --enable-all \ --disable-benchmark --disable-crypttests --disable-examples make install - - name: 'cache wolfssl (opensslextra-intel)' # does support `OPENSSL_COEXIST` - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-intel') }} + - name: 'cache wolfssl (opensslextra)' # does support `OPENSSL_COEXIST` + if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl-opensslextra-intel + id: cache1-wolfssl-opensslextra env: - cache-name: cache-wolfssl-opensslextra-intel + cache-name: cache1-wolfssl-opensslextra with: - path: ~/wolfssl-opensslextra - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/wolfssl-opensslextra + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }}-${{ runner.arch }} - - name: 'build wolfssl (opensslextra-intel)' - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-intel') && !steps.cache-wolfssl-opensslextra-intel.outputs.cache-hit }} + - name: 'build wolfssl (opensslextra)' + if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra') && !steps.cache1-wolfssl-opensslextra.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "wolfssl-${WOLFSSL_VERSION}-stable" ./autogen.sh - ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-opensslextra \ + ./configure --disable-dependency-tracking --prefix=/home/runner/pkg/wolfssl-opensslextra \ --enable-tls13 --enable-harden --enable-ech --enable-ed25519 --enable-opensslextra \ --disable-benchmark --disable-crypttests --disable-examples make install - - name: 'cache wolfssl (opensslextra-arm)' # does support `OPENSSL_COEXIST` - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-arm') }} + - name: 'cache mbedtls (latest)' + if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-wolfssl-opensslextra-arm + id: cache1-mbedtls-latest env: - cache-name: cache-wolfssl-opensslextra-arm + cache-name: cache1-mbedtls-latest with: - path: ~/wolfssl-opensslextra - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.WOLFSSL_VERSION }} + path: ~/pkg/mbedtls + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_VERSION }}-${{ runner.arch }} - - name: 'build wolfssl (opensslextra-arm)' - if: ${{ contains(matrix.build.install_steps, 'wolfssl-opensslextra-arm') && !steps.cache-wolfssl-opensslextra-arm.outputs.cache-hit }} - run: | - curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}-stable.tar.gz" --output pkg.bin - sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin - cd "wolfssl-${WOLFSSL_VERSION}-stable" - ./autogen.sh - ./configure --disable-dependency-tracking --prefix=/home/runner/wolfssl-opensslextra \ - --enable-tls13 --enable-harden --enable-ech --enable-ed25519 --enable-opensslextra \ - --disable-benchmark --disable-crypttests --disable-examples - make install - - - name: 'cache mbedtls (latest-intel)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-intel') }} - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-mbedtls-latest-intel - env: - cache-name: cache-mbedtls-latest-intel - with: - path: ~/mbedtls - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_VERSION }} - - - name: 'build mbedtls (latest-intel)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-intel') && !steps.cache-mbedtls-latest-intel.outputs.cache-hit }} + - name: 'build mbedtls (latest)' + if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest') && !steps.cache1-mbedtls-latest.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_VERSION}/mbedtls-${MBEDTLS_VERSION}.tar.bz2" --output pkg.bin @@ -673,31 +654,7 @@ jobs: cd "mbedtls-${MBEDTLS_VERSION}" ./scripts/config.py set MBEDTLS_THREADING_C ./scripts/config.py set MBEDTLS_THREADING_PTHREAD - cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/mbedtls \ - -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF - cmake --build . - cmake --install . - - - name: 'cache mbedtls (latest-arm)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-arm') }} - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-mbedtls-latest-arm - env: - cache-name: cache-mbedtls-latest-arm - with: - path: ~/mbedtls - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_VERSION }} - - - name: 'build mbedtls (latest-arm)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-latest-arm') && !steps.cache-mbedtls-latest-arm.outputs.cache-hit }} - run: | - curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_VERSION}/mbedtls-${MBEDTLS_VERSION}.tar.bz2" --output pkg.bin - sha256sum pkg.bin && tar -xjf pkg.bin && rm -f pkg.bin - cd "mbedtls-${MBEDTLS_VERSION}" - ./scripts/config.py set MBEDTLS_THREADING_C - ./scripts/config.py set MBEDTLS_THREADING_PTHREAD - cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/mbedtls \ + cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/mbedtls \ -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF cmake --build . cmake --install . @@ -705,23 +662,23 @@ jobs: - name: 'cache mbedtls (prev)' if: ${{ contains(matrix.build.install_steps, 'mbedtls-prev') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-mbedtls-prev + id: cache1-mbedtls-prev env: - cache-name: cache-mbedtls-prev + cache-name: cache1-mbedtls-prev with: - path: ~/mbedtls-prev - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_PREV_VERSION }} + path: ~/pkg/mbedtls-prev + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.MBEDTLS_PREV_VERSION }}-${{ runner.arch }} - name: 'build mbedtls (prev)' - if: ${{ contains(matrix.build.install_steps, 'mbedtls-prev') && !steps.cache-mbedtls-prev.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'mbedtls-prev') && !steps.cache1-mbedtls-prev.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MBEDTLS_PREV_VERSION}/mbedtls-${MBEDTLS_PREV_VERSION}.tar.bz2" --output pkg.bin - sha256sum pkg.bin | tee /dev/stderr | grep -qwF -- "${MBEDTLS_PREV_SHA256}" && tar -xjf pkg.bin && rm -f pkg.bin + sha256sum pkg.bin && tar -xjf pkg.bin && rm -f pkg.bin cd "mbedtls-${MBEDTLS_PREV_VERSION}" ./scripts/config.py set MBEDTLS_THREADING_C ./scripts/config.py set MBEDTLS_THREADING_PTHREAD - cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/mbedtls-prev \ + cmake -B . -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/mbedtls-prev \ -DENABLE_PROGRAMS=OFF -DENABLE_TESTING=OFF cmake --build . cmake --install . @@ -729,108 +686,108 @@ jobs: - name: 'cache openldap (static)' if: ${{ contains(matrix.build.install_steps, 'openldap-static') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openldap-static + id: cache1-openldap-static env: - cache-name: cache-openldap-static + cache-name: cache1-openldap-static with: - path: ~/openldap-static - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENLDAP_VERSION }} + path: ~/pkg/openldap-static + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENLDAP_VERSION }}-${{ runner.arch }} - name: 'build openldap (static)' - if: ${{ contains(matrix.build.install_steps, 'openldap-static') && !steps.cache-openldap-static.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'openldap-static') && !steps.cache1-openldap-static.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - "https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${OPENLDAP_VERSION}.tgz" --output pkg.bin + "https://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-${OPENLDAP_VERSION//_/.}.tgz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin - cd "openldap-${OPENLDAP_VERSION}" + cd "openldap-${OPENLDAP_VERSION//_/.}" autoreconf -fi - ./configure --prefix=/home/runner/openldap-static --enable-static --disable-shared --disable-slapd + ./configure --prefix=/home/runner/pkg/openldap-static --enable-static --disable-shared --disable-slapd make install - name: 'cache openssl (thread sanitizer)' if: ${{ contains(matrix.build.install_steps, 'openssl-tsan') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-openssl-tsan + id: cache1-openssl-tsan env: - cache-name: cache-openssl-tsan + cache-name: cache1-openssl-tsan with: - path: ~/openssl - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }} + path: ~/pkg/openssl + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.OPENSSL_VERSION }}-${{ runner.arch }} - name: 'build openssl (thread sanitizer)' - if: ${{ contains(matrix.build.install_steps, 'openssl-tsan') && !steps.cache-openssl-tsan.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'openssl-tsan') && !steps.cache1-openssl-tsan.outputs.cache-hit }} run: | git clone --quiet --depth 1 --branch "openssl-${OPENSSL_VERSION}" https://github.com/openssl/openssl cd openssl - CC=clang CFLAGS='-fsanitize=thread' LDFLAGS='-fsanitize=thread' ./config --prefix=/home/runner/openssl --libdir=lib no-makedepend no-apps no-docs no-tests + CC=clang CFLAGS='-fsanitize=thread' LDFLAGS='-fsanitize=thread' ./config --prefix=/home/runner/pkg/openssl --libdir=lib no-makedepend no-apps no-docs no-tests make make -j1 install_sw - name: 'cache awslc' if: ${{ contains(matrix.build.install_steps, 'awslc') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-awslc + id: cache1-awslc env: - cache-name: cache-awslc + cache-name: cache1-awslc with: - path: ~/awslc - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }} + path: ~/pkg/awslc + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.AWSLC_VERSION }}-${{ runner.arch }} - name: 'build awslc' - if: ${{ contains(matrix.build.install_steps, 'awslc') && !steps.cache-awslc.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'awslc') && !steps.cache1-awslc.outputs.cache-hit }} run: | curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --location --proto-redir =https "https://github.com/awslabs/aws-lc/archive/refs/tags/v${AWSLC_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "aws-lc-${AWSLC_VERSION}" - cmake -B . -G Ninja -DCMAKE_INSTALL_PREFIX=/home/runner/awslc -DBUILD_TOOL=OFF -DBUILD_TESTING=OFF + cmake -B . -G Ninja -DBUILD_TESTING=OFF -DBUILD_TOOL=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/awslc cmake --build . cmake --install . - name: 'cache boringssl' if: ${{ contains(matrix.build.install_steps, 'boringssl') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-boringssl + id: cache1-boringssl env: - cache-name: cache-boringssl + cache-name: cache1-boringssl with: - path: ~/boringssl - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }} + path: ~/pkg/boringssl + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.BORINGSSL_VERSION }}-${{ runner.arch }} - name: 'build boringssl' - if: ${{ contains(matrix.build.install_steps, 'boringssl') && !steps.cache-boringssl.outputs.cache-hit }} + if: ${{ contains(matrix.build.install_steps, 'boringssl') && !steps.cache1-boringssl.outputs.cache-hit }} run: | - mkdir boringssl-src - cd boringssl-src curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - "https://boringssl.googlesource.com/boringssl/+archive/${BORINGSSL_VERSION}.tar.gz" --output pkg.bin + --location --proto-redir =https "https://github.com/google/boringssl/releases/download/${BORINGSSL_VERSION}/boringssl-${BORINGSSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin - cmake -B . -G Ninja -DCMAKE_INSTALL_PREFIX=/home/runner/boringssl -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=ON + cd "boringssl-${BORINGSSL_VERSION}" + cmake -B . -G Ninja -DBUILD_TESTING=OFF -DCMAKE_INSTALL_PREFIX=/home/runner/pkg/boringssl cmake --build . cmake --install . - name: 'cache rustls' if: ${{ contains(matrix.build.install_steps, 'rustls') }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - id: cache-rustls + id: cache1-rustls env: - cache-name: cache-rustls + cache-name: cache1-rustls with: path: ~/rustls - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.RUSTLS_VERSION }} + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ env.RUSTLS_VERSION }}-${{ runner.arch }} - - name: 'fetch rustls deb' - if: ${{ contains(matrix.build.install_steps, 'rustls') && !steps.cache-rustls.outputs.cache-hit }} + - name: 'install rustls' + if: ${{ contains(matrix.build.install_steps, 'rustls') && !steps.cache1-rustls.outputs.cache-hit }} run: | cd ~ + if [ "$(uname -m)" = 'aarch64' ]; then + arch='arm64' + else + arch='x86_64' + fi curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ - --location --proto-redir =https "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/librustls_${RUSTLS_VERSION}_amd64.deb.zip" --output pkg.bin + --location --proto-redir =https "https://github.com/rustls/rustls-ffi/releases/download/v${RUSTLS_VERSION}/rustls-ffi-${arch}-linux-gnu.zip" --output pkg.bin sha256sum pkg.bin && unzip pkg.bin -d rustls && rm -f pkg.bin - - - name: 'build rustls' - # Note: we do not check cache-hit here. If the cache is hit, we need to dpkg install the deb. - if: ${{ contains(matrix.build.install_steps, 'rustls') }} - run: sudo dpkg -i ~/rustls/"librustls_${RUSTLS_VERSION}_amd64.deb" + sed -i.bak 's|prefix=dist|prefix=/home/runner/rustls|g' rustls/lib/pkgconfig/rustls.pc; cat rustls/lib/pkgconfig/rustls.pc - name: 'install Intel compilers' if: ${{ contains(matrix.build.install_steps, 'intelc') }} @@ -839,8 +796,6 @@ jobs: curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ --compressed https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | \ sudo tee /etc/apt/trusted.gpg.d/intel-sw.asc >/dev/null - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt sudo add-apt-repository 'deb https://apt.repos.intel.com/oneapi all main' sudo apt-get -o Dpkg::Use-Pty=0 install intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic source /opt/intel/oneapi/setvars.sh @@ -907,6 +862,7 @@ jobs: - name: 'single-use function check' if: ${{ (contains(matrix.build.configure, '--disable-unity') || contains(matrix.build.generate, '-DCMAKE_UNITY_BUILD=OFF')) && !contains(matrix.build.install_steps, 'filc') }} + background: true run: | git config --global --add safe.directory "*" if [ "${MATRIX_BUILD}" = 'cmake' ]; then @@ -918,8 +874,8 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- bld/src/curl --disable -V - name: 'curl install' @@ -933,6 +889,14 @@ jobs: make -C bld V=1 install fi + - name: 'randcurl' + if: ${{ contains(matrix.build.install_steps, 'randcurl') }} + background: true + run: | + mkdir run + cd run + ../.github/scripts/randcurl.pl 60 ../bld/src/curl + - name: 'build tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') }} run: | @@ -942,17 +906,14 @@ jobs: make -C bld V=1 -C tests fi - - name: 'install test prereqs' - if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && matrix.build.container == null }} - run: | - python3 -m venv ~/venv - if bld/src/curl --disable -V 2>/dev/null | grep smb; then - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi + - name: 'test-linter' + if: ${{ contains(matrix.build.install_steps, 'test-linter') }} + background: true + run: cd tests && ./test-linter.pl ../bld - name: 'run tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} - timeout-minutes: ${{ contains(matrix.build.install_packages, 'valgrind') && 20 || 10 }} + timeout-minutes: ${{ contains(matrix.build.install_packages, 'valgrind') && 15 || 10 }} env: TEST_TARGET: ${{ matrix.build.torture && 'test-torture' || 'test-ci' }} TFLAGS: '${{ matrix.build.tflags }}' @@ -960,6 +921,7 @@ jobs: if [ "${TEST_TARGET}" = 'test-ci' ] && [[ "${MATRIX_INSTALL_PACKAGES}" = *'valgrind'* ]]; then TFLAGS+=' -j6' TFLAGS+=' !776' # skip long-running flaky test + TFLAGS+=' !1674' # skip long-running test if [[ "${MATRIX_INSTALL_PACKAGES}" = *'libgss-dev'* ]]; then TFLAGS+=' ~2077 ~2078' # memory leaks from Curl_auth_decode_spnego_message() -> gss_init_sec_context() fi @@ -984,9 +946,9 @@ jobs: - name: 'install pytest prereqs' if: ${{ contains(matrix.build.install_steps, 'pytest') }} - run: | - [ -d ~/venv ] || python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/http/requirements.txt + uses: $/.github/actions/pkg-install + with: + pip: -r tests/http/requirements.txt - name: 'run pytest' if: ${{ contains(matrix.build.install_steps, 'pytest') }} @@ -1001,13 +963,6 @@ jobs: make -C bld V=1 pytest-ci fi - - name: 'randcurl' - if: ${{ contains(matrix.build.install_steps, 'randcurl') }} - run: | - mkdir run - cd run - ../.github/scripts/randcurl.pl 60 ../bld/src/curl - - name: 'build examples' if: ${{ !contains(matrix.build.install_packages, 'valgrind') && !contains(matrix.build.name, '!examples') }} run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index e44b276a2a98..2f6079364cdd 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -32,6 +32,7 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none # Apple APIs and the macos-version-min value required to avoid deprecation # warnings with llvm/clang, and/or the feature getting enabled at build-time @@ -43,12 +44,12 @@ permissions: {} # - 10.11 El Capitan (2015) - connectx() (runtime) # - 10.12 Sierra (2016) - clock_gettime() (build-time, runtime) # - 10.14 Mojave (2018) - SecTrustEvaluateWithError() (runtime), GSS Framework +# - 27.0 Golden Gate (2026) - pipe2() (build-time, runtime) env: CURL_CI: github - CURL_TEST_MIN: 1750 + CURL_TEST_MIN: 1850 DO_NOT_TRACK: '1' - HOMEBREW_NO_INSTALL_CLEANUP: '1' MAKEFLAGS: -j 4 LDFLAGS: -w # suppress 'object file was built for newer macOS version than being linked' warnings @@ -57,14 +58,15 @@ jobs: name: "iOS, ${{ (matrix.build.generator && format('CM-{0}', matrix.build.generator)) || (matrix.build.generate && 'CM' || 'AM' )}} ${{ matrix.build.name }} arm64" runs-on: macos-latest timeout-minutes: 10 + cache-mode: write env: DEVELOPER_DIR: "/Applications/Xcode${{ matrix.build.xcode && format('_{0}', matrix.build.xcode) || '' }}.app/Contents/Developer" CC: 'clang' LDFLAGS: '' MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }} MATRIX_OPTIONS: ${{ matrix.build.options }} - # renovate: datasource=github-tags depName=libressl/portable versioning=semver registryUrl=https://github.com - LIBRESSL_VERSION: 4.3.1 + # renovate: datasource=github-tags depName=libressl/portable + LIBRESSL_VERSION: 4.3.3 strategy: fail-fast: false matrix: @@ -77,7 +79,6 @@ jobs: install_steps: libressl # FIXME: Could not make OPENSSL_ROOT_DIR work. CMake seems to prepend sysroot to it. generator: Xcode - xcode: '' # default Xcode. Set it once to silence actionlint. options: --config Debug generate: >- -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=OFF @@ -88,22 +89,12 @@ jobs: -DCURL_USE_LIBPSL=OFF -DCURL_ENABLE_NTLM=ON steps: - - name: 'brew install' + - name: 'install prereqs' if: ${{ matrix.build.configure }} timeout-minutes: 5 - run: | - # shellcheck disable=SC2181 - while [[ $? == 0 ]]; do - for i in 1 2 3; do - if brew install automake libtool; then - break 2 - else - echo "Error: wait to try again: $i" - sleep 10 - fi - done - false Too many retries - done + uses: $/.github/actions/pkg-install + with: + brew: automake libtool - name: 'toolchain versions' run: | @@ -129,17 +120,14 @@ jobs: - name: 'build libressl' if: ${{ contains(matrix.build.install_steps, 'libressl') && !steps.cache-libressl.outputs.cache-hit }} run: | - curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 60 --retry 3 --retry-connrefused \ - --location --proto-redir =https "https://github.com/libressl/portable/releases/download/v${LIBRESSL_VERSION}/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin + curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 120 --retry 6 --retry-connrefused \ + "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-${LIBRESSL_VERSION}.tar.gz" --output pkg.bin sha256sum pkg.bin && tar -xzf pkg.bin && rm -f pkg.bin cd "libressl-${LIBRESSL_VERSION}" - cmake -B . -G Ninja \ - -DCMAKE_INSTALL_PREFIX=/Users/runner/libressl \ + cmake -B . -G Ninja -DLIBRESSL_APPS=OFF -DLIBRESSL_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/Users/runner/libressl \ -DCMAKE_SYSTEM_NAME=iOS \ -DCMAKE_SYSTEM_PROCESSOR=aarch64 \ - -DBUILD_SHARED_LIBS=OFF \ - -DLIBRESSL_APPS=OFF \ - -DLIBRESSL_TESTS=OFF + -DBUILD_SHARED_LIBS=OFF cmake --build . cmake --install . --verbose @@ -203,8 +191,8 @@ jobs: - name: 'curl info' run: | - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -f '%10z bytes: %N' -- - name: 'build tests' if: ${{ matrix.build.generate }} # skip for autotools to save time @@ -233,7 +221,6 @@ jobs: CC: '${{ matrix.build.compiler }}' MATRIX_BUILD: ${{ matrix.build.generate && 'cmake' || 'autotools' }} MATRIX_COMPILER: '${{ matrix.build.compiler }}' - MATRIX_INSTALL: '${{ matrix.build.install }}' MATRIX_INSTALL_STEPS: '${{ matrix.build.install_steps }}' MATRIX_MACOS_VERSION_MIN: '${{ matrix.build.macos-version-min }}' strategy: @@ -243,13 +230,12 @@ jobs: - name: '!ssl !debug brotli zstd' compiler: gcc-13 configure: --without-ssl --with-brotli --with-zstd --with-apple-idn - tflags: '--min=1520' - xcode: '' # default Xcode. Set it once to silence actionlint. + tflags: '--min=1620' - name: '!ssl libssh2 AppleIDN' compiler: clang generate: -DENABLE_DEBUG=ON -DCURL_USE_LIBSSH2=ON -DUSE_APPLE_IDN=ON -DCURL_ENABLE_SSL=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_DISABLE_HTTPSIG=OFF - tflags: '--min=1630' + tflags: '--min=1750' - name: 'OpenSSL libssh c-ares' compiler: clang @@ -264,15 +250,17 @@ jobs: - name: '!ssl HTTP-only c-ares' macos-version-min: '10.15' # Catalina (2019) compiler: clang - tflags: '--min=960' + tflags: '--min=1040' generate: >- -DENABLE_DEBUG=ON -DENABLE_ARES=ON - -DCURL_ENABLE_SSL=OFF -DHTTP_ONLY=ON - -DCURL_DISABLE_ALTSVC=ON -DENABLE_UNIX_SOCKETS=OFF + -DCURL_ENABLE_SSL=OFF -DCURL_DISABLE_ALTSVC=ON -DENABLE_UNIX_SOCKETS=OFF -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=OFF -DUSE_NGHTTP2=OFF -DCURL_USE_GSSAPI=OFF -DUSE_LIBIDN2=OFF -DCURL_USE_LIBPSL=OFF -DCURL_BROTLI=OFF -DCURL_ZLIB=OFF -DCURL_ZSTD=OFF -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=OFF + -DCURL_DISABLE_DICT=ON -DCURL_DISABLE_FILE=ON -DCURL_DISABLE_FTP=ON -DCURL_DISABLE_GOPHER=ON -DCURL_DISABLE_IMAP=ON + -DCURL_DISABLE_IPFS=ON -DCURL_DISABLE_LDAP=ON -DCURL_DISABLE_LDAPS=ON -DCURL_DISABLE_MQTT=ON -DCURL_DISABLE_POP3=ON + -DCURL_DISABLE_RTSP=ON -DCURL_DISABLE_SMTP=ON -DCURL_DISABLE_TELNET=ON -DCURL_DISABLE_TFTP=ON -DCURL_DISABLE_WEBSOCKETS=ON - name: 'LibreSSL !ldap +examples' compiler: clang @@ -292,6 +280,8 @@ jobs: -DCMAKE_C_STANDARD=90 -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON - name: 'OpenSSL SecTrust krb5' + image: xcode-27 # for pipe2() + macos-version-min: '10.15' compiler: clang install: libnghttp3 libngtcp2 install_steps: pytest @@ -302,7 +292,7 @@ jobs: - name: 'OpenSSL event-based' compiler: clang generate: -DENABLE_DEBUG=ON -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF -DCURL_USE_LIBSSH2=OFF -DCURL_ENABLE_NTLM=ON - tflags: '--test-event --min=1400' + tflags: '--test-event --min=1450' - name: 'OpenSSL gsasl AppleIDN SecTrust +examples' compiler: clang @@ -386,7 +376,7 @@ jobs: compiler: clang install: rustls-ffi generate: -DENABLE_DEBUG=ON -DCURL_USE_RUSTLS=ON -DUSE_ECH=ON -DCURL_DISABLE_LDAP=ON -DCURL_ENABLE_NTLM=ON - tflags: '--min=1730' + tflags: '--min=1850' - name: 'OpenSSL torture 1' compiler: clang @@ -417,31 +407,16 @@ jobs: brew unlink openssl fi - - name: 'brew install' + - name: 'install prereqs' timeout-minutes: 5 - # Run this command with retries because of spurious failures seen - # while running the tests, for example - # https://github.com/curl/curl/runs/4095721123?check_suite_focus=true - env: - INSTALL_PACKAGES: >- + uses: $/.github/actions/pkg-install + with: + brew: >- + pkgconf libpsl libssh2 ${{ matrix.build.install }} ${{ matrix.build.generate && 'ninja' || 'automake libtool' }} ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') && 'nghttp2 stunnel' || '' }} ${{ contains(matrix.build.install_steps, 'pytest') && 'caddy httpd vsftpd' || '' }} - run: | - # shellcheck disable=SC2181 - while [[ $? == 0 ]]; do - for i in 1 2 3; do - if brew install pkgconf libpsl libssh2 ${INSTALL_PACKAGES} ${MATRIX_INSTALL}; then - break 2 - else - echo "Error: wait to try again: $i" - sleep 10 - fi - done - false Too many retries - done - - name: 'toolchain versions' run: | [[ "${MATRIX_COMPILER}" = 'llvm'* ]] && CC="$(brew --prefix "${MATRIX_COMPILER}")/bin/clang" @@ -481,6 +456,8 @@ jobs: CC+=" --target=$(uname -m)-apple-darwin" fi + [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}" # for autotools, and cmake with gcc + if [ "${MATRIX_BUILD}" = 'cmake' ]; then for _chkprefill in '' ${MATRIX_CHKPREFILL}; do options='' @@ -506,7 +483,6 @@ jobs: options+=" --with-sysroot=${sysroot}" CFLAGS+=" --sysroot=${sysroot}" fi - [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}" [[ "${MATRIX_INSTALL_STEPS}" = *'pytest'* ]] && options+=' --with-test-vsftpd=no' # Skip ~20 tests that stretch run time by 7x on macOS mkdir bld && cd bld && ../configure --prefix="$PWD"/curl-install --enable-unity --enable-warnings --enable-werror --disable-static \ --disable-dependency-tracking --enable-option-checking=fatal \ @@ -546,8 +522,8 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -f '%10z bytes: %N' -- bld/src/curl --disable --version - name: 'curl install' @@ -567,14 +543,6 @@ jobs: make -C bld V=1 -C tests fi - - name: 'install test prereqs' - if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} - run: | - python3 -m venv ~/venv - if bld/src/curl --disable -V 2>/dev/null | grep smb; then - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi - - name: 'run tests' if: ${{ !contains(matrix.build.install_steps, 'skipall') && !contains(matrix.build.install_steps, 'skiprun') }} timeout-minutes: ${{ contains(matrix.build.install_steps, 'torture') && 20 || 10 }} @@ -586,7 +554,6 @@ jobs: if [ "${TEST_TARGET}" != 'test-ci' ]; then TFLAGS+=' --buildinfo' # only test-ci sets this by default, set it manually for test-torture fi - source ~/venv/bin/activate if [[ "${MATRIX_INSTALL_STEPS}" = *'codeset-test1'* ]]; then locale || true unset LANG @@ -611,9 +578,9 @@ jobs: - name: 'install pytest prereqs' if: ${{ contains(matrix.build.install_steps, 'pytest') }} - run: | - [ -d ~/venv ] || python3 -m venv ~/venv - ~/venv/bin/pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/http/requirements.txt + uses: $/.github/actions/pkg-install + with: + pip: -r tests/http/requirements.txt - name: 'run pytest' if: ${{ contains(matrix.build.install_steps, 'pytest') }} @@ -652,63 +619,52 @@ jobs: fail-fast: false matrix: # Sources: - # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md - compiler: [gcc-13, gcc-14, gcc-15, llvm@15, llvm@18, llvm@20, clang] - # Xcode support matrix as of 2025-10, with default macOS SDK versions and OS names, years: + # https://github.com/actions/runner-images/blob/main/images/macos/xcode-27-arm64-Readme.md + compiler: [gcc-13, gcc-14, gcc-15, llvm@18, llvm@20, clang] + # Xcode support matrix as of 2026-09, with default macOS SDK versions and OS names, years: # * = default Xcode on the runner. - # macos-14: 15.0.1, 15.1, 15.2, 15.3,*15.4 - # macos-15: 16.0, 16.1, 16.2, 16.3,*16.4, 26.0 - # macos-26: 16.4 *26.0 - # macOSSDK: 14.0, 14.2, 14.2, 14.4, 14.5, 15.0, 15.1, 15.2, 15.4, 15.5, 26.0 - # Sonoma (2023) Sequoia (2024) Tahoe (2025) + # macos-15: 16.0, 16.1, 16.2, 16.3,*16.4, 26.0 + # macos-26: 16.4,*26.6 + # xcode-27: *27.0 + # macOSSDK: 15.0, 15.1, 15.2, 15.4, 15.5, 26.x 27.x + # Sequoia (2024) Tahoe (2025) Golden Gate (2026) # https://github.com/actions/runner-images/tree/main/images/macos # https://en.wikipedia.org/wiki/MacOS_version_history - image: [macos-14, macos-15, macos-26] + image: [macos-15, macos-26, xcode-27] xcode: [''] # default Xcodes macos-version-min: [''] build: [autotools, cmake] exclude: # Combinations not covered by runner images: - - { image: macos-14, compiler: 'llvm@18' } - - { image: macos-14, compiler: 'llvm@20' } - - { image: macos-15, compiler: 'llvm@15' } - { image: macos-15, compiler: 'llvm@20' } - - { image: macos-26, compiler: 'llvm@15' } - { image: macos-26, compiler: 'llvm@18' } + - { image: xcode-27, compiler: 'llvm@18' } # Covered by the main workflow - { image: macos-15, compiler: 'gcc-13' } - { image: macos-15, compiler: 'llvm@18' } - { image: macos-15, compiler: 'clang' } # Reduce build combinations, by dropping less interesting ones - - { image: macos-26, compiler: 'gcc-13' } - - { compiler: 'gcc-14' , build: cmake } + - { image: macos-15, compiler: 'gcc-14' } + - { image: xcode-27, compiler: 'gcc-13' } + - { image: xcode-27, compiler: 'gcc-14' } + # incompatible combination + - { image: xcode-27, compiler: 'gcc-15' } # Reduce autotools to only one job that is also build with cmake - { compiler: 'gcc-13' , build: autotools } - { compiler: 'gcc-14' , build: autotools } - { compiler: 'gcc-15' , build: autotools } - - { compiler: 'llvm@15', build: autotools } - { compiler: 'llvm@18', build: autotools } - { compiler: 'llvm@20', build: autotools } - - { image: macos-14, build: autotools } - { image: macos-15, build: autotools } + - { image: macos-26, build: autotools } steps: - name: 'install autotools' if: ${{ matrix.build == 'autotools' }} - run: | - # shellcheck disable=SC2181 - while [[ $? == 0 ]]; do - for i in 1 2 3; do - if brew install automake libtool; then - break 2 - else - echo "Error: wait to try again: $i" - sleep 10 - fi - done - false Too many retries - done + uses: $/.github/actions/pkg-install + with: + brew: automake libtool - name: 'toolchain versions' run: | @@ -750,6 +706,9 @@ jobs: CC+=" --target=$(uname -m)-apple-darwin" fi + export CFLAGS + [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}" # for autotools, and cmake with gcc + if [ "${MATRIX_BUILD}" = 'cmake' ]; then [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && options+=" -DCMAKE_OSX_DEPLOYMENT_TARGET=${MATRIX_MACOS_VERSION_MIN}" # would pick up nghttp2, libidn2, and libssh2 @@ -766,7 +725,6 @@ jobs: -DCURL_USE_GSSAPI=ON -DCURL_GSS_FLAVOR=Apple \ ${options} else - export CFLAGS if [[ "${MATRIX_COMPILER}" = 'llvm'* ]]; then options+=" --target=$(uname -m)-apple-darwin" fi @@ -774,7 +732,6 @@ jobs: options+=" --with-sysroot=${sysroot}" CFLAGS+=" --sysroot=${sysroot}" fi - [ -n "${MATRIX_MACOS_VERSION_MIN}" ] && CFLAGS+=" -mmacosx-version-min=${MATRIX_MACOS_VERSION_MIN}" # would pick up nghttp2, libidn2, but libssh2 is disabled by default mkdir bld && cd bld && ../configure --enable-unity --enable-warnings --enable-werror --disable-static \ --disable-dependency-tracking --enable-option-checking=fatal \ @@ -816,6 +773,6 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.dylib' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -f '%10z bytes: %N' -- bld/src/curl --disable --version diff --git a/.github/workflows/non-native.yml b/.github/workflows/non-native.yml index dfe0f16acbb0..b233955944a7 100644 --- a/.github/workflows/non-native.yml +++ b/.github/workflows/non-native.yml @@ -32,10 +32,11 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: none env: CURL_CI: github - CURL_TEST_MIN: 1820 + CURL_TEST_MIN: 1940 DO_NOT_TRACK: '1' jobs: @@ -59,12 +60,12 @@ jobs: matrix: include: # https://github.com/DragonFlyBSD/DPorts - # { os: 'dragonflybsd', version: '6.4.2', build: 'autotools', arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skiprun', - # install: 'autoconf automake libtool openldap26-client libidn2', - # options: '--with-openssl --enable-ldap --enable-ldaps --with-libidn2' } + - { os: 'dragonflybsd', version: '6.4.2', build: 'autotools', arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skiprun', + install: 'autoconf automake libtool openldap26-client libidn2', + options: '--with-openssl --enable-ldap --enable-ldaps --with-libidn2' } - # { os: 'dragonflybsd', version: '6.4.2', build: 'cmake' , arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skipall', - # install: 'cmake ninja' } + - { os: 'dragonflybsd', version: '6.4.2', build: 'cmake' , arch: 'x86_64' , cc: 'gcc' , desc: 'openssl', tflags: 'skipall', + install: 'cmake ninja' } # https://ports.freebsd.org/ - { os: 'freebsd' , version: '15.1', build: 'autotools', arch: 'x86_64' , cc: 'clang', desc: 'openssl', @@ -75,15 +76,15 @@ jobs: install: 'cmake-core ninja perl5 krb5-devel openldap26-client libidn2', options: '-DCURL_USE_GSSAPI=ON -DCMAKE_UNITY_BUILD=OFF' } - # { os: 'freebsd' , version: '15.1', build: 'cmake' , arch: 'riscv64', cc: 'clang', desc: 'openssl !examples', tflags: 'HTTP --min=0 --subset=0/10 -R --seed', - # install: 'cmake-core ninja perl5', - # options: '-D_CURL_PREFILL=ON -DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF -DCURL_DISABLE_HTTPSIG=OFF -DCURL_DISABLE_TYPECHECK=ON' } + - { os: 'freebsd' , version: '15.1', build: 'cmake' , arch: 'riscv64', cc: 'clang', desc: 'openssl !examples', tflags: 'HTTP --min=0 --subset=0/10 -R --seed', + install: 'cmake-core ninja perl5', + options: '-D_CURL_PREFILL=ON -DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF -DCURL_DISABLE_HTTPSIG=OFF -DCURL_DISABLE_TYPECHECK=ON' } - { os: 'freebsd' , version: '14.3', build: 'autotools', arch: 'arm64' , cc: 'clang', desc: 'openssl !examples', tflags: 'skipall', install: 'autoconf automake libtool krb5-devel openldap26-client libidn2 stunnel', options: '--with-openssl --with-gssapi --enable-ldap --enable-ldaps --with-libidn2 --disable-typecheck' } - - { os: 'freebsd' , version: '14.3', build: 'cmake' , arch: 'arm64' , cc: 'clang', desc: 'openssl', tflags: 'skiprun', + - { os: 'freebsd' , version: '14.5', build: 'cmake' , arch: 'arm64' , cc: 'clang', desc: 'openssl', tflags: 'skiprun', install: 'cmake-core ninja perl5 krb5-devel openldap26-client libidn2 stunnel', options: '-DCURL_USE_GSSAPI=ON -DCURL_DISABLE_TYPECHECK=ON' } @@ -124,7 +125,7 @@ jobs: persist-credentials: false - name: 'setup VM' - uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0 + uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0 with: environment_variables: 'CC CURL_CI CURL_TEST_MIN DO_NOT_TRACK MAKEFLAGS MATRIX_ARCH MATRIX_BUILD MATRIX_INSTALL MATRIX_OPTIONS MATRIX_OS MATRIX_VERSION TFLAGS' operating_system: '${{ matrix.os }}' @@ -136,6 +137,7 @@ jobs: if [ "${MATRIX_OS}" = 'dragonflybsd' ]; then sudo pkg install -y pkgconf brotli libnghttp2 ${MATRIX_INSTALL} elif [ "${MATRIX_OS}" = 'freebsd' ]; then + find /usr/local/etc/pkg/repos || true sudo pkg install -y pkgconf brotli libnghttp2 ${MATRIX_INSTALL} elif [ "${MATRIX_OS}" = 'midnightbsd' ]; then if [ "${MATRIX_BUILD}" = 'autotools' ]; then @@ -143,6 +145,7 @@ jobs: sudo mport upgrade | grep -v -E '(Downloading.+%|^/usr/local)' fi sudo mport install pkgconf brotli libnghttp2 ${MATRIX_INSTALL} | grep -v -E '(Downloading.+%|^/usr/local)' || true + command -v pkg-config >/dev/null 2>&1 elif [ "${MATRIX_OS}" = 'netbsd' ]; then sudo pkgin -y install pkg-config perl brotli libssh2 libpsl nghttp2 ${MATRIX_INSTALL} elif [ "${MATRIX_OS}" = 'openbsd' ]; then @@ -197,7 +200,11 @@ jobs: done if command -v pccritic >/dev/null 2>&1; then pccritic --version - pccritic --min-score 98 bld/libcurl.pc + if [ "${MATRIX_OS}" = 'freebsd' ]; then + pccritic --min-score 93 bld/libcurl.pc + else + pccritic --min-score 98 bld/libcurl.pc + fi fi - name: 'build' @@ -210,8 +217,8 @@ jobs: - name: 'curl -V' run: | - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | xargs -0 stat -f '%10z bytes: %N' -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.so.*' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -f '%10z bytes: %N' -- bld/src/curl --disable --version - name: 'curl install' @@ -256,12 +263,14 @@ jobs: name: "AmigaOS, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} gcc AmiSSL m68k" runs-on: ubuntu-latest timeout-minutes: 5 + cache-mode: write env: MAKEFLAGS: -j 5 MATRIX_BUILD: '${{ matrix.build }}' - AMISSL_VERSION: '5.27' + # renovate: datasource=github-releases depName=jens-maus/amissl versioning=semver-coerced + AMISSL_VERSION: 5.27 AMISSL_SHA256: 5003bef8c5930354d16b0ce7196d71b2811891c42fad38a9238c5ce4098ad42a - TOOLCHAIN_VERSION: 6.5.0 + TOOLCHAIN_VERSION: 6.5.0 # manually bumped TOOLCHAIN_SHA256: 381e227c9ef552f073771d6f851cfdf873b574f3cf5db7c1c0107ea5d7146edc strategy: matrix: @@ -350,8 +359,8 @@ jobs: - name: 'curl info' run: | - find . -type f \( -name curl -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . -type f \( -name curl -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- - name: 'build tests' if: ${{ matrix.build == 'cmake' }} # skip for autotools to save time @@ -372,7 +381,7 @@ jobs: fi android: - name: "Android ${{ matrix.platform }}, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.name }} arm64" + name: "Android ${{ matrix.platform }}, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.name }} ${{ matrix.arch }}" runs-on: ubuntu-latest timeout-minutes: 5 env: @@ -382,16 +391,19 @@ jobs: strategy: matrix: include: - - { build: 'autotools', platform: '21', name: "!ssl !zstd", + - { build: 'autotools', platform: '21', arch: 'arm64-v8a' , name: "!ssl !zstd", options: '--without-ssl --without-libpsl --without-zstd' } - - { build: 'cmake' , platform: '21', name: "!ssl !zstd", + - { build: 'cmake' , platform: '21', arch: 'arm64-v8a' , name: "!ssl !zstd", + options: '-DCURL_ENABLE_SSL=OFF -DCURL_USE_LIBPSL=OFF -DCURL_ZSTD=OFF' } + + - { build: 'cmake' , platform: '21', arch: 'armeabi-v7a', name: "!ssl !zstd", options: '-DCURL_ENABLE_SSL=OFF -DCURL_USE_LIBPSL=OFF -DCURL_ZSTD=OFF' } - - { build: 'autotools', platform: '35', name: "!ssl !zstd", + - { build: 'autotools', platform: '35', arch: 'arm64-v8a' , name: "!ssl !zstd", options: '--without-ssl --without-libpsl --without-zstd' } - - { build: 'cmake' , platform: '35', name: "!ssl !zstd", + - { build: 'cmake' , platform: '35', arch: 'arm64-v8a' , name: "!ssl !zstd", options: '-DCURL_ENABLE_SSL=OFF -DCURL_USE_LIBPSL=OFF -DCURL_ZSTD=OFF' } fail-fast: false @@ -406,25 +418,31 @@ jobs: - name: 'configure' env: + MATRIX_ARCH: '${{ matrix.arch }}' MATRIX_OPTIONS: '${{ matrix.options }}' MATRIX_PLATFORM: '${{ matrix.platform }}' run: | if [ "${MATRIX_BUILD}" = 'cmake' ]; then # https://developer.android.com/ndk/guides/cmake cmake -B bld -G Ninja \ - -DANDROID_ABI=arm64-v8a \ + -DANDROID_ABI="${MATRIX_ARCH}" \ -DANDROID_PLATFORM="android-${MATRIX_PLATFORM}" \ -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" -DCMAKE_WARN_DEPRECATED=OFF \ -DCMAKE_UNITY_BUILD=ON \ -DCURL_WERROR=ON \ ${MATRIX_OPTIONS} else + if [ "${MATRIX_ARCH}" = 'armeabi-v7a' ]; then + triplet='armv7a-linux-androideabi' + else + triplet='aarch64-linux-android' + fi TOOLCHAIN="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64" mkdir bld && cd bld && ../configure --enable-unity --enable-warnings --enable-werror --disable-shared \ --disable-dependency-tracking --enable-option-checking=fatal \ - CC="$TOOLCHAIN/bin/aarch64-linux-android${MATRIX_PLATFORM}-clang" \ + CC="$TOOLCHAIN/bin/${triplet}${MATRIX_PLATFORM}-clang" \ AR="$TOOLCHAIN/bin/llvm-ar" \ RANLIB="$TOOLCHAIN/bin/llvm-ranlib" \ - --host="aarch64-linux-android${MATRIX_PLATFORM}" \ + --host="${triplet}${MATRIX_PLATFORM}" \ ${MATRIX_OPTIONS} fi @@ -453,8 +471,8 @@ jobs: - name: 'curl info' run: | - find . -type f \( -name curl -o -name '*.so' -o -name '*.a' \) -print0 | xargs -0 file -- - find . -type f \( -name curl -o -name '*.so' -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . -type f \( -name curl -o -name '*.so' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . -type f \( -name curl -o -name '*.so' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- - name: 'build tests' run: | @@ -476,11 +494,12 @@ jobs: name: "MS-DOS, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} djgpp !ssl i586" runs-on: ubuntu-latest timeout-minutes: 5 + cache-mode: write env: LDFLAGS: -s MAKEFLAGS: -j 5 MATRIX_BUILD: '${{ matrix.build }}' - # renovate: datasource=github-releases depName=andrewwutw/build-djgpp versioning=semver-coerced registryUrl=https://github.com + # renovate: datasource=github-releases depName=andrewwutw/build-djgpp versioning=semver-coerced TOOLCHAIN_VERSION: '3.4' TOOLCHAIN_SHA256: 8464f17017d6ab1b2bb2df4ed82357b5bf692e6e2b7fee37e315638f3d505f00 strategy: @@ -488,13 +507,11 @@ jobs: build: [autotools, cmake] fail-fast: false steps: - - name: 'install packages' + - name: 'install prereqs' timeout-minutes: 2 - run: | - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install libfl2 + uses: $/.github/actions/pkg-install + with: + apt: libfl2 - name: 'cache compiler (djgpp)' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 @@ -574,8 +591,8 @@ jobs: - name: 'curl info' run: | - find . \( -name '*.exe' -o -name '*.a' \) -print0 | xargs -0 file -- - find . \( -name '*.exe' -o -name '*.a' \) -print0 | xargs -0 stat -c '%10s bytes: %n' -- + find . \( -name '*.exe' -o -name '*.a' \) -print0 | sort -z | xargs -0 file -- + find . \( -name '*.exe' -o -name '*.a' \) -print0 | sort -z | xargs -0 stat -c '%10s bytes: %n' -- - name: 'build tests' if: ${{ matrix.build == 'cmake' }} # skip for autotools to save time diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000000..bbac296e463a --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,59 @@ +# Copyright (C) Daniel Stenberg, , et al. +# +# SPDX-License-Identifier: curl + +name: 'OpenSSF Scorecard' + +'on': + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '15 14 * * 0' + push: + branches: ['master'] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +permissions: {} +# Key not supported by ossf/scorecard-action as of v2.5.0 +# cache-mode: none + +jobs: + analysis: + name: 'Scorecard analysis' + runs-on: ubuntu-latest + # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. + if: ${{ github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' }} + permissions: + security-events: write # to upload the results to code-scanning dashboard. + id-token: write # Needed to publish results and get a badge (see publish_results below). + + steps: + - name: 'Checkout code' + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: 'Run analysis' + uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: 'Upload artifact' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - name: 'Upload to code-scanning' + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 + with: + sarif_file: results.sarif diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 73ec97abc1b2..eeedeeedcbf3 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -32,17 +32,18 @@ concurrency: cancel-in-progress: true permissions: {} +cache-mode: write env: CURL_CI: github - CURL_TEST_MIN: 1800 + CURL_TEST_MIN: 1900 CURL_TEST_SSH_KEYALGO: ed25519 DO_NOT_TRACK: '1' - OPENSSH_WINDOWS_VERSION: 10.0.0.0p2-Preview + OPENSSH_WINDOWS_VERSION: 10.0.0.0p2-Preview # manually bumped OPENSSH_WINDOWS_SHA256_ARM64: 698c6aec31c1dd0fb996206e8741f4531a97355686b5431ef347d531b07fcd42 OPENSSH_WINDOWS_SHA256_WIN64: 23f50f3458c4c5d0b12217c6a5ddfde0137210a30fa870e98b29827f7b43aba5 - STUNNEL_VERSION: '5.80' - STUNNEL_SHA256: 25947bd268e2e670e1c7f915cefd54585aaa440d09eb20c6109cdbeaf3140bc4 + # renovate: datasource=github-tags depName=mtrojnar/stunnel versioning=semver-coerced extractVersion=^stunnel-(?.+)$ + STUNNEL_VERSION: 5.82 jobs: build-cache: @@ -58,7 +59,7 @@ jobs: with: path: C:\my-stunnel key: ${{ runner.os }}-stunnel-${{ env.STUNNEL_VERSION }}-amd64 - lookup-only: true + lookup-only: true # requires cache read permission - name: 'install test prereqs (stunnel)' if: ${{ !steps.cache-stunnel.outputs.cache-hit }} @@ -68,18 +69,19 @@ jobs: cd /c && mkdir my-stunnel && cd my-stunnel curl --disable --fail --silent --show-error --connect-timeout 15 --max-time 240 --retry 3 --retry-connrefused \ "https://www.stunnel.org/archive/5.x/stunnel-${STUNNEL_VERSION}-win64-installer.exe" --output pkg.bin - sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${STUNNEL_SHA256}" && 7z x -y pkg.bin >/dev/null && rm -f pkg.bin && ls -l && bin/tstunnel -version + sha256sum pkg.bin && 7z x -y pkg.bin >/dev/null && rm -f pkg.bin && ls -l && bin/tstunnel -version cygwin: name: "cygwin, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.platform }} ${{ matrix.name }}" needs: build-cache runs-on: windows-2022 timeout-minutes: 10 + cache-mode: read defaults: run: shell: D:\cygwin\bin\bash.exe '{0}' # zizmor: ignore[misfeature] env: - CURL_TEST_MIN: 1850 + CURL_TEST_MIN: 2040 LDFLAGS: -s MAKEFLAGS: -j 5 SHELLOPTS: 'igncr' @@ -93,8 +95,8 @@ jobs: install: 'libssl-devel libssh2-devel' } - { name: 'openssl', build: 'cmake', platform: 'x86_64', tflags: '', - config: '-DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON', - install: 'libssl-devel libssh2-devel' } + config: '-DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_DISABLE_HTTPSIG=OFF -DCURL_ENABLE_NTLM=ON', + install: 'libssl-devel libssh2-devel python3' } fail-fast: false steps: - uses: cygwin/cygwin-install-action@3f0a3f9f988f7e96b8c18098ae05eaec175f5b52 # v6.0.2 @@ -104,7 +106,7 @@ jobs: # https://cygwin.com/mirrors.html # Main mirror status: https://archlinux.org/mirrors/kernel.org/ site: https://mirrors.kernel.org/sourceware/cygwin/ - # https://cygwin.com/cgi-bin2/package-grep.cgi + # https://cygwin.com/packages/ packages: >- ${{ matrix.build == 'autotools' && 'autoconf automake libtool make' || 'cmake ninja' }} gcc-core binutils perl pkgconf @@ -181,8 +183,8 @@ jobs: timeout-minutes: 1 run: | PATH=/usr/bin - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 file -- - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- if [ "${MATRIX_BUILD}" = 'cmake' ]; then PATH="$PWD/bld/lib:$PATH" fi @@ -216,9 +218,6 @@ jobs: run: | PATH=/usr/bin:/cygdrive/c/my-stunnel/bin TFLAGS="-j8 ${TFLAGS}" - if [ -x "$(cygpath "${SYSTEMROOT}/System32/curl.exe")" ]; then - TFLAGS+=" -ac $(cygpath "${SYSTEMROOT}/System32/curl.exe")" - fi if [ "${MATRIX_BUILD}" = 'cmake' ]; then PATH="$PWD/bld/lib:$PATH" cmake --build bld --verbose --target test-ci @@ -252,6 +251,7 @@ jobs: LDFLAGS: -s MAKEFLAGS: -j 5 MATRIX_BUILD: '${{ matrix.build }}' + MATRIX_ENV: '${{ matrix.env }}' MATRIX_OPENSSH: '${{ matrix.openssh }}' MATRIX_SYS: '${{ matrix.sys }}' MATRIX_TEST: '${{ matrix.test }}' @@ -260,42 +260,31 @@ jobs: include: # MSYS - { name: '!proxy', - build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '--min=1620', - config: '--enable-debug --with-openssl --disable-threaded-resolver --disable-proxy --enable-ntlm', - install: 'openssl-devel libssh2-devel' } - - { name: 'default', - build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: 'skiprun', - config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-ntlm', - install: 'openssl-devel libssh2-devel' } - - { name: 'default', - build: 'cmake' , sys: 'msys' , env: 'x86_64' , tflags: '', - config: '-DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_ENABLE_NTLM=ON', - install: 'openssl-devel libssh2-devel' } + build: 'cmake' , sys: 'msys' , env: 'x86_64' , tflags: 'skiprun', + config: '-DENABLE_DEBUG=ON -DENABLE_THREADED_RESOLVER=OFF -DCURL_DISABLE_PROXY=ON -DCURL_ENABLE_NTLM=ON' } - { name: 'default R', build: 'autotools', sys: 'msys' , env: 'x86_64' , tflags: '', - config: '--with-openssl --enable-ntlm', install: 'openssl-devel libssh2-devel' } + config: '--with-openssl --enable-httpsig --enable-ntlm', + install: 'diffutils' } # for tests # MinGW - { name: 'default', - build: 'autotools', sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun', - config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-static --without-zlib', - install: 'mingw-w64-x86_64-openssl mingw-w64-x86_64-libssh2' } + build: 'autotools', sys: 'clang64' , env: 'clang-x86_64' , tflags: 'skiprun', + config: '--enable-debug --with-openssl --disable-threaded-resolver --enable-static --without-zlib' } - { name: 'wolfssl c-ares U', build: 'autotools', sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '', config: '--enable-debug --with-wolfssl --enable-windows-unicode --enable-ares --enable-static --disable-shared --enable-ca-native --enable-ntlm', - install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-nghttp3 mingw-w64-ucrt-x86_64-libssh2 mingw-w64-ucrt-x86_64-wolfssl' } + install: 'mingw-w64-ucrt-x86_64-wolfssl' } - { name: 'schannel c-ares U', type: 'Debug', - build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: '--min=1720', + build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: '--min=1850', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON -DCURL_DROP_UNUSED=ON', - install: 'mingw-w64-x86_64-c-ares mingw-w64-x86_64-libssh2' } + install: 'mingw-w64-clang-x86_64-python' } # for dict and telnet tests # MinGW torture - { name: 'schannel U torture 1', type: 'Debug', build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=0/2', - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', - install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-libssh2' } + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON' } - { name: 'schannel U torture 2', type: 'Debug', - build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=1/2 --min=885', - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON', - install: 'mingw-w64-ucrt-x86_64-c-ares mingw-w64-ucrt-x86_64-libssh2' } + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: '-t --shallow=13 --subset=1/2', + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DENABLE_ARES=ON' } # WARNING: libssh uses hard-coded world-writable paths (C:ProgramData/, /etc/..., ~/.ssh/) # to read its configuration from, making it vulnerable to attacks on # Windows. Do not use this component till there is a fix for these. @@ -304,64 +293,76 @@ jobs: - { name: 'gnutls libssh', type: 'Debug', openssh: 'OpenSSH-Windows', build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: '', config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_GNUTLS=ON -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON -DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=ON -DCURL_ENABLE_NTLM=ON', - install: 'mingw-w64-clang-x86_64-gnutls mingw-w64-clang-x86_64-nghttp3 mingw-w64-clang-x86_64-ngtcp2 mingw-w64-clang-x86_64-libssh unzip' } + install: 'mingw-w64-clang-x86_64-gnutls mingw-w64-clang-x86_64-libssh unzip' } - { name: 'schannel R', type: 'Release', image: 'windows-11-arm', build: 'cmake' , sys: 'clangarm64', env: 'clang-aarch64', tflags: 'skiprun', - config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCURL_DROP_UNUSED=ON', - install: 'mingw-w64-clang-aarch64-libssh2' } + config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCURL_DROP_UNUSED=ON' } - { name: 'openssl', type: 'Debug', chkprefill: '_chkprefill', build: 'cmake' , sys: 'clang64' , env: 'clang-x86_64' , tflags: 'skiprun', - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON -DUSE_PROXY_HTTP3=ON', - install: 'mingw-w64-clang-x86_64-openssl mingw-w64-clang-x86_64-nghttp3 mingw-w64-clang-x86_64-ngtcp2 mingw-w64-clang-x86_64-libssh2' } + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=OFF -DUSE_NGTCP2=ON -DUSE_PROXY_HTTP3=ON' } - { name: 'openssl', type: 'Release', test: 'uwp', build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', - config: '-DBUILD_SHARED_LIBS=ON -DCURL_USE_OPENSSL=ON', - install: 'mingw-w64-ucrt-x86_64-openssl mingw-w64-ucrt-x86_64-libssh2' } + config: '-DBUILD_SHARED_LIBS=ON -DCURL_USE_OPENSSL=ON' } # { name: 'schannel', type: 'Release', test: 'uwp', # build: 'autotools', sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', - # config: '--without-debug --with-schannel --disable-static', - # install: 'mingw-w64-ucrt-x86_64-libssh2' } + # config: '--without-debug --with-schannel --disable-static' } - { name: 'schannel dev debug', type: 'Debug', cppflags: '-DCURL_SCHANNEL_DEV_DEBUG', image: 'windows-2025', - build: 'cmake' , sys: 'mingw64' , env: 'x86_64' , tflags: 'skiprun', - config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DCURL_USE_WOLFSSL=ON -DENABLE_UNICODE=ON -DCMAKE_VERBOSE_MAKEFILE=ON -DCURL_DISABLE_HTTPSIG=OFF', - install: 'mingw-w64-x86_64-libssh2 mingw-w64-x86_64-wolfssl' } - - { name: 'MultiSSL R', type: 'Release', - build: 'cmake' , sys: 'mingw32' , env: 'i686' , tflags: 'skiprun', - config: '-DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=ON -DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=ON -DCURL_USE_SCHANNEL=ON -DENABLE_ARES=ON -DENABLE_UNICODE=ON', - install: 'mingw-w64-i686-c-ares mingw-w64-i686-gnutls mingw-w64-i686-libssh2 mingw-w64-i686-openssl' } + build: 'cmake' , sys: 'ucrt64' , env: 'ucrt-x86_64' , tflags: 'skiprun', + config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=ON -DCURL_USE_SCHANNEL=ON -DCURL_USE_WOLFSSL=ON -DENABLE_UNICODE=ON -DCURL_DISABLE_HTTPSIG=OFF', + install: 'mingw-w64-ucrt-x86_64-wolfssl' } fail-fast: false steps: - name: 'preinstalled tools' shell: bash run: echo '::group::MSYS2 packages'; /c/msys64/usr/bin/pacman.exe -Q || true; echo '::endgroup::' - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 if: ${{ matrix.sys == 'msys' }} with: msystem: ${{ matrix.sys }} # https://packages.msys2.org/search + # Packages that are used by most jobs. They each inflate cache size and come with a per-job install overhead. + # Add individual package requirements to the matrix `install:` entry instead. install: >- - gcc - ${{ matrix.build }} ${{ matrix.build == 'autotools' && 'make' || 'ninja' }} - diffutils - zlib-devel - brotli-devel - libzstd-devel - libnghttp2-devel - libpsl-devel - ${{ matrix.install }} + gcc perl + zlib-devel brotli-devel libzstd-devel libpsl-devel libnghttp2-devel + openssl-devel libssh2-devel - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 if: ${{ matrix.sys != 'msys' }} with: msystem: ${{ matrix.sys }} - update: ${{ contains(matrix.install, 'wolfssl') && 'true' || 'false' }} + # Packages that are used by most jobs. They each inflate cache size and come with a per-job install overhead. + # Add individual package requirements to the matrix `install:` entry instead. install: >- + perl diffutils mingw-w64-${{ matrix.env }}-cc - mingw-w64-${{ matrix.env }}-${{ matrix.build }} ${{ matrix.build == 'autotools' && 'make' || '' }} - ${{ matrix.sys != 'mingw32' && format('mingw-w64-{0}-diffutils', matrix.env) || '' }} mingw-w64-${{ matrix.env }}-libpsl - ${{ matrix.install }} + mingw-w64-${{ matrix.env }}-libssh2 + mingw-w64-${{ matrix.env }}-c-ares + mingw-w64-${{ matrix.env }}-nghttp3 + mingw-w64-${{ matrix.env }}-ngtcp2 + mingw-w64-${{ matrix.env }}-openssl + + - name: 'install prereqs' + env: + MATRIX_INSTALL: '${{ matrix.install }}' + run: | + # Install build tools in a separate step to avoid inflating shared cache size and being installed even if unused. + if [ "${MATRIX_SYS}" = 'msys' ]; then + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + pkgs='cmake ninja' + else + pkgs='autotools' + fi + else + if [ "${MATRIX_BUILD}" = 'cmake' ]; then + pkgs="mingw-w64-${MATRIX_ENV}-cmake" + else + pkgs="mingw-w64-${MATRIX_ENV}-autotools" + fi + fi + /usr/bin/pacman --noconfirm --noprogressbar --sync --needed ${pkgs} ${MATRIX_INSTALL} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -378,7 +379,6 @@ jobs: CPPFLAGS: '${{ matrix.cppflags }}' MATRIX_CHKPREFILL: '${{ matrix.chkprefill }}' MATRIX_CONFIG: '${{ matrix.config }}' - MATRIX_ENV: '${{ matrix.env }}' MATRIX_TYPE: '${{ matrix.type }}' TFLAGS: '${{ matrix.tflags }}' run: | @@ -404,7 +404,7 @@ jobs: [ "${TFLAGS}" = 'skiprun' ] && options+=' -D_CURL_SKIP_BUILD_CERTS=ON' [ "${_chkprefill}" = '_chkprefill' ] && options+=' -D_CURL_PREFILL=OFF' cmake -B "bld${_chkprefill}" -G Ninja ${options} \ - -DCMAKE_INSTALL_PREFIX="${HOME}"/curl-install \ + -DCMAKE_INSTALL_PREFIX="$HOME"/curl-install \ -DCMAKE_C_FLAGS="${CFLAGS_CMAKE} ${CPPFLAGS}" \ -DCMAKE_BUILD_TYPE="${MATRIX_TYPE}" \ -DCMAKE_UNITY_BUILD=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=30 \ @@ -463,8 +463,8 @@ jobs: # avoid libtool's curl.exe wrapper for shared builds mv bld/src/.libs/curl.exe bld/src/curl.exe || true fi - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 file -- - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- if [ "${MATRIX_TEST}" != 'uwp' ]; then # curl: error initializing curl library bld/src/curl.exe --disable --version fi @@ -524,7 +524,7 @@ jobs: if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} timeout-minutes: ${{ contains(matrix.tflags, '-t') && 15 || 10 }} env: - MATRIX_ENV: '${{ matrix.env }}' + MATRIX_CONFIG: '${{ matrix.config }}' MATRIX_INSTALL: '${{ matrix.install }}' TFLAGS: '${{ matrix.tflags }}' run: | @@ -535,16 +535,16 @@ jobs: if [[ "${MATRIX_INSTALL}" = *'libssh2-wincng'* ]]; then TFLAGS+=' ~SCP ~SFTP' # Flaky: `-8, Unable to exchange encryption keys`. https://github.com/libssh2/libssh2/issues/804 unset CURL_TEST_SSH_KEYALGO # libssh2 built with WinCNG does not support ssh-ed25519 hostkeys - export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group-exchange-sha256 + export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group16-sha512 fi - if [[ "${MATRIX_INSTALL} " = *'-libssh '* && \ + if [[ "${MATRIX_CONFIG} " = *'-DCURL_USE_LIBSSH=ON'* && \ "${MATRIX_ENV}" = *'x86_64'* ]]; then export CURL_TEST_SSH_DISABLE_KEX=mlkem768x25519-sha256 # broken with libssh 0.12.0 Windows x64 fi fi if [ -n "${MATRIX_OPENSSH}" ]; then # OpenSSH-Windows TFLAGS+=' ~601 ~603 ~617 ~619 ~621 ~641 ~665 ~2004 ~3022' # SCP - if [[ "${MATRIX_INSTALL} " = *'libssh '* ]]; then + if [[ "${MATRIX_CONFIG} " = *'-DCURL_USE_LIBSSH=ON'* ]]; then TFLAGS+=' ~614' # 'SFTP pre-quote chmod' SFTP, pre-quote, directory fi fi @@ -555,9 +555,6 @@ jobs: PATH="/c/OpenSSH-Win64:$PATH" fi fi - if [ -x "$(cygpath "${SYSTEMROOT}/System32/curl.exe")" ]; then - TFLAGS+=" -ac $(cygpath "${SYSTEMROOT}/System32/curl.exe")" - fi PATH="$PATH:/c/my-stunnel/bin" if [ "${MATRIX_BUILD}" = 'cmake' ]; then PATH="$PWD/bld/lib:$PATH" @@ -589,7 +586,7 @@ jobs: run: shell: msys2 {0} # zizmor: ignore[misfeature] env: - CURL_TEST_MIN: 1630 + CURL_TEST_MIN: 1780 LDFLAGS: -s MAKEFLAGS: -j 5 MATRIX_DIR: '${{ matrix.dir }}' @@ -600,9 +597,9 @@ jobs: sys: 'mingw64' dir: 'w64devkit' env: 'x86_64' - ver: '16.1.0' - url: 'https://github.com/skeeto/w64devkit/releases/download/v2.8.0/w64devkit-x64-2.8.0.7z.exe' - SHA256: 6252bf34fe2231a55ac7f03d482b36d2c7c58697990551bba508102cfb3f342e + ver: '16.2.0' + url: 'https://github.com/skeeto/w64devkit/releases/download/v2.10.0/w64devkit-x64-2.10.0.7z.exe' # manually bumped + SHA256: 18d0a4c71a166f8401ab6305781bec5882b40b5e06ba9807c61cb5f3b3c6325e config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DCURL_USE_LIBSSH=ON -DENABLE_UNICODE=OFF -DENABLE_UNIX_SOCKETS=OFF -DCURL_GCC_ANALYZER=ON -DCMAKE_C_FLAGS=-DCURL_DEBUG_NO_WIN32_WND' install: perl mingw-w64-x86_64-libssh type: 'Debug' @@ -611,7 +608,7 @@ jobs: dir: 'mingw64' env: 'x86_64' ver: '9.5.0' - url: 'https://github.com/brechtsanders/winlibs_mingw/releases/download/9.5.0-10.0.0-msvcrt-r1/winlibs-x86_64-posix-seh-gcc-9.5.0-mingw-w64msvcrt-10.0.0-r1.7z' + url: 'https://github.com/brechtsanders/winlibs_mingw/releases/download/9.5.0-10.0.0-msvcrt-r1/winlibs-x86_64-posix-seh-gcc-9.5.0-mingw-w64msvcrt-10.0.0-r1.7z' # manually bumped SHA256: 41637132ea7dc36a7f86a1961eaa334c380b5a3423d36aecb481cabcd006e3fe config: '-DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DCURL_DISABLE_VERBOSE_STRINGS=ON -DCMAKE_C_FLAGS=-DCURL_DEBUG_NO_WIN32_WND' type: 'Release' @@ -621,43 +618,43 @@ jobs: dir: 'mingw64' env: 'x86_64' ver: '7.3.0' - url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-win32/seh/x86_64-7.3.0-release-win32-seh-rt_v5-rev0.7z' + url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-win32/seh/x86_64-7.3.0-release-win32-seh-rt_v5-rev0.7z' # manually bumped SHA256: 9dc08c9c2bdd5d8173f87791bed644f6e290624f739de474f117b590dfd8a721 config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=ON -DCURL_USE_MBEDTLS=ON -DCURL_TARGET_WINDOWS_VERSION=0x0600' install: mingw-w64-x86_64-mbedtls type: 'Debug' tflags: 'skiprun' - - name: 'schannel !unity' # mingw-w64 5.0 + - name: 'MultiSSL !unity' # mingw-w64 5.0 sys: 'mingw32' dir: 'mingw32' env: 'i686' ver: '6.4.0' - url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/6.4.0/threads-win32/dwarf/i686-6.4.0-release-win32-dwarf-rt_v5-rev0.7z' + url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/6.4.0/threads-win32/dwarf/i686-6.4.0-release-win32-dwarf-rt_v5-rev0.7z' # manually bumped SHA256: 12d2c62ad4527ec8a52275ea8485678dcbe20bec4716a3c7ba274f225d696085 - config: '-DCURL_USE_SCHANNEL=ON -DENABLE_UNICODE=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_TARGET_WINDOWS_VERSION=0x0600' - install: perl mingw-w64-i686-libssh2 - type: 'Debug' + config: '-DCURL_USE_GNUTLS=ON -DCURL_USE_OPENSSL=ON -DCURL_USE_SCHANNEL=ON -DENABLE_ARES=ON -DENABLE_UNICODE=OFF -DCMAKE_UNITY_BUILD=OFF -DCURL_TARGET_WINDOWS_VERSION=0x0600' + install: perl mingw-w64-i686-c-ares mingw-w64-i686-gnutls mingw-w64-i686-libssh2 mingw-w64-i686-openssl + type: 'Release' tflags: 'skiprun' - name: 'schannel !examples' # mingw-w64 3.0 sys: 'mingw64' dir: 'mingw64' env: 'x86_64' ver: '4.8.1' - url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.8.1/threads-win32/seh/x86_64-4.8.1-release-win32-seh-rt_v3-rev2.7z' + url: 'https://downloads.sourceforge.net/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.8.1/threads-win32/seh/x86_64-4.8.1-release-win32-seh-rt_v3-rev2.7z' # manually bumped (lowest supported mingw-w64 version) SHA256: 1353d997e85bb4494ebbebb432d824848d66b32c6045900da9a38a767b3c4ab4 config: '-DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=ON -DCURL_TARGET_WINDOWS_VERSION=0x0600' type: 'Debug' tflags: 'skipall' - chkprefill: '' # Set it once to silence actionlint fail-fast: false steps: - name: 'preinstalled tools' shell: bash run: echo '::group::MSYS2 packages'; /c/msys64/usr/bin/pacman.exe -Q || true; echo '::endgroup::' - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 with: msystem: ${{ matrix.sys }} + suppress-deprecation-warnings: 'msystem-mingw64 msystem-mingw32' release: false update: false cache: false @@ -744,8 +741,8 @@ jobs: - name: 'curl -V' timeout-minutes: 1 run: | - /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 file -- - /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- PATH="$PWD/bld/lib:$PATH" bld/src/curl.exe --disable --version @@ -765,14 +762,6 @@ jobs: key: ${{ runner.os }}-stunnel-${{ env.STUNNEL_VERSION }}-amd64 fail-on-cache-miss: true - - name: 'install test prereqs' - if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} - timeout-minutes: 2 - run: | - if bld/src/curl.exe --disable -V 2>/dev/null | grep smb; then - python3 -m pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi - - name: 'run tests' if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} timeout-minutes: 10 @@ -783,9 +772,6 @@ jobs: TFLAGS="-j8 ${TFLAGS}" TFLAGS+=' !498' # 'Reject too large HTTP response headers on endless redirects' HTTP, HTTP GET (runtests detecting result code 2009 instead of 56 returned by curl) TFLAGS+=' ~3000 ~3001 ~3023 ~3024' # 'HTTPS localhost, last subject alt name matches, CN does not match' HTTPS, HTTP GET, PEM certificate (returning 56) - if [ -x "$(cygpath "${SYSTEMROOT}/System32/curl.exe")" ]; then - TFLAGS+=" -ac $(cygpath "${SYSTEMROOT}/System32/curl.exe")" - fi PATH="$PWD/bld/lib:$PATH:/c/my-stunnel/bin" cmake --build bld --target test-ci @@ -801,8 +787,9 @@ jobs: linux-cross-mingw-w64: name: "linux-mingw, ${{ matrix.build == 'cmake' && 'CM' || 'AM' }} ${{ matrix.compiler }}" - runs-on: ubuntu-26.04 + runs-on: ubuntu-26.04-arm timeout-minutes: 10 + cache-mode: none env: LDFLAGS: -s MAKEFLAGS: -j 5 @@ -817,16 +804,11 @@ jobs: - { build: 'cmake' , compiler: 'gcc' } - { build: 'cmake' , compiler: 'clang-tidy', install_packages: 'clang-22 clang-tidy-22', CFLAGS: '-Wunused-macros' } steps: - - name: 'install packages' + - name: 'install prereqs' timeout-minutes: 2 - env: - MATRIX_INSTALL_PACKAGES: '${{ matrix.install_packages }}' - run: | - ls -l /etc/apt/sources.list.d - sudo find /etc/apt/sources.list.d -type f -not -name 'ubuntu.sources' -delete -print - sudo sed -i 's/priority:1/priority:9/' /etc/apt/apt-mirrors.txt; cat /etc/apt/apt-mirrors.txt - sudo apt-get -o Dpkg::Use-Pty=0 update - sudo apt-get -o Dpkg::Use-Pty=0 install gcc-mingw-w64-x86-64-win32 ${MATRIX_INSTALL_PACKAGES} + uses: $/.github/actions/pkg-install + with: + apt: gcc-mingw-w64-x86-64-win32 ${{ matrix.install_packages }} - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -846,6 +828,7 @@ jobs: options+=' -DENABLE_UNICODE=ON -DUSE_SSLS_EXPORT=ON' options+=' -DCMAKE_C_COMPILER=clang-22' options+=" -DCMAKE_RC_COMPILER=llvm-windres-$(clang-22 -dumpversion | cut -d '.' -f 1)" + options+=' -DCMAKE_RC_FLAGS=--target=pe-x86-64' # Required for non-x86-64 runners else options+=" -DCMAKE_C_COMPILER=${TRIPLET}-gcc" fi @@ -892,8 +875,8 @@ jobs: - name: 'curl info' run: | - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 file -- - find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + find . \( -name '*.exe' -o -name '*.dll' -o -name '*.a' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- - name: 'build tests' if: ${{ matrix.build == 'cmake' }} # Save time by skipping this for autotools and clang-tidy @@ -967,7 +950,7 @@ jobs: type: 'Debug' image: 'windows-2025' chkprefill: '_chkprefill' - tflags: '--min=1850' + tflags: '--min=1980' config: >- -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DUSE_NGTCP2=ON @@ -997,6 +980,7 @@ jobs: -DCURL_ENABLE_NTLM=ON -DUSE_PROXY_HTTP3=ON - name: 'schannel U' + install-msys2: 'perl' install-vcpkg: 'zlib libssh2[core,zlib]' arch: 'arm64' env: 'clang-aarch64' @@ -1004,9 +988,7 @@ jobs: type: 'Debug' image: 'windows-11-arm' openssh: 'OpenSSH-Windows' - tflags: '--min=1720' - # leave SMB disabled to save 30-60 seconds by omitting prereqs, - # to counteract the slower test run step + tflags: '--min=1850' config: >- -DENABLE_DEBUG=ON -DCURL_USE_SCHANNEL=ON @@ -1018,11 +1000,11 @@ jobs: shell: bash run: echo '::group::MSYS2 packages'; /c/msys64/usr/bin/pacman.exe -Q || true; echo '::endgroup::' - - uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0 with: msystem: ${{ matrix.arch == 'arm64' && 'clangarm64' || 'ucrt64' }} - release: ${{ contains(matrix.image, 'arm') }} - cache: ${{ contains(matrix.image, 'arm') }} + release: false + cache: ${{ matrix.tflags != 'skiprun' }} path-type: inherit install: >- mingw-w64-${{ matrix.env }}-libpsl @@ -1080,16 +1062,21 @@ jobs: # The option to disable the MSBuild heuristics is likely called: # IgnoreStandardErrorWarningFormat=true, but there seems to be no # facility to pass it via CMake or CLI. - MSVC_EDITION='2022/Enterprise/vc/tools/msvc' - [[ "${MATRIX_IMAGE}" = *'windows-2025'* ]] && MSVC_EDITION='18/Enterprise/vc/tools/msvc' [[ "$(uname -s)" = *'ARM64'* ]] && MSVC_HOST='arm64' || MSVC_HOST='x64' # x86 MSVC_ROOTD="$(cygpath --mixed --short-name "$PROGRAMFILES/Microsoft Visual Studio")" # to avoid spaces in directory names + echo 'Installed MSVC versions:' + /usr/bin/find "$(cygpath --unix "$MSVC_ROOTD")" -mindepth 2 -maxdepth 2 -type d -name '*' || true + MSVC_EDITION='18/Enterprise/vc/tools/msvc' + echo 'Installed MSVC roots:' + /usr/bin/find "$(cygpath --unix "$MSVC_ROOTD/$MSVC_EDITION")" -mindepth 1 -maxdepth 1 -type d -name '*.*' MSVC_ROOTU="$(/usr/bin/find "$(cygpath --unix "$MSVC_ROOTD/$MSVC_EDITION")" -mindepth 1 -maxdepth 1 -type d -name '*.*' | sort | tail -n 1)" MSVC_ROOTW="$(cygpath --mixed "$MSVC_ROOTU")" MSVC_ROOTU="$(cygpath --unix "$MSVC_ROOTW")" MSVC_BINU="$MSVC_ROOTU/bin/Host$MSVC_HOST/$MATRIX_ARCH" MSDK_ROOTW="$(cygpath --mixed --short-name "$(printenv 'ProgramFiles(x86)')/Windows Kits")/10" MSDK_ROOTU="$(cygpath --unix "$MSDK_ROOTW")" + echo 'Installed Windows Kits:' + /usr/bin/find "$MSDK_ROOTU/lib" -mindepth 1 -maxdepth 1 -type d -name '*.*' MSDK_VER="$(basename "$(/usr/bin/find "$MSDK_ROOTU/lib" -mindepth 1 -maxdepth 1 -type d -name '*.*' | sort | tail -n 1)")" MSDK_LIBW="$MSDK_ROOTW/lib/$MSDK_VER" MSDK_INCW="$MSDK_ROOTW/include/$MSDK_VER" @@ -1158,17 +1145,11 @@ jobs: timeout-minutes: 5 run: cmake --build bld --config "${MATRIX_TYPE}" --parallel 5 - - name: 'libcurl.pc, curl-config' - run: | - for f in libcurl.pc curl-config; do - echo "::group::${f}"; grep -v '^#' bld/"${f}" || true; echo '::endgroup::' - done - - name: 'curl -V' timeout-minutes: 1 run: | - /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 file -- - /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- + /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | sort -z | xargs -0 file -- + /usr/bin/find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- if [ "${MATRIX_PLAT}" != 'uwp' ]; then # Missing: ucrtbased.dll, VCRUNTIME140D.dll, VCRUNTIME140D_APP.dll PATH="$PWD/bld/lib/${MATRIX_TYPE}:$(cygpath --unix "${VCPKG_INSTALLATION_ROOT}")/installed/${MATRIX_ARCH}-${MATRIX_PLAT}/${MATRIX_TYPE}/bin:$PATH" "bld/src/${MATRIX_TYPE}/curl.exe" --disable --version @@ -1212,9 +1193,6 @@ jobs: sha256sum pkg.bin && sha256sum pkg.bin | grep -qwF -- "${OPENSSH_WINDOWS_SHA256_WIN64}" && unzip pkg.bin && rm -f pkg.bin fi fi - if "bld/src/${MATRIX_TYPE}/curl.exe" --disable -V 2>/dev/null | grep smb; then - python3 -m pip --disable-pip-version-check --no-input --no-cache-dir install --progress-bar off --prefer-binary -r tests/requirements.txt - fi - name: 'run tests' if: ${{ matrix.tflags != 'skipall' && matrix.tflags != 'skiprun' }} @@ -1229,7 +1207,7 @@ jobs: "${MATRIX_INSTALL_VCPKG}" = *'libssh2[core,zlib]'* ]]; then TFLAGS+=' ~SCP ~SFTP' # Flaky: `-8, Unable to exchange encryption keys`. https://github.com/libssh2/libssh2/issues/804 unset CURL_TEST_SSH_KEYALGO # libssh2 built with WinCNG does not support ssh-ed25519 hostkeys - export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group-exchange-sha256 + export CURL_TEST_SSH_ENABLE_KEX=diffie-hellman-group16-sha512 fi if [ -n "${MATRIX_OPENSSH}" ]; then # OpenSSH-Windows TFLAGS+=' ~601 ~603 ~617 ~619 ~621 ~641 ~665 ~2004' # SCP diff --git a/.mailmap b/.mailmap index 22b0f256b8ce..c2bade7d0d97 100644 --- a/.mailmap +++ b/.mailmap @@ -125,3 +125,4 @@ Kaixuan Li Darren Banfi Alhuda Khan Ralf Mueller <217359725+hunterinvariants@users.noreply.github.com> +Joshua Rogers diff --git a/CMake/CurlTests.c b/CMake/CurlTests.c index e8117410fd20..f6027d0af4f6 100644 --- a/CMake/CurlTests.c +++ b/CMake/CurlTests.c @@ -331,7 +331,7 @@ int main(void) #ifdef HAVE_BUILTIN_AVAILABLE int main(void) { - if(__builtin_available(macOS 10.12, iOS 5.0, *)) {} + if(__builtin_available(macOS 10.12, iOS 5, *)) {} return 0; } #endif @@ -388,3 +388,13 @@ int main(void) return 0; } #endif + +#ifdef HAVE_TIME_T_UNSIGNED +#include +static int time_t_is_unsigned[((time_t)-1 > 0) ? 1 : -1]; +int main(void) +{ + (void)time_t_is_unsigned; + return 0; +} +#endif diff --git a/CMake/FindMbedTLS.cmake b/CMake/FindMbedTLS.cmake index 21a5f4aec734..804596a261e1 100644 --- a/CMake/FindMbedTLS.cmake +++ b/CMake/FindMbedTLS.cmake @@ -37,12 +37,6 @@ # - `MBEDTLS_VERSION`: Version of mbedTLS. # - `CURL::mbedtls`: mbedTLS library target. -if(DEFINED MBEDTLS_INCLUDE_DIRS AND NOT DEFINED MBEDTLS_INCLUDE_DIR) - message(WARNING "MBEDTLS_INCLUDE_DIRS is deprecated, use MBEDTLS_INCLUDE_DIR instead.") - set(MBEDTLS_INCLUDE_DIR "${MBEDTLS_INCLUDE_DIRS}") - unset(MBEDTLS_INCLUDE_DIRS) -endif() - set(_mbedtls_pc_requires "mbedtls" "mbedx509" "mbedcrypto") if(NOT DEFINED MBEDTLS_INCLUDE_DIR AND diff --git a/CMake/FindWolfSSL.cmake b/CMake/FindWolfSSL.cmake index 964320ad6b60..32a9656f3120 100644 --- a/CMake/FindWolfSSL.cmake +++ b/CMake/FindWolfSSL.cmake @@ -34,15 +34,6 @@ # - `WOLFSSL_VERSION`: Version of wolfSSL. # - `CURL::wolfssl`: wolfSSL library target. -if(DEFINED WolfSSL_INCLUDE_DIR AND NOT DEFINED WOLFSSL_INCLUDE_DIR) - message(WARNING "WolfSSL_INCLUDE_DIR is deprecated, use WOLFSSL_INCLUDE_DIR instead.") - set(WOLFSSL_INCLUDE_DIR "${WolfSSL_INCLUDE_DIR}") -endif() -if(DEFINED WolfSSL_LIBRARY AND NOT DEFINED WOLFSSL_LIBRARY) - message(WARNING "WolfSSL_LIBRARY is deprecated, use WOLFSSL_LIBRARY instead.") - set(WOLFSSL_LIBRARY "${WolfSSL_LIBRARY}") -endif() - set(_wolfssl_pc_requires "wolfssl") if(NOT DEFINED WOLFSSL_INCLUDE_DIR AND diff --git a/CMake/FindZstd.cmake b/CMake/FindZstd.cmake index 5a2db76bb1a2..81ecd746d94b 100644 --- a/CMake/FindZstd.cmake +++ b/CMake/FindZstd.cmake @@ -35,15 +35,6 @@ # - `ZSTD_VERSION`: Version of zstd. # - `CURL::zstd`: zstd library target. -if(DEFINED Zstd_INCLUDE_DIR AND NOT DEFINED ZSTD_INCLUDE_DIR) - message(WARNING "Zstd_INCLUDE_DIR is deprecated, use ZSTD_INCLUDE_DIR instead.") - set(ZSTD_INCLUDE_DIR "${Zstd_INCLUDE_DIR}") -endif() -if(DEFINED Zstd_LIBRARY AND NOT DEFINED ZSTD_LIBRARY) - message(WARNING "Zstd_LIBRARY is deprecated, use ZSTD_LIBRARY instead.") - set(ZSTD_LIBRARY "${Zstd_LIBRARY}") -endif() - set(_zstd_pc_requires "libzstd") if(NOT DEFINED ZSTD_INCLUDE_DIR AND diff --git a/CMake/unix-cache.cmake b/CMake/unix-cache.cmake index 8ec2cd48c193..36c75e7daa0e 100644 --- a/CMake/unix-cache.cmake +++ b/CMake/unix-cache.cmake @@ -80,6 +80,7 @@ if((APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.9) OR elseif(NOT APPLE) set(HAVE_MEMSET_S 0) endif() +set(HAVE_FCHMOD 1) set(HAVE_FCNTL 1) set(HAVE_FCNTL_H 1) set(HAVE_FCNTL_O_NONBLOCK 1) diff --git a/CMakeLists.txt b/CMakeLists.txt index 693d93d01417..c0d5724ec407 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -498,8 +498,6 @@ option(CURL_DISABLE_SHA512_256 "Disable SHA-512/256 hash algorithm" OFF) 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_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) @@ -534,28 +532,6 @@ if(CURL_DISABLE_HTTP) set(CURL_DISABLE_WEBSOCKETS ON) endif() -# Corresponds to HTTP_ONLY in lib/curl_setup.h -option(HTTP_ONLY "Disable all protocols except HTTP (This overrides all CURL_DISABLE_* options)" OFF) -mark_as_advanced(HTTP_ONLY) - -if(HTTP_ONLY) - set(CURL_DISABLE_DICT ON) - set(CURL_DISABLE_FILE ON) - set(CURL_DISABLE_FTP ON) - set(CURL_DISABLE_GOPHER ON) - set(CURL_DISABLE_IMAP ON) - set(CURL_DISABLE_IPFS ON) - set(CURL_DISABLE_LDAP ON) - set(CURL_DISABLE_LDAPS ON) - set(CURL_DISABLE_MQTT ON) - set(CURL_DISABLE_POP3 ON) - set(CURL_DISABLE_RTSP ON) - set(CURL_DISABLE_SMTP ON) - set(CURL_DISABLE_TELNET ON) - set(CURL_DISABLE_TFTP ON) - set(CURL_DISABLE_WEBSOCKETS ON) -endif() - if(WINDOWS_STORE) set(CURL_DISABLE_TELNET ON) # telnet code needs fixing to compile for UWP. endif() @@ -639,13 +615,13 @@ elseif(APPLE) set(HAVE_WRITABLE_ARGV 1) set(HAVE_SENDMMSG 0) endif() +if(DOS OR AMIGA) + set(HAVE_TIME_T_UNSIGNED 1) # Pre-fill +endif() if(AMIGA) set(HAVE_GETADDRINFO 0) # Breaks the build when detected and used. endif() -if(DOS OR AMIGA) - set(HAVE_TIME_T_UNSIGNED 1) -endif() if(NOT WIN32) find_package(Threads) @@ -832,6 +808,10 @@ if(_use_core_foundation_and_core_services) list(APPEND CURL_LIBS "-framework CoreServices") endif() +if(NOT DEFINED _curl_stdlib_cxx) + set(_curl_stdlib_cxx "stdc++") +endif() + if(CURL_USE_OPENSSL) find_package(OpenSSL REQUIRED) set(_ssl_enabled ON) @@ -870,9 +850,9 @@ if(CURL_USE_OPENSSL) message(WARNING "AWS-LC/BoringSSL 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++") + if(OPENSSL_USE_STATIC_LIBS AND + (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")) + list(APPEND CURL_LIBS "${_curl_stdlib_cxx}") endif() endif() @@ -1020,6 +1000,10 @@ macro(curl_openssl_check_exists) if(WIN32) list(APPEND CMAKE_REQUIRED_LIBRARIES "bcrypt") # for OpenSSL/LibreSSL BCryptGenRandom() endif() + if((HAVE_AWSLC OR HAVE_BORINGSSL) AND OPENSSL_USE_STATIC_LIBS AND + (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")) + list(APPEND CMAKE_REQUIRED_LIBRARIES "${_curl_stdlib_cxx}") + endif() endif() if(USE_WOLFSSL) list(APPEND CMAKE_REQUIRED_LIBRARIES CURL::wolfssl) @@ -1682,15 +1666,17 @@ check_function_exists("setrlimit" HAVE_SETRLIMIT) if(APPLE) check_function_exists("mach_absolute_time" HAVE_MACH_ABSOLUTE_TIME) else() - # Apple platforms do not offer pipe2(), but the iPhone Simulator-specific + # Apple SDK <27 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. + # For 27+, detection is done in-source. check_function_exists("pipe2" HAVE_PIPE2) endif() if(NOT WIN32) check_include_file("dirent.h" HAVE_DIRENT_H) + check_function_exists("fchmod" HAVE_FCHMOD) check_function_exists("gettimeofday" HAVE_GETTIMEOFDAY) # sys/time.h check_function_exists("if_nametoindex" HAVE_IF_NAMETOINDEX) # net/if.h check_function_exists("realpath" HAVE_REALPATH) @@ -1819,16 +1805,7 @@ if(NOT WIN32 AND NOT CMAKE_CROSSCOMPILING) }" HAVE_WRITABLE_ARGV) endif() -if(NOT CMAKE_CROSSCOMPILING) - include(CheckCSourceRuns) - check_c_source_runs(" - #include - int main(void) - { - time_t t = -1; - return t < 0; - }" HAVE_TIME_T_UNSIGNED) -endif() +curl_internal_test(HAVE_TIME_T_UNSIGNED) curl_internal_test(HAVE_GLIBC_STRERROR_R) curl_internal_test(HAVE_POSIX_STRERROR_R) @@ -2022,10 +1999,6 @@ curl_add_if("POP3" NOT CURL_DISABLE_POP3) 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" CURL_ENABLE_SMB AND - _use_curl_ntlm_core AND (SIZEOF_CURL_OFF_T GREATER 4)) -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) curl_add_if("SCP" USE_LIBSSH2 OR USE_LIBSSH) diff --git a/Dockerfile b/Dockerfile index 934d4b119caf..e52e1b5ca365 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ # $ ./scripts/maketgz 8.7.1 # To update, get the latest digest e.g. from https://hub.docker.com/_/debian/tags -FROM debian:bookworm-slim@sha256:88200866dfff7ea7f5cbcb6ec7c8a701889efe6fe859fe64d6990e4b07ea4171 +FROM debian:bookworm-slim@sha256:7c7b2c966bc9ee8cedfeef67e0e279108992c77681fa595db4a9d65c06ccc587 RUN apt-get update -qq && apt-get install -qq -y --no-install-recommends \ build-essential make autoconf automake libtool git perl zip zlib1g-dev gawk && \ diff --git a/README.md b/README.md index 9f77de607be2..f8061f2edc0b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: curl curl is a command-line tool for transferring data from or to a server using URLs. It supports these protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, -SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. +SFTP, SMTP, SMTPS, TELNET, TFTP, WS and WSS. Learn how to use curl by reading [the man page](https://curl.se/docs/manpage.html) or [everything diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 8342fec4564d..2ef30ed91d96 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,325 +1,304 @@ -curl and libcurl 8.22.0 +curl and libcurl 8.23.0 Public curl releases: 276 Command line options: 278 curl_easy_setopt() options: 312 Public functions in libcurl: 100 - Authors: 1518 - Contributors: 3786 + Authors: 1547 + Contributors: 3844 This release includes the following changes: - o gssapi: add support for Apple GSS Framework [72] - o hardening: add API guards [64] - o RFC 9421 HTTP Message Signatures support [108] - o spnego: block NTLM fallback in SPNEGO negotiation [151] - o TLS: drop support for TLS-SRP [71] - o vquic: add option to use Apple fast UDP [137] + o lib: CURL_GLOBAL_WIN32 -> CURL_GLOBAL_WINSOCK [64] + o SMB: drop support [63] + o tool_cb_wrt: inject the no-clobber number before the extension [21] + o tool_cb_wrt: try up to .9999 when no-clobbering [46] This release includes the following bugfixes: - o altsvc: continue after unknown parameters [198] - o asyn-thrdd: retry link-local ipv6 if missing scope id [118] - o autotools: minor fixes and improvements [33] - o build: always use local `inet_pton()`/`inet_ntop()` implementations [56] - o build: assume POSIX `select()` is available [166] - o build: clear `Require.private` for static-only builds in `libcurl.pc` [188] - o build: drop `dirent.h` and `opendir()` detections on Windows [186] - o build: drop detecting `gettimeofday()` on Windows [184] - o build: drop superfluous `STDC_HEADERS` macro [51] - o build: enable thread-safe `getaddrinfo()` for OpenBSD [35] - o build: minor debug option message fixes/improvements [200] - o build: require `!NDEBUG` for debug-enabled (aka development) builds [202] - o build: strip duplicate spaces after `Libs.private:` in `libcurl.pc` [191] - o build: strip trailing spaces from `libcurl.pc` [194] - o cd2nroff: fix backslashes for 4-space indent lines [104] - o cd2nroff: stricter checks for asterisks for italics [73] - o cf-ngtcp2-cmn: de-duplicate `ngtcp2_conn_client_new()` call code [156] - o cf-ngtcp2-cmn: initialize new callback ptr for ngtcp2 1.24.0+ [52] - o cf-socket: avoid broken NetBSD SOCK_NONBLOCK [275] - o cf-socket: disable TCP SYN retransmissions for localhost on Windows [164] - o cfilters: fix event-based connection shutdown [91] - o clock: save one call [286] - o cmake/FindLibgsasl: fix to set `LIBGSASL_VERSION` with pkg-config detection [229] - o cmake: check libgsasl version at configure time [277] - o cmake: dedupe expressions into local vars in `cmake_uninstall.in.cmake` [9] - o cmake: fix not to build `tunits` when `BUILD_CURL_EXE=OFF` [7] - o cmake: flatten build tree, tidy up base dir variables [12] - o cmake: minor improvements to `cmake_uninstall.in.cmake` [54] - o cmake: optimize OpenSSL fork detection [228] - o cmake: replace `remove` command with `rm` and pass arg safely [11] - o cmake: robustify base path in local file reference [15] - o cmake: stop probing unused `float.h` for `STDC_HEADERS` [10] - o cmake: use built-in variable and target property dump functions with CMake 4.5+ [155] - o config-riscos.h: delete handcrafted RISC OS config header, in favor of autotools [178] - o config-win32.h: drop UWP, c-ares, simplify more [231] - o config-win32.h: limit use to MSVC IDE Project builds [193] - o configure: clarify --enable-debug option [133] - o configure: fix misleading error messages [42] - o configure: link `-lcrypt32` instead of `-lm` for wolfSSL on Windows [79] - o configure: only check in the watt library if WATT_ROOT is set [120] - o configure: remove double check for GnuTLS [21] - o configure: set ldap lib to no by default for non-finds [18] - o conncache: apply multi limits to transfers using a shared pool [41] - o conncache: conn upkeep/alive: move and enhance [152] - o conncache: connection alive checks intervals [20] - o conncache: don't assume curl_off_t increment wrap-around [138] - o conncache: guess maxconnects different [289] - o connect: connection close tweaks [112] - o connect: only set connect timer on first socket [206] - o connection reuse: age check [261] - o connection reuse: check SSL configs when doing a scheme upgrade [249] - o connections: use admin handles only for maintenance [213] - o content_encoding: exact-match the identity transfer-coding token [189] - o content_encoding: give a clear error on multi-member gzip [46] - o cookie: cookies set for an exact PSL domain is host-only [304] - o cookie: improve TAB handling [258] - o cookie: refuse to load cookies set against a PSL domain [139] - o CREDENTIALS.md: remove comment about empty user/pass [50] - o ctype: exclude control bytes from ISPRINT and ISGRAPH [119] - o curl: help category cleanups [169] - o curl_gssapi: document/update feature availability [145] - o curl_threads: always use native threads/mutex on Windows [185] - o curl_trc: remove unused expire timers [147] - o curl_url_set.md: expand the CURLU_NO_AUTHORITY description [134] - o curl_ws_meta.md: polish and better vocabulary [19] - o CURLOPT_HEADERFUNCTION.md: document folded header unfolding [53] - o CURLOPT_SOCKOPTFUNCTION.md: ALREADY_CONNECTED does not work for HTTP/3 [262] - o CURLOPT_SSH_*_KEYFILE: used for setting up, then no more [48] - o CURLOPT_UNRESTRICTED_AUTH.md: 'Authorization', not 'Authentication' [74] - o CURLOPT_USERNAME.md: ambient username caveats [271] - o CURLSHOPT_(UN)SHARE.md: do not modify shares while in use [44] - o curlx_inet_ntop: return `CURLcode`, drop setting `errno` [237] - o curlx_inet_pton: drop setting `errno` on error [236] - o DEPRECATE.md: HTTP/2 Server Push gets removed in March 2027 [174] - o dict: avoid busy-loop in sendf() when the socket is not writable [99] - o dist: fix to drop test bundle .c files from the source tarball [305] - o dnsd: fix bounds check in `read_https_alpn_part()` [143] - o docs/INTERNALS.md -> docs/DEPENDENCIES.md [127] - o docs: clarify that cookies need domain set to match [224] - o docs: connection reuse behavior for socket callbacks [219] - o docs: make 5 example snippets compile cleanly with clang [192] - o docs: mention possible auth option conflicts [114] - o docs: remove doubled word in SECURITY-ADVISORY.md [183] - o DoH: improvements [203] - o easy: fix unused global on non-Windows [292] - o easy_lock: silence `portability-no-assembler` with clang-tidy 23.1.0+ [291] - o FAQ: correct an option typo [278] - o file: support directory listing on Windows [205] - o filter: change time reporting [235] - o FTP: fix TLS session reuse on the data connection [80] - o ftp: reject control bytes in ACCT and alternative-to-user [26] - o gitignore: maintenance updates [170] - o gopher: fix partial sends of CRLF [288] - o gopher: reject CR and LF in the selector [1] - o h2 push: use squeaky clean easy handle [246] - o h2: bootstrap max streams from multi handle if in use [132] - o h3-proxy: fix NULL deref when non-:status header arrives before :status [167] - o Happy Eyeballing v3: resolution delay of 25ms [232] - o header api: add guards [168] - o headers: name the arguments the way the definitions name them [234] - o HISTORY.md: PSL support in 2015 - o HISTORY: add when c-ares support was introduced (2004) - o HISTORY: September 1999: started using CVS - o hostip: only cache negative resolves for authoritative answers [16] - o hsts: only match the exact strings [269] - o http digest: tie peer/credentials on input [264] - o http2: make server push transfers inherit share from parent [81] - o http2: remove assert in ingress processing [272] - o http: avoid length underflow in Curl_compareheader [78] - o http: custom Authorization: header overrides Negotiate [223] - o http: fix non-tunneling proxy hostname use [116] - o http: stop dropping large custom headers [69] - o http: trim custom header name before the Authorization drop [17] - o httpsrr: DoH with HTTPS, fix response handling [113] - o idn: restore `MultiByteToWideChar()` `MB_ERR_INVALID_CHARS` flag [103] - o imap: APPEND CRLF fix [256] - o include: include when building for modern Linux. [308] - o INSTALL.md: add building-from-source overview section [29] - o INTERNALS.md: require quiche 0.20.0+ [101] - o ipv6 scope_id: set from first peer [242] - o keylog: add a random size argument to Curl_tls_keylog_write() [180] - o ldap: base64-encode LDIF values beginning with colon or less-than [218] - o ldap: reject control characters in URL-decoded filter values [196] - o ldap: support empty username and password [106] - o ldap: support insecure mode for Windows native LDAP [3] - o lib1587: fix gcc `-Wconversion` with LibreSSL on Windows, test in CI [6] - o lib2405: adjust for non-threaded builds [149] - o lib: add "Curl_" prefix to two global functions [84] - o lib: add multi_wakeup_internal [86] - o lib: drop unused `system_win32.h` includes [290] - o lib: fix 'ns' -> 'us' in trace messages [57] - o lib: new easy option string storage [215] - o lib: optimize struct layouts for reduced memory usage [212] - o lib: ratelimit timestamps [14] - o lib: silence gcc-16 compiler warnings `-Wmaybe-uninitialized` [243] - o lib: update mentions of the legacy "sessionhandle" [157] - o libcurl.md: emphasize that the output needs checking [259] - o libcurl.pc: add `License` tag [190] - o libcurl.pc: add Copyright tag to the pkgconf file - o libcurl.pc: add the Link.ABI and Source tags [210] - o macos sectrust: fail ocsp verify when not builtin [252] - o Makefile.am: improve etags [257] - o mbedtls: enforce verifyhost when verifypeer is disabled [208] - o mbedtls: replace `memset()` with `psa_hash_operation_init()` [28] - o md5: replace magic numbers with `MD5_DIGEST_LEN` [122] - o mime.c: avoid integer overflow in base64 size calculation [105] - o mime: reject CR and LF in mail part name and filename [30] - o mod_curltest: fix compiler warnings [49] - o mprintf: acknowledge %F [245] - o mprintf: avoid never-ending loop for positive-infinite [247] - o mprintf: fix long double output [250] - o mqtt: reject control bytes in the topic [43] - o multi: cap expire times to INT_MAX internally [216] - o multi: forbid curl_easy_pause from within multi socket callback [22] - o multi: hold timeout values in 'int' instead of 'long' [165] - o multi: remove #if 0'ed code that uses old struct [150] - o multi: shrink expire timer indices [199] - o multi: timeout improvements [209] - o multi: use index list for expire timeouts [197] - o multi: xfer table initial size and growth [255] - o multihandle: move two struct fields [163] - o ngtcp2+openssL: fix early data [225] - o ngtcp2: avoid NULL deref in cf_ngtcp2_send [260] - o ngtcp2: clean up after ngtcp2 in `curl_global_cleanup` [126] - o ngtcp2: let verify failures win over expiry processing errors [98] - o openldap: handle Curl_sasl_continue() returns better [45] - o openssl+sectrust: fix session reuse [4] - o openssl+sectrust: move session verified set into result check [82] - o openssl: avoid conn reuse if provider is used [214] - o openssl: avoid strlen() on the data from OpenSSL [280] - o openssl: aws-lc ocsp workaround [263] - o openssl: drop unused pre-OpenSSL3 `ctx_option_t` typedef [8] - o openssl: fix DER buffer leak in Apple SecTrust verification [217] - o openssl: no server cert is only okay if also not pinned [226] - o openssl: prefer modern API flavors for `EVP_MD_CTX` new/free [47] - o openssl: replace stray legacy API variant with `EVP_DigestInit_ex()` [27] - o os400: port latest header files changes to ILE/RPG interface [241] - o os400: rewrite upper ebcdic wrappers using dynbuf [227] - o progress: cleanup, less memory [179] - o protocol: simpler Curl_getn_scheme runs faster [239] - o proxy: CONNECT trailers handling [251] - o psl: update a comment to understandable English [162] - o pytest: update two H3 tests for nghttp3 1.18.0+ [158] - o quic: upload improvements [276] - o quiche: set the max field section size [100] - o rtsp: refactor method handling and improve error checks [161] - o runtests: allow comments in `setenv` section, merge sections in test433 [89] - o runtests: fix `mode="warn"` tests passing unconditionally, fix test 1752 [66] - o runtests: flush cached test parts when (re)loading a file [95] - o runtests: restore `-k` option and actively process as no-op [32] - o sasl: fix zero-length response encoding [36] - o schannel: add ALPN support for mingw-w64 <9 and =128-bit pointers [107] - o url: fix handling of empty user in NTLM matching [221] - o url: fix negotiate/ntlm connection reuse [176] - o url: reject control codes in credentials set via CURLOPT [70] - o urlapi: allow URLs to not have userauth (hostname) [92] - o urlapi: avoid dedotdotify() if possible [182] - o urlapi: clear password buffer on error path [121] - o urlapi: do not keep an internal port string [31] - o urlapi: improved return codes [148] - o urlapi: preserve empty markers in relative URLs [61] - o urldata: cleanups [175] - o urldata: drop four strings from the aptr struct [136] - o urldata: sort the connectdata struct fields by size [177] - o VERSIONS.md: document Rock-solid curl releases [201] - o vms: fix symbol typo and missing closing quotes in `config_h.com` [124] - o vquic: add Curl_ prefix to some global functions [76] - o vquic: initialize new callback slot for nghttp3 v1.18.0+ [87] - o vquic: silence `-Wmissing-field-initializers` for nghttp3/ngtcp2 callback tables [159] - o vquic: use ngtcp2 v1.25.0 new close2 callback [154] - o vssh: keyfile use cleanups [83] - o vssh: silence gcc-11 `-Wnull-dereference`, dedupe `CURL_EASY_STR()` calls [240] - o vtls: move 'native_ca_store' ssl_config_data => ssl_primary_config [211] - o vtls_scache: use case sensitive path match - o VULN-DISCLOSURE-POLICY.md: issues that should be found by tests are LOW [5] - o wcurl: import v2026.08.30 [279] - o websocket: pause writing and meta data fix [135] - o winsock: drop redundant version checks at initialization [284] - o wolfssl: do not run Curl_wssl_setup_x509_store() twice [265] - o wolfssl: fix build for wolfssl without bio chain support [75] - o ws: fix write callback error handling [204] - o ws: pause/unpause write handling [55] + o apple-sectrust: check ocsp copy alloc [292] + o apple: shorten versions in `__builtin_available()` calls [251] + o apple: sync hash init calls, add comments [217] + o apple: sync up version guard macro style [263] + o base64: avoid repeated input loads when encoding [114] + o base64: optimize decode lookup table and quantum loop [88] + o build: convert HAS_TIME_T_UNSIGNED to a compile-time check [65] + o build: drop `HTTP_ONLY` option [23] + o capsule: reject a capsule that can never be buffered [84] + o cf-h1-proxy: reset chunk state on new request [146] + o cf-h1-proxy: stricter status line parsing [145] + o cf-https-connect: re-arm the timer for the hard eyeballs timeout [222] + o cf-ngtcp2-cmn: fix possible `-Wdeclaration-after-statement` [296] + o cf-ngtcp2-proxy: check for stream close on rx win update [159] + o cf-ngtcp2-proxy: fix rx flow control [174] + o cf-socket: adjust NO_SYN_RETRANSMISSIONS for older Windows [275] + o cf-socket: clamp keepalive seconds-to-milliseconds conversions [167] + o cfilters: fail a send that reports more written than requested [194] + o clang-tidy: merge nested `if()`s [39] + o cmake/Find*: drop deprecated compatibility config variables [259] + o cmake: enable C++ library for GCC in AWS-LC/BoringSSL static builds [232] + o cmake: link llvm stdc++ lib to AWS-LC/BoringSSL-specific feature detections only [271] + o config2setopts: always set the security options for SCP/SFTP [67] + o config2setopts: rename define for maximum cookie header length [248] + o conncache: avoid evicting connect-only connections [79] + o conncache: never discard a connection still in use [208] + o conncache: remove bundle dest [50] + o connect: do not busy-loop in blocking connect without a timeout [240] + o connect: keep IP info and connect time when connect fails [195] + o connectdata: use less curltime [34] + o cookie: accept and ignore empty property names in Set-Cookie [234] + o cookie: accept and ignore values for 'secure' and 'httponly' [187] + o cookie: accept slightly longer lines in cookie jars [105] + o cookie: cookies for a PSL domain from jar just limits tailmatch [176] + o cookie: ignore individual cookie errors when loading [6] + o cpool: do not use admin handle for conn lookup [299] + o cshutdown: make it smaller [60] + o cshutdown: use ptrarray instead of llist [156] + o curl-openssl.m4: sync LibreSSL detection method with rest of forks [218] + o curl_addrinfo: drop redundant parentheses [41] + o curl_easy_send/recv: check pointer arg [175] + o curl_fopen: restore the uid and gid checks [70] + o curl_formadd.md: mention the strlen() for names as well [29] + o curl_multi_perform: doc update [148] + o curl_setup.h: drop compatibility raw C flag `ENABLE_IPV6` [260] + o CURLINFO_EFFECTIVE_URL.md: mention a short lifetime [116] + o CURLOPT*ISSUERCERT*: not verified cryptographically [28] + o CURLOPT_POSTFIELDSIZE.md: improve wording [144] + o CURLOPT_SHARE.md: remove bad explanation for cookie sharing [7] + o CURLOPT_SSL_CTX_FUNCTION.md: client cert caveats [87] + o curlx_fopen: add macro to cast file mode [245] + o cw-out: avoid O(n^2) work while buffering paused output [74] + o cw-out: recheck callbacks for each call [134] + o cw-pause: fix O(N^2) list traversal in cw_pause_flush [137] + o DEPENDENCIES.md: fix release date for valgrind 3.0.0 to 2005-08-03 + o DEPRECATE.md: reflect SMB removal [252] + o DEPRECATE: Common Name support in TLS certificates for OpenSSL [172] + o digest: check peer equality for staleness [24] + o digest: fix maximum length quoted value parsing [22] + o digest: parse without storing 'value' in local buffer [20] + o digest: quote the digest-uri param as well [85] + o dnscache/conncache tweaks [75] + o dnscache: check hostname on lookup [209] + o dnscache: compare full hostnames when lookup keys are truncated [301] + o docs: add the missing .html to the url-encode book link [3] + o docs: document and align the blocking situations [163] + o docs: note CAINFO/SSL_VERIFYPEER/SSL_VERIFYSTATUS effect on LDAP [188] + o docs: SSL session import/file updates [199] + o doh: reject undersized HTTPS RDATA [11] + o dynhds: check size overflows [198] + o dynhds: grow header array geometrically [73] + o easy: change event pollset construction [242] + o easy: duplicating a zero byte memory area needs no malloc [91] + o easy: make ca_cache_timeout standalone [51] + o easy: validate buffer pointer in curl_easy_recv and curl_easy_send [152] + o ECH: rework config, add test cases [220] + o escape: polish encoding and decoding for speed [37] + o escape: use hex-pair lookup in curl_easy_escape [77] + o examples: clean up crawler link parsing resources [119] + o fnmatch: replace recursive matcher with iterative greedy algorithm [72] + o formdata: reject a negative content length in curl_formadd [128] + o ftp: require TLS on the data connection for implicit FTPS [126] + o ftp: tighten use_ssl check for conn reuse [19] + o ftp: URL encode file names when wildcarding [61] + o ftplistparser: no more overwriting set.write_func [100] + o ftplistparser: when wildcarding, skip files with slashes [125] + o ftpserver.pl: fix warnings when running test 804 [257] + o getinfo: make sure CURLINFO_EFFECTIVE_URL does not contain creds [107] + o getinfo: make sure CURLINFO_REDIRECT_URL does not contain creds [94] + o getparam: switch off CONNECT headers when using -J [4] + o glob: reduce the number of allocations for URLs that don't need globbing. [109] + o global_init: tidy up, simplify, fix issues [43] + o gnutls: hardening [230] + o GnuTLS: remove our own OCSP signature verification [266] + o gtls: no early return on verify fail [149] + o h3-proxy: teardown fix [182] + o happy eyeballs: bound work from many duplicate/failing addresses [80] + o hash: change key arguments to const [14] + o hash: vary bucket placement across process runs [108] + o headers: avoid O(n^2) rescans in curl_easy_nextheader() [81] + o hsts: cap max-age to two years [47] + o hsts: on load, only remove exact duplicates [56] + o HTTP-CONNECT: do not react to 401 responses [31] + o http2: changes in error handling [53] + o http2: don't send a CURLE_SEND_ERROR after a valid http2 response [71] + o http2: fix reset after 1xx response [289] + o http2: init some callbacks for HTTP/2 push easy handles [191] + o http2: initialize URL handle for server pushes [104] + o http2: limit trailers and headers count [211] + o http2: update a refused stream's error after GOAWAY [102] + o http2: use stream in userdata [246] + o http: abide to close-rules when transfer-encoding off [132] + o http: ignore Proxy-Authenticate unless the response comes from a proxy [120] + o http: only enable Negotiate/NTLM to allowed origins [99] + o http: reject empty header names [241] + o http: reject resumed uploads [16] + o http_aws_sigv4: fix the s3express service name [273] + o http_chunks: accept exactly 17 characters chunk-size [170] + o http_chunks: limit trailers size [212] + o httpsig-key.md: correct the generating Ed25519 keys commands [97] + o httpsrr+ech: check target and port [133] + o if2ip: check interface name case sensitive [158] + o imap: close the connection on premature done instead of finalizing [197] + o imap: compare selected mailbox names case-sensitively [57] + o imap: do size checks using 64-bit math [291] + o inet_pton: reject a colon that ends an IPv6 address [179] + o IPFS.md: replace sunset link, allowlist another one to avoid `mdlinkcheck` hang [168] + o KNOWN_BUGS: drop the curl-config private details entry [290] + o KNOWN_BUGS: drop the empty error buffer entry, it no longer happens [66] + o ldap: base64-encode a non-SAFE DN and reject a control-byte type name [55] + o ldap: tidy up variable scope, merge nested `#if`s [206] + o lib557: exclude three floating point test cases for VS2013 and earlier [249] + o lib: connection shutdown, cleanup code [92] + o lib: make private functions static and remove unused code [83] + o lib: reject internal handles in the multi/easy public API [103] + o lib: replace `CURLX_FILE_MODE()` with `mode_t` cast [205] + o libssh2: match hashed known_hosts entries by host [30] + o libssh2: skip repeat checkp() for already-absent hashed key types [82] + o libssh: work with empty priv_key [143] + o mbedtls: fix ticket handling [52] + o mbedtls: pass correct length for PEM/DER key blobs [177] + o mbedtls: resend length check [140] + o mdlinkcheck: pass `--retry-max-time` to avoid server-controlled long retry times [161] + o mime: bound Curl_mime_duppart() recursion depth [164] + o mime: bound Curl_mime_prepare_headers() recursion depth [76] + o mime: do curl_mime_free() non-recursively [48] + o mprintf: acknowledge %F [300] + o mprintf: avoid scanning strings twice for unbounded %s [110] + o mprintf: make mprintf() and mfprintf() return -1 on error [279] + o mprintf: stage output and emit it in runs [38] + o mprintf: use size_t width for the bare Windows %I modifier [178] + o mqtt: drain queued output before advancing the state machine [118] + o mqtt: remaininglength check for 32-bit systems [150] + o mqttd: work around bogus VS2019 32-bit compiler warning [202] + o mulit_ev: remove socket references on forget [89] + o multi-event: fix timeout of pending transfers [111] + o multi: adjust the cleanup order in multi_done [135] + o multi: is_finished() fix [239] + o multi: reduce struct size with small cleanups [25] + o multi: remove redundant completion message list [32] + o multi: store timeout epoch as whole seconds [36] + o multi_ntfy: fix handling of more than 128 notifications in a batch [86] + o multi_ntfy: recheck callback on dispatch [228] + o multi_ntfy: replace enabled bitset with inline flags [44] + o multihandle: some easy shrinks [33] + o netrc: empty quoted values now produce a valid empty string [62] + o netrc: survive comment-only .netrc file [93] + o ngtcp2: shrink max sendbuf [13] + o ngtcp2: use stream in userdata [185] + o openldap: hand it a dup'd socket, not curl's own + o openssl/gtls: reject CRLfile with native CA store, matching rustls [27] + o openssl: fix OpenSSL v4.1 no-deprecated build [190] + o openssl: hardening [227] + o openssl: support X509 store caching when CApath is configured [264] + o openssl: use non-deprecated API for MD5/MD4 [69] + o os400: document curl_formadd_CCSID multi-chunk peculiarity [5] + o os400: restore HAVE_FCHMOD [181] + o os400: use correct free function [189] + o ossl_verifyhost: remove assumption of null-termination of ASN1_STRING. [26] + o output-dir.md: expand on its behavior [151] + o parsedate: find day names, months and time zones faster [68] + o parsedate: remove the unused leading three letters [106] + o parsedate: validate NULL date pointer in curl_getdate [213] + o peer: compare zoneid case sensitive [229] + o peer: parsing a zoneid as number fix [139] + o peer: preserve ipv6 props from "connect-to" [155] + o perf-tests: output two-digital decimals always [96] + o pingpong: add a max loop counter for Curl_pp_readresp [113] + o pingpong: guard sendleft against a send reporting more than requested [196] + o pingpong: schedule server response timeouts for the socket API [221] + o pytest: adjust threshold in test_20_11_crazy_pings for arm64 [237] + o pytest: check server status without ssl verify [154] + o pytest: fixture fixes [193] + o pytest: set `PerSourcePenalties no` when sshd supports it [169] + o pytest: silence pytest warnings [261] + o pytest: start all servers early [214] + o retry.md: clarify that the server can override retry delay time [184] + o Revert "setopt: clear the 'custom' CA booleans when set to NULL" [235] + o rtsp: accept a Content-Length body on methods that expected none [115] + o rtsp: do not send a learned session id to another origin [10] + o runtests: add extra room for mininum number of tests in sliced jobs [201] + o runtests: Added runnernums in order to have O(1) lookup [45] + o runtests: unset `CURL_MEMDEBUG` for curl requests made to the CI API [258] + o rustls: break recv loop on empty plain text [298] + o rustls: consume CURLOPT_SSLCERT_BLOB / CURLOPT_SSLKEY_BLOB [219] + o schannel: auto_client_cert conn matching [17] + o schannel: clear borrowed sslContext on close [15] + o schannel: fix `-Wextra-semi-stmt` with clang and extra debug enabled [267] + o schannel: fix compiler warning `-Wstrict-aliasing` in ALPN code [243] + o schannel: fix guard for old MSVC workaround [204] + o schannel: handle empty tokens during renegotiation [183] + o schannel: retain close_notify records across send retries [302] + o sectrust: add guards for rest of Apple OS flavors [250] + o share link: forget connection [223] + o socketpair: fix accept loop [210] + o socketpair: use `pipe2()` on Apple OSes (requires SDK 27) [262] + o socks: support CURLAUTH_ONLY in CURLOPT_SOCKS5_AUTH [124] + o socks: track SOCKS5 authentication per socket, not per connection [171] + o socksd: count connections per test, widen the method2 sentinel + o ssl: dedup ssl_fsslctx [35] + o ssls: validate input arguments in import, export, and unpack [136] + o strparse: speed up number parsing [129] + o sws: fix connection-monitor for connections closed before a request [153] + o telnet: make it work through proxy tunnels [112] + o test 1605: move from unit to libtest [49] + o test/servers.pm: restrict h2/3 port reuse [203] + o test1119, 1167: disable MSYS2 argument conversion [254] + o test1399: improve reliability [9] + o test1679: unset `SSL_CERT_FILE`, use `TESTNUMBER` [58] + o test2007: bump to use SHA256 public key hash (was: MD5) [226] + o test2007: set `MSYS2_ARG_CONV_EXCL` to avoid flaky failures [216] + o test2072: enable on Cygwin/MSYS2 [256] + o test428: skip when running on older MSYS2 versions [276] + o test: restore the old test 1609 as 3271 [162] + o testcurl.pl: drop support for building c-ares from embedded source tree [253] + o tests/http: use @classmethod when appropriate [231] + o tests: add SFTP connection reuse tests [224] + o tests: clean up Python test code [130] + o tests: fix typos in a comment [247] + o tests: require valgrind 3+ (2007-08-03) [282] + o tftp: verify direction in a few places [101] + o tool: (re)set SSL verification in --libcurl output [1] + o tool: add Unicode and long-filename support to `--create-dir` (Windows) [141] + o tool: buffer headers before remote filename selection [98] + o tool: handling of "synthetic" errors [233] + o tool_doswin: recognize superscript numbers as reserved [117] + o tool_easysrc: reset the handle between --next operations [90] + o tool_getparam: do not print the value of a redacted argument [127] + o tool_getparam: restore --socks5-gssapi-service [200] + o tool_help: guard category[2] access when category is bare "-" [59] + o tool_ipfs: keep the path percent-encoded when rewriting to the gateway [138] + o tool_operate: clear the retry flag when the output file fails [122] + o tool_util: add Unicode support to `tool_execpath()` [142] + o u8_strset, u32_ptrset: fix boundaries handling [131] + o uint-bset: skip unused slots when counting + o uint-table: add remove-max precaution [165] + o unit1399: check microsecond precision across redirects [225] + o unit1664: add workaround for VS2019 16.3 - VS2022 17.4 x64 miscompile [272] + o url: check syntax before applying default-protocol [147] + o url: CURLOPT_INTERFACE fix connection reuse match [186] + o url: match connections without force reuse and candidates [95] + o url: ntlm/negotiate, prohibit connection reuse on follows [18] + o urlapi: make curl_url_dup() copy the guessed_scheme flag [166] + o urlapi: normalize legacy numeric IPv4 hosts set via CURLUPART_HOST [180] + o urlapi: return NULL from curl_url_dup() when given a NULL handle [160] + o urlapi: run the urlparser perf test faster [54] + o urldata: remove conn->given [40] + o urldata: remove ssl_primary_config from easy handle [78] + o urldata: shrink connectdata negotiate states [207] + o vdns: announce resolver start before the HTTPS RR query is sent [173] + o vquic: fix preprocessor check for old Darwin versions [8] + o vquic: use `CURLX_FILE_MODE()` in `Curl_qlogdir()` [238] + o vssh: do not busy-loop in blocking state machine without a timeout [121] + o vtls: fix Curl_cert_hostcheck when the pattern is not a C string [12] + o vtls_scache: remember lock state locally [297] + o warnless: dedupe Intel compiler warning suppression pragmas [42] + o wcurl: import v2026.10.12 [236] + o websocket: auto PONG with client reader fix [215] + o websocket: error on EOS with incomplete frame [244] + o wolfssl: remove BIO destroy callback [157] + o ws: check pointer args in curl_ws_recv [192] + o ws: do not auto-pong in raw mode [123] + o x509: stricter parsing [2] This release includes the following known bugs: @@ -331,333 +310,319 @@ For all changes ever done in curl: Planned upcoming removals include: + o Common Name support in TLS certificates for OpenSSL o HTTP/2 Server Push o local crypto implementations o NTLM - o SMB See https://curl.se/dev/deprecate.html This release would not have looked like this without help, code, reports and advice from friends like these: - 11soda11, 1rhino2 on hackerone, accl on hackerone, AlanKingPL, Alb3e3, - Alexey Samsonov, Alhuda Khan, Anton Karpov, anupamme, Arham Wani, - Athos Ribeiro, Bartel Sielski, Bigtang on hackerone, Bill Mill, - Bryan Henderson, Carlos Henrique Lima Melara, CatboxParadox, - Christian Ullrich, Christoph Reiter, claudex on github, Collin Funk, - cybertron10 on github, Dan Fandrich, Daniel Gustafsson, Daniel Stenberg, - dependabot[bot], ed0d2b2ce19451f2 on github, Emmanuel Ugwu, Eunsoo Kim, - felix h, firexinghe on github, Fred Klassen, GLaDOS-418 on github, - Graham Campbell, Hendrik Hübner, HwangRock, Igor Morgenstern, itzTanos29, - Jace Laquerre, Jayanth Vennamreddy, Joel Depooter, Johannes Schindelin, - John Verne, Keng-Yu Lin, Kieran Gannon, kit-ty-kate on github, - Laurent Sabourin, Manuel Sánchez-Guijarro, Marcel Jamin, marco-jardim, - Martin Dukek, martin-fzi on github, Matthew John Cheetham, Max Dymond, - Memduh Çelik, Patrick Monnerat, Pavel Sobolev, pszemus on github, - Ralf Mueller, Ramesh Adhikari, Rarylson Freitas, Ray Satiro, renovate[bot], - Rito Rhymes, RMMoreton on github, Roger Leigh, Ron Kuper, Ross Burton, - Sameeh Jubran, Sam James, Samuel Dainard, Samuel Henrique, Scott Talbert, - sdgh179 on github, Sergei Zimmerman, smaeljaish on hackerone, Stanislav Fort, - Stefan Eissing, Stephan Zeisberg, stze on hackerone, Thomas Chauchefoin, - Viktor Szakats, xmoezzz on github, Yoshiro Yoneya, zaveshaa - (85 contributors) + 1rhino2 on hackerone, Adam Shirt, Adrien Rey-Jarthon, Alexander Köplinger, + Andreas Westin, Anthropic, Artem Prilutskiy, aschank on github, + Aviv Engelberg, Axel Mierczuk, Bobbeh Rhino, bupt-Yy-young on github, + Charles Muehlberger, Christian Hesse, Christian Ullrich, Dan Fandrich, + Daniel Lang, Daniel McCarney, Daniel S. Roche, Daniel Stenberg, Daniel Wade, + dataisland, David Benjamin, David Bures, dependabot[bot], + Fengxiaoxx on github, Fernando Gallardo, Filippo Tedeschi, Florent Castelli, + Fredrik Blau, GPT-6.1 Sol, Hasan Flayyih Abdullah, Huang Yiheng, + Ilias Aberkane, İlyas Serdar Dursun, jared-m-cohen-civ on github, jdymitarai, + Johannes Schindelin, Jorge Rocamora, Joshua Rogers, JuanCarlosgg on github, + KayanoLiam on github, Kuroda Kayn, ldm0, Luna Tong, Maksim Hayder, + Marcel Raad, Martin Dukek, Max Dymond, Mihai-Gabriel Marin, minnnjuuu, + Mohammed K. Fathy, Muhamad Arga Reksapati, Nicolas Grekas, ns-ylambert, + Paolo Ganci, Patrick Monnerat, rawsun007, Ray Satiro, Rémi NICOLE, + Renan Pimentel, renovate[bot], Rhino, Richard Payne, Ron Kuper, + Roshan Ramani, Rudolf Polzer, Samuel Henrique, Sebastian Raase, Skye Soss, + Stanislav Fort, Stefan Eissing, Stephen Psaradellis, szedenik-adam on github, + tawmoto on github, Thanniru Sai Teja, Tip ten Brink, Tyler Yankee, Un1q32, + Viktor Szakats, Wonyoung Jung, xhon-pelushi, Yechan Bae, yuwk + (84 contributors) References to bug reports and discussions on issues: - [1] = https://curl.se/bug/?i=22116 - [2] = https://curl.se/bug/?i=22158 - [3] = https://curl.se/bug/?i=22152 - [4] = https://curl.se/bug/?i=22235 - [5] = https://curl.se/bug/?i=22190 - [6] = https://curl.se/bug/?i=22195 - [7] = https://curl.se/bug/?i=22198 - [8] = https://curl.se/bug/?i=22197 - [9] = https://curl.se/bug/?i=22194 - [10] = https://curl.se/bug/?i=22191 - [11] = https://curl.se/bug/?i=22193 - [12] = https://curl.se/bug/?i=22192 - [13] = https://curl.se/bug/?i=22344 - [14] = https://curl.se/bug/?i=22292 - [15] = https://curl.se/bug/?i=22187 - [16] = https://curl.se/bug/?i=22302 - [17] = https://curl.se/bug/?i=22178 - [18] = https://curl.se/bug/?i=22308 - [19] = https://curl.se/bug/?i=22233 - [20] = https://curl.se/bug/?i=22169 - [21] = https://curl.se/bug/?i=22307 - [22] = https://curl.se/bug/?i=22179 - [23] = https://curl.se/bug/?i=22182 - [24] = https://curl.se/bug/?i=22232 - [25] = https://curl.se/bug/?i=22231 - [26] = https://curl.se/bug/?i=22301 - [27] = https://curl.se/bug/?i=22222 - [28] = https://curl.se/bug/?i=22220 - [29] = https://curl.se/bug/?i=22113 - [30] = https://curl.se/bug/?i=22247 - [31] = https://curl.se/bug/?i=22167 - [32] = https://curl.se/bug/?i=22100 - [33] = https://curl.se/bug/?i=22154 - [34] = https://curl.se/bug/?i=22150 - [35] = https://curl.se/bug/?i=22148 - [36] = https://curl.se/bug/?i=22218 - [37] = https://curl.se/bug/?i=22119 - [38] = https://curl.se/bug/?i=22165 - [39] = https://curl.se/bug/?i=22164 - [40] = https://curl.se/bug/?i=22136 - [41] = https://curl.se/bug/?i=22265 - [42] = https://curl.se/bug/?i=22300 - [43] = https://curl.se/bug/?i=22112 - [44] = https://curl.se/bug/?i=22217 - [45] = https://curl.se/bug/?i=22213 - [46] = https://curl.se/bug/?i=22156 - [47] = https://curl.se/bug/?i=22219 - [48] = https://curl.se/bug/?i=22211 - [49] = https://curl.se/bug/?i=22214 - [50] = https://curl.se/bug/?i=22212 - [51] = https://curl.se/bug/?i=22206 - [52] = https://curl.se/bug/?i=22205 - [53] = https://curl.se/bug/?i=22296 - [54] = https://curl.se/bug/?i=22201 - [55] = https://curl.se/bug/?i=22273 - [56] = https://curl.se/bug/?i=22170 - [57] = https://curl.se/bug/?i=22200 - [58] = https://curl.se/bug/?i=22280 - [59] = https://curl.se/bug/?i=22277 - [60] = https://curl.se/bug/?i=22294 - [61] = https://curl.se/bug/?i=22298 - [62] = https://curl.se/bug/?i=22303 - [63] = https://curl.se/bug/?i=22286 - [64] = https://curl.se/bug/?i=22237 - [65] = https://curl.se/bug/?i=22487 - [66] = https://curl.se/bug/?i=22388 - [67] = https://curl.se/bug/?i=22340 - [68] = https://curl.se/bug/?i=22253 - [69] = https://curl.se/bug/?i=22336 - [70] = https://curl.se/bug/?i=22236 - [71] = https://curl.se/bug/?i=21965 - [72] = https://curl.se/bug/?i=22052 - [73] = https://curl.se/bug/?i=22257 - [74] = https://curl.se/bug/?i=22256 - [75] = https://curl.se/bug/?i=22255 - [76] = https://curl.se/bug/?i=22254 - [77] = https://curl.se/bug/?i=22250 - [78] = https://curl.se/bug/?i=22338 - [79] = https://curl.se/bug/?i=22251 - [80] = https://curl.se/bug/?i=22225 - [81] = https://curl.se/bug/?i=22488 - [82] = https://curl.se/bug/?i=22248 - [83] = https://curl.se/bug/?i=22243 - [84] = https://curl.se/bug/?i=22245 - [85] = https://curl.se/bug/?i=22431 - [86] = https://curl.se/bug/?i=22272 - [87] = https://curl.se/bug/?i=22399 - [88] = https://curl.se/bug/?i=22210 - [89] = https://curl.se/bug/?i=22389 - [90] = https://curl.se/bug/?i=22346 - [91] = https://curl.se/bug/?i=22282 - [92] = https://curl.se/bug/?i=22279 - [93] = https://curl.se/bug/?i=22316 - [94] = https://curl.se/bug/?i=22433 - [95] = https://curl.se/bug/?i=22319 - [96] = https://curl.se/bug/?i=22323 - [97] = https://curl.se/bug/?i=22318 - [98] = https://curl.se/bug/?i=22317 - [99] = https://curl.se/bug/?i=22576 - [100] = https://curl.se/bug/?i=22331 - [101] = https://curl.se/bug/?i=22333 - [102] = https://curl.se/bug/?i=22334 - [103] = https://curl.se/bug/?i=22326 - [104] = https://curl.se/bug/?i=22393 - [105] = https://curl.se/bug/?i=22320 - [106] = https://curl.se/bug/?i=22162 - [107] = https://curl.se/bug/?i=22299 - [108] = https://curl.se/bug/?i=22386 - [109] = https://curl.se/bug/?i=22312 - [110] = https://curl.se/bug/?i=22374 - [111] = https://curl.se/bug/?i=22387 - [112] = https://curl.se/bug/?i=22379 - [113] = https://curl.se/bug/?i=22372 - [114] = https://curl.se/bug/?i=22648 - [115] = https://curl.se/bug/?i=22276 - [116] = https://curl.se/bug/?i=22382 - [117] = https://curl.se/bug/?i=22305 - [118] = https://curl.se/bug/?i=22330 - [119] = https://curl.se/bug/?i=22371 - [120] = https://curl.se/bug/?i=22380 - [121] = https://curl.se/bug/?i=21637 - [122] = https://curl.se/bug/?i=22377 - [123] = https://curl.se/bug/?i=22376 - [124] = https://curl.se/bug/?i=22375 - [125] = https://curl.se/bug/?i=21637 - [126] = https://curl.se/bug/?i=22363 - [127] = https://curl.se/bug/?i=22430 - [128] = https://curl.se/bug/?i=22369 - [129] = https://curl.se/bug/?i=22354 - [130] = https://curl.se/bug/?i=21467 - [131] = https://curl.se/bug/?i=22037 - [132] = https://curl.se/bug/?i=22418 - [133] = https://curl.se/bug/?i=22512 - [134] = https://curl.se/bug/?i=22515 - [135] = https://curl.se/bug/?i=22413 - [136] = https://curl.se/bug/?i=22603 - [137] = https://curl.se/bug/?i=22341 - [138] = https://curl.se/bug/?i=22569 - [139] = https://curl.se/bug/?i=22500 - [140] = https://curl.se/bug/?i=22568 - [141] = https://curl.se/bug/?i=22422 - [142] = https://curl.se/bug/?i=22421 - [143] = https://curl.se/bug/?i=22420 - [144] = https://curl.se/bug/?i=22415 - [145] = https://curl.se/bug/?i=22419 - [146] = https://curl.se/bug/?i=22471 - [147] = https://curl.se/bug/?i=22468 - [148] = https://curl.se/bug/?i=22337 - [149] = https://curl.se/bug/?i=22414 - [150] = https://curl.se/bug/?i=22467 - [151] = https://curl.se/bug/?i=21315 - [152] = https://curl.se/bug/?i=21806 - [153] = https://curl.se/bug/?i=22409 - [154] = https://curl.se/bug/?i=22270 - [155] = https://curl.se/bug/?i=22566 - [156] = https://curl.se/bug/?i=22401 - [157] = https://curl.se/bug/?i=22463 - [158] = https://curl.se/bug/?i=22397 - [159] = https://curl.se/bug/?i=22400 - [160] = https://curl.se/bug/?i=22460 - [161] = https://curl.se/bug/?i=22505 - [162] = https://curl.se/bug/?i=22502 - [163] = https://curl.se/bug/?i=22565 - [164] = https://curl.se/bug/?i=22494 - [165] = https://curl.se/bug/?i=22564 - [166] = https://curl.se/bug/?i=22448 - [167] = https://curl.se/bug/?i=22449 - [168] = https://curl.se/bug/?i=22530 - [169] = https://curl.se/bug/?i=22447 - [170] = https://curl.se/bug/?i=22445 - [171] = https://curl.se/bug/?i=22444 - [172] = https://curl.se/bug/?i=22443 - [173] = https://curl.se/bug/?i=22647 - [174] = https://curl.se/bug/?i=22490 - [175] = https://curl.se/bug/?i=22604 - [176] = https://curl.se/bug/?i=22528 - [177] = https://curl.se/bug/?i=22678 - [178] = https://curl.se/bug/?i=22598 - [179] = https://curl.se/bug/?i=22547 - [180] = https://curl.se/bug/?i=22560 - [181] = https://curl.se/bug/?i=22555 - [182] = https://curl.se/bug/?i=22557 - [183] = https://curl.se/bug/?i=22642 - [184] = https://curl.se/bug/?i=22594 - [185] = https://curl.se/bug/?i=22593 - [186] = https://curl.se/bug/?i=22588 - [187] = https://curl.se/bug/?i=22552 - [188] = https://curl.se/bug/?i=22548 - [189] = https://curl.se/bug/?i=22541 - [190] = https://curl.se/bug/?i=22545 - [191] = https://curl.se/bug/?i=22544 - [192] = https://curl.se/bug/?i=22638 - [193] = https://curl.se/bug/?i=22595 - [194] = https://curl.se/bug/?i=22536 - [195] = https://curl.se/bug/?i=22540 - [196] = https://curl.se/bug/?i=22524 - [197] = https://curl.se/bug/?i=22473 - [198] = https://curl.se/bug/?i=22644 - [199] = https://curl.se/bug/?i=22591 - [200] = https://curl.se/bug/?i=22532 - [201] = https://curl.se/bug/?i=22636 - [202] = https://curl.se/bug/?i=22484 - [203] = https://curl.se/bug/?i=22514 - [204] = https://curl.se/bug/?i=22627 - [205] = https://curl.se/bug/?i=22577 - [206] = https://curl.se/bug/?i=22587 - [207] = https://curl.se/bug/?i=22672 - [208] = https://curl.se/bug/?i=22475 - [209] = https://curl.se/bug/?i=22584 - [210] = https://curl.se/bug/?i=22519 - [211] = https://curl.se/bug/?i=22668 - [212] = https://curl.se/bug/?i=22585 - [213] = https://curl.se/bug/?i=22567 - [214] = https://curl.se/bug/?i=22665 - [215] = https://curl.se/bug/?i=22628 - [216] = https://curl.se/bug/?i=22579 - [217] = https://curl.se/bug/?i=22631 - [218] = https://curl.se/bug/?i=22339 - [219] = https://curl.se/bug/?i=20521 - [220] = https://curl.se/bug/?i=22623 - [221] = https://curl.se/bug/?i=22629 - [222] = https://curl.se/bug/?i=22721 - [223] = https://curl.se/bug/?i=22610 - [224] = https://curl.se/bug/?i=22621 - [225] = https://curl.se/bug/?i=22649 - [226] = https://curl.se/bug/?i=22660 - [227] = https://curl.se/bug/?i=22675 - [228] = https://curl.se/bug/?i=22664 - [229] = https://curl.se/bug/?i=22662 - [230] = https://curl.se/bug/?i=22466 - [231] = https://curl.se/bug/?i=22613 - [232] = https://curl.se/bug/?i=22612 - [233] = https://curl.se/bug/?i=22611 - [234] = https://curl.se/bug/?i=22550 - [235] = https://curl.se/bug/?i=22587 - [236] = https://curl.se/bug/?i=22607 - [237] = https://curl.se/bug/?i=22229 - [238] = https://curl.se/bug/?i=22383 - [239] = https://curl.se/bug/?i=22658 - [240] = https://curl.se/bug/?i=22656 - [241] = https://curl.se/bug/?i=22695 - [242] = https://curl.se/bug/?i=22717 - [243] = https://curl.se/bug/?i=22651 - [244] = https://curl.se/bug/?i=22718 - [245] = https://curl.se/bug/?i=22719 - [246] = https://curl.se/bug/?i=22715 - [247] = https://curl.se/bug/?i=22713 - [248] = https://curl.se/bug/?i=22692 - [249] = https://curl.se/bug/?i=22710 - [250] = https://curl.se/bug/?i=22706 - [251] = https://curl.se/bug/?i=22708 - [252] = https://curl.se/bug/?i=22690 - [253] = https://curl.se/bug/?i=22774 - [254] = https://curl.se/bug/?i=22709 - [255] = https://curl.se/bug/?i=22705 - [256] = https://curl.se/bug/?i=22702 - [257] = https://curl.se/bug/?i=22680 - [258] = https://curl.se/bug/?i=22699 - [259] = https://curl.se/bug/?i=22700 - [260] = https://curl.se/bug/?i=22698 - [261] = https://curl.se/bug/?i=22671 - [262] = https://curl.se/bug/?i=22696 - [263] = https://curl.se/bug/?i=22694 - [264] = https://curl.se/bug/?i=22683 - [265] = https://curl.se/bug/?i=22689 - [268] = https://curl.se/bug/?i=22687 - [269] = https://curl.se/bug/?i=22643 - [270] = https://curl.se/bug/?i=22681 - [271] = https://curl.se/bug/?i=22768 - [272] = https://curl.se/bug/?i=22776 - [273] = https://curl.se/bug/?i=22685 - [274] = https://curl.se/bug/?i=22682 - [275] = https://curl.se/bug/?i=22586 - [276] = https://curl.se/bug/?i=22669 - [277] = https://curl.se/bug/?i=22667 - [278] = https://curl.se/bug/?i=22679 - [279] = https://curl.se/bug/?i=22758 - [280] = https://curl.se/bug/?i=22767 - [281] = https://curl.se/bug/?i=22763 - [282] = https://curl.se/bug/?i=22739 - [284] = https://curl.se/bug/?i=22754 - [286] = https://curl.se/bug/?i=22761 - [288] = https://curl.se/bug/?i=22714 - [289] = https://curl.se/bug/?i=22759 - [290] = https://curl.se/bug/?i=22757 - [291] = https://curl.se/bug/?i=22751 - [292] = https://curl.se/bug/?i=22750 - [295] = https://curl.se/bug/?i=22739 - [296] = https://curl.se/bug/?i=22738 - [297] = https://curl.se/bug/?i=22737 - [301] = https://curl.se/bug/?i=22726 - [304] = https://curl.se/bug/?i=22730 - [305] = https://curl.se/bug/?i=22686 - [306] = https://curl.se/bug/?i=22725 - [307] = https://curl.se/bug/?i=22716 - [308] = https://curl.se/bug/?i=22635 + [1] = https://curl.se/bug/?i=22701 + [2] = https://curl.se/bug/?i=22799 + [3] = https://curl.se/bug/?i=22811 + [4] = https://curl.se/bug/?i=22838 + [5] = https://curl.se/bug/?i=22887 + [6] = https://curl.se/bug/?i=22840 + [7] = https://curl.se/bug/?i=22788 + [8] = https://curl.se/bug/?i=22797 + [9] = https://curl.se/bug/?i=22798 + [10] = https://curl.se/bug/?i=22920 + [11] = https://curl.se/bug/?i=22791 + [12] = https://curl.se/bug/?i=22795 + [13] = https://curl.se/bug/?i=22786 + [14] = https://curl.se/bug/?i=22886 + [15] = https://curl.se/bug/?i=22720 + [16] = https://curl.se/bug/?i=22703 + [17] = https://curl.se/bug/?i=22827 + [18] = https://curl.se/bug/?i=22884 + [19] = https://curl.se/bug/?i=22823 + [20] = https://curl.se/bug/?i=22831 + [21] = https://curl.se/bug/?i=22787 + [22] = https://curl.se/bug/?i=22830 + [23] = https://curl.se/bug/?i=22740 + [24] = https://curl.se/bug/?i=22828 + [25] = https://curl.se/bug/?i=22807 + [26] = https://curl.se/bug/?i=22822 + [27] = https://curl.se/bug/?i=22826 + [28] = https://curl.se/bug/?i=22825 + [29] = https://curl.se/bug/?i=22926 + [30] = https://curl.se/bug/?i=22874 + [31] = https://curl.se/bug/?i=22817 + [32] = https://curl.se/bug/?i=22805 + [33] = https://curl.se/bug/?i=22801 + [34] = https://curl.se/bug/?i=22815 + [35] = https://curl.se/bug/?i=22876 + [36] = https://curl.se/bug/?i=22806 + [37] = https://curl.se/bug/?i=22780 + [38] = https://curl.se/bug/?i=22766 + [39] = https://curl.se/bug/?i=22793 + [40] = https://curl.se/bug/?i=22875 + [41] = https://curl.se/bug/?i=22810 + [42] = https://curl.se/bug/?i=22796 + [43] = https://curl.se/bug/?i=22746 + [44] = https://curl.se/bug/?i=22804 + [45] = https://curl.se/bug/?i=22753 + [46] = https://curl.se/bug/?i=22784 + [47] = https://curl.se/bug/?i=22878 + [48] = https://curl.se/bug/?i=22927 + [49] = https://curl.se/bug/?i=22928 + [50] = https://curl.se/bug/?i=22873 + [51] = https://curl.se/bug/?i=22877 + [52] = https://curl.se/bug/?i=22871 + [53] = https://curl.se/bug/?i=22870 + [54] = https://curl.se/bug/?i=22924 + [55] = https://curl.se/bug/?i=22945 + [56] = https://curl.se/bug/?i=22869 + [57] = https://curl.se/bug/?i=22868 + [58] = https://curl.se/bug/?i=22866 + [59] = https://curl.se/bug/?i=22957 + [60] = https://curl.se/bug/?i=22862 + [61] = https://curl.se/bug/?i=22860 + [62] = https://issues.oss-fuzz.com/issues/560352522 + [63] = https://curl.se/bug/?i=22652 + [64] = https://curl.se/bug/?i=22819 + [65] = https://curl.se/bug/?i=22849 + [66] = https://curl.se/bug/?i=544 + [67] = https://curl.se/bug/?i=22839 + [68] = https://curl.se/bug/?i=22981 + [69] = https://curl.se/bug/?i=22221 + [70] = https://curl.se/bug/?i=22843 + [71] = https://curl.se/bug/?i=22984 + [72] = https://curl.se/bug/?i=22911 + [73] = https://curl.se/bug/?i=22910 + [74] = https://curl.se/bug/?i=22896 + [75] = https://curl.se/bug/?i=22900 + [76] = https://curl.se/bug/?i=22971 + [77] = https://curl.se/bug/?i=23021 + [78] = https://curl.se/bug/?i=22901 + [79] = https://curl.se/bug/?i=22902 + [80] = https://curl.se/bug/?i=22897 + [81] = https://curl.se/bug/?i=22898 + [82] = https://curl.se/bug/?i=22905 + [83] = https://curl.se/bug/?i=22906 + [84] = https://curl.se/bug/?i=22998 + [85] = https://curl.se/bug/?i=23018 + [86] = https://curl.se/bug/?i=22963 + [87] = https://curl.se/bug/?i=22899 + [88] = https://curl.se/bug/?i=22903 + [89] = https://curl.se/bug/?i=22895 + [90] = https://curl.se/bug/?i=22890 + [91] = https://curl.se/bug/?i=22893 + [92] = https://curl.se/bug/?i=22888 + [93] = https://curl.se/bug/?i=22889 + [94] = https://curl.se/bug/?i=23020 + [95] = https://curl.se/bug/?i=22857 + [96] = https://curl.se/bug/?i=22982 + [97] = https://curl.se/bug/?i=22961 + [98] = https://curl.se/bug/?i=22977 + [99] = https://curl.se/bug/?i=23012 + [100] = https://curl.se/bug/?i=22969 + [101] = https://curl.se/bug/?i=22974 + [102] = https://curl.se/bug/?i=22923 + [103] = https://curl.se/bug/?i=22917 + [104] = https://curl.se/bug/?i=22970 + [105] = https://curl.se/bug/?i=22964 + [106] = https://curl.se/bug/?i=23013 + [107] = https://curl.se/bug/?i=23016 + [108] = https://curl.se/bug/?i=22916 + [109] = https://curl.se/bug/?i=22947 + [110] = https://curl.se/bug/?i=23019 + [111] = https://curl.se/bug/?i=22942 + [112] = https://curl.se/bug/?i=22941 + [113] = https://curl.se/bug/?i=22932 + [114] = https://curl.se/bug/?i=23017 + [115] = https://curl.se/bug/?i=12414 + [116] = https://curl.se/bug/?i=23015 + [117] = https://curl.se/bug/?i=23011 + [118] = https://issues.oss-fuzz.com/issues/563679840, https://issues.oss-fuzz.com/issues/559825608 + [119] = https://curl.se/bug/?i=23008 + [120] = https://curl.se/bug/?i=23002 + [121] = https://curl.se/bug/?i=23005 + [122] = https://curl.se/bug/?i=23001 + [123] = https://curl.se/bug/?i=23040 + [124] = https://curl.se/bug/?i=23003 + [125] = https://curl.se/bug/?i=23089 + [126] = https://curl.se/bug/?i=22995 + [127] = https://curl.se/bug/?i=22997 + [128] = https://curl.se/bug/?i=22996 + [129] = https://curl.se/bug/?i=22993 + [130] = https://curl.se/bug/?i=23290 + [131] = https://curl.se/bug/?i=22991 + [132] = https://curl.se/bug/?i=22950 + [133] = https://curl.se/bug/?i=23086 + [134] = https://curl.se/bug/?i=23085 + [135] = https://curl.se/bug/?i=23370 + [136] = https://curl.se/bug/?i=23062 + [137] = https://curl.se/bug/?i=22968 + [138] = https://curl.se/bug/?i=22944 + [139] = https://curl.se/bug/?i=23060 + [140] = https://curl.se/bug/?i=23083 + [141] = https://curl.se/bug/?i=23289 + [142] = https://curl.se/bug/?i=23287 + [143] = https://curl.se/bug/?i=23080 + [144] = https://curl.se/bug/?i=23078 + [145] = https://curl.se/bug/?i=23068 + [146] = https://curl.se/bug/?i=23069 + [147] = https://curl.se/bug/?i=23273 + [148] = https://curl.se/bug/?i=23283 + [149] = https://curl.se/bug/?i=23061 + [150] = https://curl.se/bug/?i=23071 + [151] = https://curl.se/bug/?i=23067 + [152] = https://curl.se/bug/?i=23063 + [153] = https://curl.se/bug/?i=23072 + [154] = https://curl.se/bug/?i=23064 + [155] = https://curl.se/bug/?i=23055 + [156] = https://curl.se/bug/?i=23234 + [157] = https://curl.se/bug/?i=23058 + [158] = https://curl.se/bug/?i=23056 + [159] = https://curl.se/bug/?i=23054 + [160] = https://curl.se/bug/?i=23051 + [161] = https://curl.se/bug/?i=23245 + [162] = https://curl.se/bug/?i=23052 + [163] = https://curl.se/bug/?i=23006 + [164] = https://curl.se/bug/?i=23023 + [165] = https://curl.se/bug/?i=23034 + [166] = https://curl.se/bug/?i=23043 + [167] = https://curl.se/bug/?i=23045 + [168] = https://curl.se/bug/?i=23245 + [169] = https://curl.se/bug/?i=23259 + [170] = https://curl.se/bug/?i=23334 + [171] = https://curl.se/bug/?i=23014 + [172] = https://curl.se/bug/?i=23280 + [173] = https://curl.se/bug/?i=23037 + [174] = https://curl.se/bug/?i=23038 + [175] = https://curl.se/bug/?i=23035 + [176] = https://curl.se/bug/?i=23261 + [177] = https://curl.se/bug/?i=23274 + [178] = https://curl.se/bug/?i=23030 + [179] = https://curl.se/bug/?i=23028 + [180] = https://curl.se/bug/?i=23027 + [181] = https://curl.se/bug/?i=23026 + [182] = https://curl.se/bug/?i=23327 + [183] = https://github.com/git-for-windows/git/issues/6457 + [184] = https://curl.se/bug/?i=23249 + [185] = https://curl.se/bug/?i=23267 + [186] = https://curl.se/bug/?i=23326 + [187] = https://curl.se/bug/?i=23265 + [188] = https://curl.se/bug/?i=23303 + [189] = https://curl.se/bug/?i=23216 + [190] = https://curl.se/bug/?i=23215 + [191] = https://curl.se/bug/?i=23183 + [192] = https://curl.se/bug/?i=23050 + [193] = https://curl.se/bug/?i=23313 + [194] = https://curl.se/bug/?i=23324 + [195] = https://curl.se/bug/?i=23318 + [196] = https://curl.se/bug/?i=23323 + [197] = https://curl.se/bug/?i=23322 + [198] = https://curl.se/bug/?i=23210 + [199] = https://curl.se/bug/?i=23220 + [200] = https://curl.se/bug/?i=23344 + [201] = https://curl.se/bug/?i=23211 + [202] = https://curl.se/bug/?i=23317 + [203] = https://curl.se/bug/?i=23208 + [204] = https://curl.se/bug/?i=23315 + [205] = https://curl.se/bug/?i=23181 + [206] = https://curl.se/bug/?i=23314 + [207] = https://curl.se/bug/?i=23192 + [208] = https://curl.se/bug/?i=23189 + [209] = https://curl.se/bug/?i=23169 + [210] = https://curl.se/bug/?i=23166 + [211] = https://curl.se/bug/?i=23165 + [212] = https://curl.se/bug/?i=23162 + [213] = https://curl.se/bug/?i=23156 + [214] = https://curl.se/bug/?i=23310 + [215] = https://curl.se/bug/?i=23296 + [216] = https://curl.se/bug/?i=23146 + [217] = https://curl.se/bug/?i=23199 + [218] = https://curl.se/bug/?i=23197 + [219] = https://curl.se/bug/?i=23196 + [220] = https://curl.se/bug/?i=23136 + [221] = https://curl.se/bug/?i=23025 + [222] = https://curl.se/bug/?i=23238 + [223] = https://hackerone.com/reports/3971585 + [224] = https://curl.se/bug/?i=23254 + [225] = https://curl.se/bug/?i=23258 + [226] = https://curl.se/bug/?i=23184 + [227] = https://curl.se/bug/?i=23299 + [228] = https://curl.se/bug/?i=23167 + [229] = https://curl.se/bug/?i=23164 + [230] = https://curl.se/bug/?i=23297 + [231] = https://curl.se/bug/?i=23148 + [232] = https://curl.se/bug/?i=23179 + [233] = https://curl.se/bug/?i=23300 + [234] = https://curl.se/bug/?i=23302 + [235] = https://curl.se/bug/?i=22614 + [236] = https://curl.se/bug/?i=23382 + [237] = https://curl.se/bug/?i=23298 + [238] = https://curl.se/bug/?i=23171 + [239] = https://curl.se/bug/?i=23295 + [240] = https://curl.se/bug/?i=23024 + [241] = https://curl.se/bug/?i=23292 + [242] = https://curl.se/bug/?i=23212 + [243] = https://curl.se/bug/?i=23163 + [244] = https://curl.se/bug/?i=23188 + [245] = https://curl.se/bug/?i=23157 + [246] = https://curl.se/bug/?i=23262 + [247] = https://curl.se/bug/?i=23153 + [248] = https://curl.se/bug/?i=23288 + [249] = https://curl.se/bug/?i=23377 + [250] = https://curl.se/bug/?i=23149 + [251] = https://curl.se/bug/?i=23150 + [252] = https://curl.se/bug/?i=23147 + [253] = https://curl.se/bug/?i=23374 + [254] = https://curl.se/bug/?i=23339 + [256] = https://curl.se/bug/?i=23142 + [257] = https://curl.se/bug/?i=23140 + [258] = https://curl.se/bug/?i=23306 + [259] = https://curl.se/bug/?i=23139 + [260] = https://curl.se/bug/?i=23138 + [261] = https://curl.se/bug/?i=23135 + [262] = https://curl.se/bug/?i=23126 + [263] = https://curl.se/bug/?i=23133 + [264] = https://curl.se/bug/?i=23363 + [266] = https://curl.se/bug/?i=23312 + [267] = https://curl.se/bug/?i=23129 + [271] = https://curl.se/bug/?i=23122 + [272] = https://curl.se/bug/?i=23306 + [273] = https://curl.se/bug/?i=23120 + [275] = https://curl.se/bug/?i=23084 + [276] = https://curl.se/bug/?i=23345 + [279] = https://curl.se/bug/?i=23112 + [282] = https://curl.se/bug/?i=23111 + [289] = https://curl.se/bug/?i=23104 + [290] = https://curl.se/bug/?i=23102 + [291] = https://curl.se/bug/?i=23100 + [292] = https://curl.se/bug/?i=23099 + [296] = https://curl.se/bug/?i=23094 + [297] = https://curl.se/bug/?i=23091 + [298] = https://curl.se/bug/?i=23092 + [299] = https://curl.se/bug/?i=23090 + [300] = https://curl.se/bug/?i=22719 + [301] = https://curl.se/bug/?i=23350 + [302] = https://curl.se/bug/?i=23368 diff --git a/REUSE.toml b/REUSE.toml index 9e6d2d166b44..6cd00845a7a9 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -14,6 +14,7 @@ SPDX-PackageDownloadLocation = "https://curl.se/" [[annotations]] path = [ ".github/pull_request_template.md", + ".github/renovate.json", "docs/INSTALL", "docs/libcurl/symbols-in-versions", "docs/options-in-versions", @@ -29,7 +30,6 @@ path = [ "projects/Windows/**", "README", "RELEASE-NOTES", - "renovate.json", "tests/certs/**", "tests/data/data**", "tests/data/test**", diff --git a/acinclude.m4 b/acinclude.m4 index 310d680600c3..bb4ac660d96d 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -1518,7 +1518,7 @@ AC_DEFUN([CURL_SUPPORTS_BUILTIN_AVAILABLE], [ AC_COMPILE_IFELSE([ AC_LANG_PROGRAM([[ ]],[[ - if(__builtin_available(macOS 10.12, iOS 5.0, *)) {} + if(__builtin_available(macOS 10.12, iOS 5, *)) {} ]]) ],[ AC_MSG_RESULT([yes]) diff --git a/appveyor.sh b/appveyor.sh index 64e8b6d13182..a22d17bf7401 100644 --- a/appveyor.sh +++ b/appveyor.sh @@ -40,11 +40,11 @@ if [ -n "${CMAKE_GENERATOR:-}" ]; then esac if [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2026' ]; then - openssl_root_win="C:/OpenSSL-v36${openssl_suffix}" + openssl_root_win="C:/OpenSSL-v36${openssl_suffix}" # manually bumped elif [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2022' ]; then - openssl_root_win="C:/OpenSSL-v35${openssl_suffix}" + openssl_root_win="C:/OpenSSL-v35${openssl_suffix}" # manually bumped elif [ "${APPVEYOR_BUILD_WORKER_IMAGE}" = 'Visual Studio 2019' ]; then - openssl_root_win="C:/OpenSSL-v30${openssl_suffix}" + openssl_root_win="C:/OpenSSL-v30${openssl_suffix}" # manually bumped fi [ -n "${openssl_root_win:-}" ] && openssl_root="$(cygpath "${openssl_root_win}")" @@ -73,7 +73,6 @@ if [ -n "${CMAKE_GENERATOR:-}" ]; then [[ "${CMAKE_GENERATE:-}" = *'-DCURL_USE_OPENSSL=ON'* ]] && options+=" -DOPENSSL_ROOT_DIR=${openssl_root_win}" # shellcheck disable=SC2086 time cmake -B "_bld${_chkprefill}" \ - -DENABLE_DEBUG=ON \ -DCMAKE_UNITY_BUILD=ON -DCURL_WERROR=ON \ -DCMAKE_VS_GLOBALS=TrackFileAccess=false \ -DCURL_STATIC_CRT=ON \ @@ -105,8 +104,8 @@ else curl="build/${platdir}/${VC_VERSION}/${PRJ_CFG}/curl${binsuffix}.exe" fi -find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 file -- -find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | xargs -0 stat -c '%10s bytes: %n' -- +find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | sort -z | xargs -0 file -- +find . \( -name '*.exe' -o -name '*.dll' -o -name '*.lib' -o -name '*.pdb' \) -print0 | grep -z curl | sort -z | xargs -0 stat -c '%10s bytes: %n' -- if [ -z "${SKIP_RUN:-}" ]; then "${curl}" --disable --version @@ -114,6 +113,30 @@ else echo "Skip running curl.exe. Reason: ${SKIP_RUN}" fi +# create artifact + +if [[ "${CREATE_ARTIFACT:-}" = 'true' ]]; then + cp -p /usr/ssl/certs/ca-bundle.crt curl-ca-bundle.crt + echo 'Checking that https works (it should find curl-ca-bundle.crt if needed)' + "${curl}" --disable --verbose --fail --silent --show-error --retry 6 --retry-all-errors --out-null https://curl.se/ + if [ -n "${APPVEYOR_PULL_REQUEST_NUMBER:-}" ]; then + archive="curl_pr${APPVEYOR_PULL_REQUEST_NUMBER}_${APPVEYOR_PULL_REQUEST_HEAD_COMMIT}.zip" + else + archive="curl_${APPVEYOR_REPO_COMMIT}.zip" + fi + { + echo 'WARNING: Do not run this build unless the download link was provided by staff.' + echo 'Anyone can submit a PR with possibly malicious code to generate a build.' + echo "${archive}" + } > WARNING.txt + echo 'Finding curl module dependencies' + "${curl}" --dump-module-paths | grep -Fv 'C:\Windows' | tee > files.tmp + echo 'Creating artifact' + 7z a "${archive}" -y -bb1 -bsp0 -mx9 -tzip -i@files.tmp curl-ca-bundle.crt WARNING.txt + rm files.tmp + appveyor PushArtifact "${archive}" +fi + # build tests if [ -n "${CMAKE_GENERATOR:-}" ] && [[ "${APPVEYOR_JOB_NAME}" = *'Build-tests'* ]]; then diff --git a/appveyor.yml b/appveyor.yml index a08285efc523..1ea548febf3b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -39,79 +39,67 @@ environment: - job_name: 'CM VS2022, Release, x64, OpenSSL 3.5, Shared, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' CMAKE_GENERATOR: 'Visual Studio 17 2022' - CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=OFF -DCURL_USE_SCHANNEL=OFF -DCURL_USE_OPENSSL=ON' + CMAKE_GENERATE: '-A x64 -DCURL_USE_SCHANNEL=OFF -DCURL_USE_OPENSSL=ON' - job_name: 'CM VS2026, Release, arm64, Schannel, Static, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' - CMAKE_VERSION: 4.2.1 + CMAKE_VERSION: 4.2.1 # manually bumped CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 CMAKE_GENERATOR: 'Visual Studio 18 2026' - CMAKE_GENERATE: '-A ARM64 -DENABLE_DEBUG=OFF -DBUILD_SHARED_LIBS=OFF' + CMAKE_GENERATE: '-A ARM64 -DBUILD_SHARED_LIBS=OFF' - job_name: 'CM VS2010, Debug, x64, Schannel, Shared, Build-tests & examples' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' - CMAKE_VERSION: 3.18.4 + CMAKE_VERSION: 3.18.4 # manually bumped (lowest minor version supported by curl) CMAKE_SHA256: a932bc0c8ee79f1003204466c525b38a840424d4ae29f9e5fb88959116f2407d CMAKE_GENERATOR: 'Visual Studio 10 2010' - CMAKE_GENERATE: '-A x64' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON' - job_name: 'CM VS2012, Release, x86, Schannel, Shared, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' - CMAKE_VERSION: 3.21.7 + CMAKE_VERSION: 3.21.7 # manually bumped CMAKE_SHA256: 4c4840e2dc2bf82e8a16081ff506bba54f3a228b91ce36317129fed4035ef2e3 CMAKE_GENERATOR: 'Visual Studio 11 2012' - CMAKE_GENERATE: '-A Win32 -DENABLE_DEBUG=OFF' + CMAKE_GENERATE: '-A Win32' - job_name: 'CM VS2013, Debug, x64, Schannel, Shared' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' - CMAKE_VERSION: 3.18.4 + CMAKE_VERSION: 3.18.4 # manually bumped (lowest minor version supported by curl) CMAKE_SHA256: a932bc0c8ee79f1003204466c525b38a840424d4ae29f9e5fb88959116f2407d CMAKE_GENERATOR: 'Visual Studio 12 2013' - CMAKE_GENERATE: '-A x64' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON' - job_name: 'CM VS2015, Debug, x64, Schannel, Static' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' - CMAKE_VERSION: 3.19.8 + CMAKE_VERSION: 3.19.8 # manually bumped CMAKE_SHA256: 2a30877a3d6b50da305b289f4d1c03befdfaeb2edba02a563c681e883d810380 CMAKE_GENERATOR: 'Visual Studio 14 2015' - CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF' - job_name: 'CM VS2017, Debug, x64, Schannel, Shared' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2017' - CMAKE_VERSION: 3.20.6 + CMAKE_VERSION: 3.20.6 # manually bumped CMAKE_SHA256: f240a38c964712aac474644b3ba21bdc2b4e8d5e31179f67bd2e6f45fa349419 CMAKE_GENERATOR: 'Visual Studio 15 2017' - CMAKE_GENERATE: '-A x64' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON' - job_name: 'CM VS2019, Debug, x64, OpenSSL 3.0 + Schannel, Shared, !verbose, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2019' CMAKE_GENERATOR: 'Visual Studio 16 2019' - CMAKE_GENERATE: '-A x64 -DCURL_USE_OPENSSL=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON' + CMAKE_GENERATE: '-A x64 -DENABLE_DEBUG=ON -DCURL_USE_OPENSSL=ON -DCURL_DISABLE_VERBOSE_STRINGS=ON' - - job_name: 'CM VS2026, Debug, x64, OpenSSL 3.5 + Schannel, Static, Unicode, Build-tests & examples, clang-cl' + - job_name: 'CM VS2026, Debug, x64, OpenSSL 3.6 + Schannel, Static, Unicode, Build-tests & examples, clang-cl' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' - CMAKE_VERSION: 4.2.1 + CMAKE_VERSION: 4.2.1 # manually bumped (first minor version to support VS2026) CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 CMAKE_GENERATOR: 'Visual Studio 18 2026' - CMAKE_GENERATE: '-A x64 -T ClangCl -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=ON' + CMAKE_GENERATE: '-A x64 -T ClangCl -DENABLE_DEBUG=ON -DBUILD_SHARED_LIBS=OFF -DCURL_USE_OPENSSL=ON -DENABLE_UNICODE=ON' + CREATE_ARTIFACT: 'true' - job_name: 'CM VS2022, Release, x64, Schannel, Shared, Unicode, Build-tests' APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' CMAKE_GENERATOR: 'Visual Studio 17 2022' - ENABLE_UNICODE: 'ON' - CMAKE_GENERATE: '-A x64 -DENABLE_UNICODE=ON -DENABLE_DEBUG=OFF' - - - job_name: 'CM VS2026, Debug, x64, !ssl, Static, Build-tests' - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2026' - CMAKE_VERSION: 4.2.1 - CMAKE_SHA256: dfc2b2afac257555e3b9ce375b12b2883964283a366c17fec96cf4d17e4f1677 - CMAKE_GENERATOR: 'Visual Studio 18 2026' - CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=OFF' - - - job_name: 'CM VS2022, Debug, x64, !ssl, Static, HTTP-only, Build-tests' - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022' - CMAKE_GENERATOR: 'Visual Studio 17 2022' - CMAKE_GENERATE: '-A x64 -DBUILD_SHARED_LIBS=OFF -DCURL_USE_SCHANNEL=OFF -DHTTP_ONLY=ON' + CMAKE_GENERATE: '-A x64 -DENABLE_UNICODE=ON' # VisualStudioSolution builds diff --git a/configure.ac b/configure.ac index a5f95c1cad0c..ed5e32a2d24d 100644 --- a/configure.ac +++ b/configure.ac @@ -1069,23 +1069,6 @@ AS_HELP_STRING([--disable-imap],[Disable IMAP support]), AC_MSG_RESULT(yes) ) -AC_MSG_CHECKING([whether to support smb]) -AC_ARG_ENABLE(smb, -AS_HELP_STRING([--enable-smb],[Enable SMB support]) -AS_HELP_STRING([--disable-smb],[Disable SMB support]), -[ case "$enableval" in - yes) - AC_MSG_RESULT(yes) - AC_DEFINE(CURL_ENABLE_SMB, 1, [to enable SMB]) - CURL_ENABLE_SMB=1 - ;; - *) - AC_MSG_RESULT(no) - ;; - esac ], - AC_MSG_RESULT(no) -) - AC_MSG_CHECKING([whether to support smtp]) AC_ARG_ENABLE(smtp, AS_HELP_STRING([--enable-smtp],[Enable SMTP support]) @@ -4168,22 +4151,21 @@ case $host_os in ;; *) AC_MSG_CHECKING([if time_t is unsigned]) - CURL_RUN_IFELSE( - [ - #include - int main(void) - { - time_t t = -1; - return t < 0; - } - ],[ + AC_COMPILE_IFELSE([ + AC_LANG_SOURCE([[ + #include + static int time_t_is_unsigned[((time_t)-1 > 0) ? 1 : -1]; + int main(void) + { + (void)time_t_is_unsigned; + return 0; + } + ]]) + ],[ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_TIME_T_UNSIGNED, 1, [Define this if time_t is unsigned]) ],[ AC_MSG_RESULT([no]) - ],[ - dnl cross-compiling, most systems are signed - AC_MSG_RESULT([no]) ]) ;; esac @@ -4250,9 +4232,10 @@ AC_CHECK_FUNCS([\ ]) if test "$curl_cv_apple" != "yes"; then - dnl Apple platforms do not offer pipe2(), but the iPhone Simulator-specific + dnl Apple SDK <27 do not offer pipe2(), but the iPhone Simulator-specific dnl /usr/lib/system/libsystem_sim_kernel.dylib exports it. To avoid false dnl detection, omit this feature check for Apple targets. + dnl For 27+, detection is done in-source. AC_CHECK_FUNCS([\ pipe2 \ ]) @@ -4260,6 +4243,7 @@ fi if test "$curl_cv_native_windows" != "yes"; then AC_CHECK_FUNCS([\ + fchmod \ gettimeofday \ if_nametoindex \ realpath \ @@ -5488,12 +5472,6 @@ if test "$CURL_DISABLE_IMAP" != "1"; then SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS IMAPS" fi fi -if test "$CURL_ENABLE_SMB" = "1" && test "$use_curl_ntlm_core" = "yes"; then - SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SMB" - if test "$SSL_ENABLED" = "1"; then - SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SMBS" - fi -fi if test "$CURL_DISABLE_SMTP" != "1"; then SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS SMTP" if test "$SSL_ENABLED" = "1"; then diff --git a/docs/CURL-DISABLE.md b/docs/CURL-DISABLE.md index 83494cf18b4f..f3ba9cc8a977 100644 --- a/docs/CURL-DISABLE.md +++ b/docs/CURL-DISABLE.md @@ -161,10 +161,6 @@ Disable the SHA-512/256 hash algorithm. Disable the shuffle DNS feature -## `CURL_ENABLE_SMB` - -Enable the SMB(S) protocols - ## `CURL_DISABLE_SMTP` Disable the SMTP(S) protocols diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md index efa659cb9350..1e9b5a94b3de 100644 --- a/docs/DEPENDENCIES.md +++ b/docs/DEPENDENCIES.md @@ -75,3 +75,4 @@ that do not find the necessary requirements are automatically skipped. - pytest - Python 3.8 (2019-10-14) - stunnel +- valgrind 3.0.0 (2005-08-03) diff --git a/docs/DEPRECATE.md b/docs/DEPRECATE.md index e20fba5d617d..8774dd79db5c 100644 --- a/docs/DEPRECATE.md +++ b/docs/DEPRECATE.md @@ -12,12 +12,6 @@ email the as soon as possible and explain to us why this is a problem for you and how your use case cannot be satisfied properly using a workaround. -## drop SMB support - -The SMB protocol has weak security and is rarely used these days. - -SMB support gets removed in September 2026. - ## drop NTLM support The NTLM authentication method has weak security and is rarely used these @@ -57,6 +51,18 @@ We estimate that barely any libcurl users still use this feature. HTTP/2 Server Push gets removed in March 2027. +## Common Name support in TLS certificates for OpenSSL + +The Common Name, (`CN`) field is deprecated since RFC 2818. It was downgraded +to a SHOULD in RFC 6125 and finally removed in RFC 9525. Browsers have not +supported this field since 2012. + +In the release after 8.23.0 curl removes support for this field in code we +control - which primarily means in the OpenSSL (and forks) backend. The other +TLS libraries provide their own functions for verification. + +Pending PR: https://github.com/curl/curl/pull/22845 + ## Past removals - axTLS (removed in 7.63.0) @@ -86,3 +92,4 @@ HTTP/2 Server Push gets removed in March 2027. - NTLM (became opt-in in 8.20.0) - c-ares < 1.16.0 (removed in 8.20.0) - TLS-SRP (removed in 8.22.0) +- SMB support (removed in 8.23.0) diff --git a/docs/FAQ.md b/docs/FAQ.md index 6cbba7d81110..fbf26f1baf17 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -22,7 +22,7 @@ The curl project produces two products: A client-side URL transfer library, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, -RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. +RTSP, SCP, SFTP, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl supports HTTPS certificates, HTTP POST, HTTP PUT, FTP uploading, Kerberos, SPNEGO, HTTP form based upload, proxies, cookies, user+password @@ -339,8 +339,8 @@ files. ## How do I tell curl to resume a transfer? -curl supports resumed transfers both ways on both FTP and HTTP. Try the `-C` -option. +curl supports resumed HTTP, FTP and SFTP downloads as well as resumed FTP and +SFTP uploads. Try the `-C` option. ## Why does my posting using -F not work? diff --git a/docs/FEATURES.md b/docs/FEATURES.md index a7f184505629..463aef6f17f5 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -184,13 +184,6 @@ SPDX-License-Identifier: curl - upload - resume -## SMB - -- SMBv1 over TCP and SSL -- download -- upload -- authentication with NTLMv1 - ## SMTP - authentication: Plain, Login, CRAM-MD5, Digest-MD5, NTLM, Kerberos 5 and diff --git a/docs/HISTORY.md b/docs/HISTORY.md index 479d9116d523..9a87c339e05f 100644 --- a/docs/HISTORY.md +++ b/docs/HISTORY.md @@ -245,7 +245,7 @@ March: security vulnerability: libcurl Arbitrary File Access April: added CMake support -August: security vulnerability: libcurl embedded zero in cert name +August: security vulnerability: libcurl embedded zero byte in cert name December: Added support for IMAP, POP3 and SMTP @@ -503,3 +503,5 @@ December 15: the website served 78 TB over the last month. ## 2026 April: removed support for RTMP + +September: removed support for SMB diff --git a/docs/HTTP3.md b/docs/HTTP3.md index 07535f1559fa..c913aebd2457 100644 --- a/docs/HTTP3.md +++ b/docs/HTTP3.md @@ -40,7 +40,7 @@ Building curl with ngtcp2 involves 3 components: `ngtcp2` itself, `nghttp3` and a QUIC supporting TLS library. The supported TLS libraries are covered below. -While any version of `ngtcp2` and `nghttp3` from v1.0.0 on are expected to +While any version of `ngtcp2` and `nghttp3` from 1.0.0 on are expected to work, using the latest versions often brings functional and performance improvements. @@ -49,11 +49,11 @@ placeholders for the version you build. ## Build with OpenSSL or fork -OpenSSL v3.5.0+ requires *ngtcp2* v1.12.0+. Earlier versions do not work. +OpenSSL 3.5.0+ requires *ngtcp2* 1.12.0+. Earlier versions do not work. -Build OpenSSL (v3.5.0+) or fork AWS-LC, BoringSSL, LibreSSL or quictls: +Build OpenSSL (3.5.0+) or fork AWS-LC, BoringSSL, LibreSSL or quictls: - # Instructions for OpenSSL v3.5.0+ + # Instructions for OpenSSL 3.5.0+ % git clone --depth 1 --branch openssl-$OPENSSL_VERSION https://github.com/openssl/openssl % cd openssl % ./config --prefix=/path/to/openssl --libdir=lib @@ -215,28 +215,36 @@ but in case of problems, we recommend their latest release tag. ## Build -Build quiche and BoringSSL (described here for quiche v0.29.1, the locations +Build quiche and BoringSSL (described here for quiche 0.30.0, the locations where BoringSSL is to be found vary with version): - % git clone --depth 1 --branch 0.29.1 --recursive https://github.com/cloudflare/quiche + % git clone --depth 1 --branch 0.30.0 --recursive https://github.com/cloudflare/quiche % cd quiche % cargo build --package quiche --release --features ffi,pkg-config-meta,qlog % ln -s libquiche.so target/release/libquiche.so.0 % mkdir -p boringssl/lib % find target/release \( -name libcrypto.a -o -name libssl.a \) -exec ln -vnf -- '{}' boringssl/lib \; - % find target/release/build/boring-sys-*/out/boringssl/src -maxdepth 1 \( -name include \) -exec ln -vsf -- '../{}' boringssl \; + % find target/release/build/boring-sys-*/out/boringssl -maxdepth 1 \( -name include \) -exec ln -vsf -- '../{}' boringssl \; -Build curl: +Build curl (with autotools): % cd .. % git clone --depth 1 https://github.com/curl/curl % cd curl % autoreconf -fi - % ./configure --with-openssl=$PWD/../quiche/boringssl --with-quiche=$PWD/../quiche/target/release + % CC=clang ./configure --with-openssl=$PWD/../quiche/boringssl --with-quiche=$PWD/../quiche/target/release CFLAGS=-fPIC LDFLAGS=-lstdc++ % make % make install -If `make install` results in `Permission denied` error, you need to prepend +Build curl (with CMake): + + % cd .. + % git clone --depth 1 https://github.com/curl/curl + % cd curl + % PKG_CONFIG_PATH=$PWD/../quiche/target/release CC=clang cmake -B bld -DOPENSSL_ROOT_DIR=$PWD/../quiche/boringssl -DOPENSSL_USE_STATIC_LIBS=ON -DUSE_QUICHE=ON + % cmake --build bld + +If the install step results in `Permission denied` error, you need to prepend it with `sudo`. # `--http3` diff --git a/docs/INSTALL-CMAKE.md b/docs/INSTALL-CMAKE.md index 16a3580634ef..951a38459e52 100644 --- a/docs/INSTALL-CMAKE.md +++ b/docs/INSTALL-CMAKE.md @@ -246,7 +246,6 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_ENABLE_APPLE_FAST_UDP`: Use Apple fast UDP (experimental). Default: `OFF` - `CURL_ENABLE_NTLM`: Enable NTLM support. Default: `OFF` -- `CURL_ENABLE_SMB`: Enable SMB. Default: `OFF` - `CURL_ENABLE_SSL`: Enable SSL support. Default: `ON` - `CURL_WINDOWS_SSPI`: Enable SSPI on Windows. Default: =`CURL_USE_SCHANNEL` - `ENABLE_IPV6`: Enable IPv6 support. Default: `ON` if target supports IPv6. @@ -309,7 +308,6 @@ target_link_libraries(my_target PRIVATE CURL::libcurl) - `CURL_DISABLE_TYPECHECK`: Disable curl_easy_setopt()/curl_easy_getinfo() type checking. Default: `OFF` - `CURL_DISABLE_VERBOSE_STRINGS`: Disable verbose strings. Default: `OFF` - `CURL_DISABLE_WEBSOCKETS`: Disable WebSocket. Default: `OFF` -- `HTTP_ONLY`: Disable all protocols except HTTP (This overrides all `CURL_DISABLE_*` options). Default: `OFF` ## Environment diff --git a/docs/INSTALL.md b/docs/INSTALL.md index ce1b63040555..56adcbb42701 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -504,8 +504,8 @@ install `libssl.a` and `libcrypto.a` to `$TOOLCHAIN/sysroot/usr/lib` and copy for Android using OpenSSL like this: ```sh -# For BoringSSL/OpenSSL. In general, you need to the SSL/TLS layer's transitive -# dependencies if you are linking statically. +# For AWS-LC/BoringSSL/OpenSSL. In general, you need to the SSL/TLS layer's +# transitive dependencies if you are linking statically. LIBS='-lssl -lcrypto -lc++' ./configure --host aarch64-linux-android --with-pic --disable-shared --with-openssl="$TOOLCHAIN/sysroot/usr" ``` diff --git a/docs/IPFS.md b/docs/IPFS.md index 2cf64543b93f..612507e23034 100644 --- a/docs/IPFS.md +++ b/docs/IPFS.md @@ -109,15 +109,15 @@ requests to [subdomain based](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#subdomain-gateway) ones. A request using: - curl ipfs://bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi --ipfs-gateway https://dweb.link + curl ipfs://bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi --ipfs-gateway https://inbrowser.link/ Which would be translated to: - https://dweb.link/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi + https://inbrowser.link/ipfs/bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi redirects to: - https://bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi.ipfs.dweb.link + https://bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi.ipfs.inbrowser.link/ If you trust this behavior from your gateway of choice then passing the `-L` option follows the redirect. diff --git a/docs/KNOWN_BUGS.md b/docs/KNOWN_BUGS.md index 6d98f26957d4..7229063206ac 100644 --- a/docs/KNOWN_BUGS.md +++ b/docs/KNOWN_BUGS.md @@ -115,12 +115,6 @@ this library to download. See [curl issue 5176](https://github.com/curl/curl/issues/5176) -## `curl-config --libs` contains private details - -`curl-config --libs` include details set in `LDFLAGS` when configure is run -that might be needed only for building libcurl. Further, `curl-config ---cflags` suffers from the same effects with `CFLAGS`/`CPPFLAGS`. - ## `LDFLAGS` passed too late making libs linked incorrectly Compiling latest curl on HP-UX and linking against a custom OpenSSL (which is @@ -196,7 +190,15 @@ https://curl.se/mail/lib-2012-07/0073.html # Authentication -## `--aws-sigv4` does not handle multipart/form-data correctly +## `--aws-sigv4` signs an empty payload hash for bodies not held in memory + +`--aws-sigv4` hashes only the in-memory POST fields when it computes the payload hash, so a request +whose body comes from anywhere else is signed as if it had no body at all: the signature is +computed over `SHA256("")` while a different body goes on the wire. This affects `-F` +(multipart/form-data) and `-T` (PUT upload) alike. + +`aws:amz::s3` is not affected, because it sends and signs `UNSIGNED-PAYLOAD` instead. Every +other service gets the empty hash, with no `x-amz-content-sha256` header sent, and no warning. [curl issue 13351](https://github.com/curl/curl/issues/13351) @@ -347,13 +349,6 @@ The struct needs to be expanded and code added to store this info. See [curl issue 13492](https://github.com/curl/curl/issues/13492) -## error buffer not set if connection to multiple addresses fails - -If you ask libcurl to resolve a hostname like example.com to IPv6 addresses -when you only have IPv4 connectivity. libcurl fails with -`CURLE_COULDNT_CONNECT`, but the error buffer set by `CURLOPT_ERRORBUFFER` -remains empty. Issue: [curl issue 544](https://github.com/curl/curl/issues/544) - ## HTTP test server 'connection-monitor' problems The `connection-monitor` feature of the HTTP test server does not work @@ -490,13 +485,3 @@ See https://curl.se/mail/lib-2024-05/0026.html and The transfer ends with error "QUIC connection is draining". [curl issue 12037](https://github.com/curl/curl/issues/12037) - -# RTSP - -## Some methods do not support response bodies - -The RTSP implementation is written to assume that a number of RTSP methods -always get responses without bodies, even though there seems to be no -indication in the RFC that this is always the case. - -[curl issue 12414](https://github.com/curl/curl/issues/12414) diff --git a/docs/MANUAL.md b/docs/MANUAL.md index d979455d8496..329fb5491e66 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -63,10 +63,6 @@ Get the main page from an IPv6 web server: curl "http://[2001:1890:1112:1::20]/" -Get a file from an SMB server: - - curl -u "domain\username:passwd" smb://server.example.com/share/file.txt - ## Download to a File Get a webpage and store in a local file with a specific name: @@ -244,11 +240,6 @@ fashion similar to: curl --proxytunnel -x proxy:port -T localfile ftp.example.com -### SMB / SMBS - - curl -T file.txt -u "domain\username:passwd" - smb://server.example.com/share/ - ### HTTP Upload all data on stdin to a specified HTTPS site: @@ -306,7 +297,7 @@ Post a simple `name` and `phone` guestbook. curl -d "name=Rafael%20Sagula&phone=3320780" https://www.example.com/guest.cgi -Or automatically [URL encode the data](https://everything.curl.dev/http/post/url-encode). +Or automatically [URL encode the data](https://everything.curl.dev/http/post/url-encode.html). curl --data-urlencode "name=Rafael Sagula&phone=3320780" https://www.example.com/guest.cgi diff --git a/docs/RELEASE-PROCEDURE.md b/docs/RELEASE-PROCEDURE.md index 81dcbc628455..38ee29d3d802 100644 --- a/docs/RELEASE-PROCEDURE.md +++ b/docs/RELEASE-PROCEDURE.md @@ -125,9 +125,10 @@ push for it. Based on the description above, here are some planned future release dates: -- June 24, 2026 -- September 2, 2026 -- October 28, 2026 -- December 23, 2026 -- February 17, 2027 -- April 14, 2027 +- October 14, 2026 +- December 9, 2026 +- February 3, 2027 +- March 31, 2027 +- May 26, 2027 +- July 21, 2027 +- September 15, 2027 diff --git a/docs/RUSTLS.md b/docs/RUSTLS.md index ed032f7fb628..4899a10aacef 100644 --- a/docs/RUSTLS.md +++ b/docs/RUSTLS.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: curl [Rustls is a TLS backend written in Rust](https://docs.rs/rustls/). curl can be built to use it as an alternative to OpenSSL or other TLS backends. We use the [rustls-ffi C bindings](https://github.com/rustls/rustls-ffi). This -version of curl is compatible with `rustls-ffi` v0.15.x. +version of curl is compatible with `rustls-ffi` 0.15.x. ## Getting rustls-ffi diff --git a/docs/THANKS b/docs/THANKS index 36a75f3de390..0fb35227178d 100644 --- a/docs/THANKS +++ b/docs/THANKS @@ -44,6 +44,7 @@ Adam Piggott Adam Rosenfield Adam Sampson adamse on github +Adam Shirt Adam Tkac ad-chaos on github Aditya Garg @@ -54,6 +55,7 @@ Adrian Burcea Adriano Meirelles Adrian Peniak Adrian Schuur +Adrien Rey-Jarthon Ady Elouej afengsoft on github afrind on github @@ -108,6 +110,7 @@ Alexander Elgert Alexander Jaeger Alexander Kanavin Alexander Klauer +Alexander Köplinger Alexander Kourakos Alexander Krasnostavsky Alexander Lazic @@ -280,6 +283,7 @@ Anthony G. Basile Anthony Hu Anthony Ramine Anthony Shaw +Anthropic Antoine Aubert Antoine Bollengier Antoine Calando @@ -321,12 +325,14 @@ Arne Soete Aron Bergman Aron Rotteveel Artak Galoyan +Artem Prilutskiy Arthur Murray Artur Sinila Arve Knudsen Arvid Norberg arvids-kokins-bidstack on github asavah on github +aschank on github Asger Hautop Drewsen Ashish Shukla Ashwin Metpalli @@ -343,9 +349,11 @@ Austin Green Austin Moore av223119 on github Avery Fay +Aviv Engelberg awesomekosm on github awesomenode on github Axel Chong +Axel Mierczuk Axel Morawietz Axel Tillequin Ayesh Karunaratne @@ -451,6 +459,7 @@ blankie bnfp on github bo0tzz on github Bo Anderson +Bobbeh Rhino bobmitchell1956 on github BobodevMm on github Bob Relyea @@ -524,6 +533,7 @@ bsr13 on hackerone bttrfl on github bubbleguuum on github Bubu on github +bupt-Yy-young on github buzo-ffm on github bxac on github Bylon2 on github @@ -575,6 +585,7 @@ Chandrakant Bagul Chara White Charles Cazabon Charles Kerr +Charles Muehlberger Charles Romestant Charlie C chemodax @@ -751,6 +762,7 @@ Daniel Kahn Gillmor Daniel Katz Daniel Krügler Daniel Kurečka +Daniel Lang Daniel Lee Hwang Daniel Lublin Daniel Marjamäki @@ -765,6 +777,7 @@ Daniel Schulte Daniel Seither Daniel Shahaf Daniel Silverstone +Daniel S. Roche Daniel Steinberg Daniel Stenberg Daniel Szmulewicz @@ -794,6 +807,7 @@ Darryl House Darshan Mody dasimx on github DasKutti on github +dataisland Dave Cottlehuber Dave Dribin Dave Halbakken @@ -810,6 +824,7 @@ David Benjamin David Binderman David Blaikie David Bohman +David Bures David Byron David Carlier David Cohen @@ -1150,10 +1165,13 @@ Felix Kaiser Felix von Leitner Felix Yan Feng Tu +Fengxiaoxx on github +Fernando Gallardo Fernando Muñoz ffath-vo on github Filipe Casal Filip Lundgren +Filippo Tedeschi Filip Salomonsson finkjsc on github Fiona Klute @@ -1164,6 +1182,7 @@ fjaell on github Flameborn on github Flavio Amieiro Flavio Medeiros +Florent Castelli Florian Eckert Florian Friedrich Florian Imdahl @@ -1201,6 +1220,7 @@ Fred Klassen Fred Machado Fred New Fred Noz +Fredrik Blau Fredrik Thulin Fred Stluka FuccDucc on github @@ -1285,6 +1305,7 @@ Gorilla Maguila Goro FUJI Götz Babin-Ebell Gou Lingfeng +GPT-6.1 Sol Graham Campbell Graham Christensen Grant Erickson @@ -1358,6 +1379,7 @@ Harry Mallon Harry Sarson Harry Sintonen Harshal Pradhan +Hasan Flayyih Abdullah Hauke Duden Haydar Alaidrus Hayden Roche @@ -1407,6 +1429,7 @@ HsiehYuho on github htasta htasta on github huanghuihui0904 +Huang Yiheng Hubert Kario Hugh Macdonald Hugo van Kemenade @@ -1446,11 +1469,13 @@ ihsinme on github Iida Yosiaki Ikko Ashimine Ilguiz Latypov +Ilias Aberkane Ilja van Sprundel Illarion Taev illusory-dream on github Ilmari Lauhakangas Ilya Kosarev +İlyas Serdar Dursun imilli on github Immanuel Gregoire ImpatientHippo on github @@ -1550,6 +1575,7 @@ Jan Venekamp Jan Verbeek Jared Jennings Jared Lundell +jared-m-cohen-civ on github Jari Aalto Jari Sundell jasal82 on github @@ -1577,6 +1603,7 @@ Jaz Fresh JazJas on github jbgoog on github J. Bromley +jdymitarai Jean-Christophe Amiel Jean-Claude Chauve Jean Fabrice @@ -1780,6 +1807,7 @@ Jon Wilkes Joombalaya on github Joonas Kuorilehto Jordan Brown +Jorge Rocamora Jörg Mueller-Tolk Jörn Hartroth Jose Alf @@ -1808,6 +1836,7 @@ Josue Andrade Gomes Jozef Kralik Juan Barreto Juan Belón +JuanCarlosgg on github Juan Cruz Viotti Juan F. Codagnone Juan Ignacio Hervás @@ -1880,6 +1909,7 @@ Kartik Mahajan Kaspar Brand Katie Wang Katsuhiko YOSHIDA +KayanoLiam on github kayrus on github Kazuho Oku kchow-FTNT on github @@ -1964,6 +1994,7 @@ kuchara on github Kunal Chandarana Kunal Ekawde kupavcevdenis on github +Kuroda Kayn Kurt Fankhauser Kushal Das Kvarec Lezki @@ -2007,6 +2038,7 @@ Lauri Kasanen Lawrence Gripper Lawrence Matthews Lawrence Wagerfield +ldm0 Leah Neukirchen Lealem Amedie Leandro Coutinho @@ -2098,6 +2130,7 @@ Luke Granger-Brown Luke Hamburg Luke Wilde luminixinc on github +Luna Tong Luo Jinghua Luong Dinh Dung Luz Paz @@ -2118,6 +2151,7 @@ MaeIsBad on github magisterquis on hackerone Mahmoud Samir Fayed Maksim Arhipov +Maksim Hayder Maksim Kuzevanov Maksim Ściepanienka Maksim Stsepanenka @@ -2390,6 +2424,7 @@ Michel Promonet Miguel Angel Miguel Diaz migueljcrum on github +Mihai-Gabriel Marin Mihai Ionescu mik Mikael Johansson @@ -2419,6 +2454,7 @@ Milon Renatus Miloš Ljumović Mingliang Zhu Mingtao Yang +minnnjuuu Miroslav Franc Miroslav Spousta Mischa Salle @@ -2432,6 +2468,7 @@ Mohamed Osama Mohammad AlSaleh Mohammad Hasbini Mohammadreza Hendiani +Mohammed K. Fathy Mohammed Naser Mohammed Sadiq Mohun Biswas @@ -2570,6 +2607,7 @@ Norbert Kett norbertmm on github Norbert Novotny nosajsnikta on github +ns-ylambert NTMan on github Nuno Goncalves Nuru on github @@ -2631,6 +2669,7 @@ Ozan Cansel Pablo Busse Palo Markovic pandada8 on github +Paolo Ganci Paolo Mossino Paolo Piacentini parasol-aser @@ -2870,6 +2909,7 @@ Rasmus Melchior Jacobsen Rasmus Thomsen Raul Onitza-Klugman Ravi Pratap +rawsun007 Ray Dassen Raymond Steen Ray Pekowski @@ -2885,8 +2925,10 @@ Reinout van Schouwen RekGRpth on github Remco van Hooff Remi Gacogne +Rémi NICOLE Remo E Rémy Léone +Renan Pimentel Renato Botelho Renaud Allard Renaud Chaillat @@ -2901,6 +2943,7 @@ RepoRascal on hackerone Reuven Wachtfogel RevaliQaQ on github Reza Arbab +Rhino Rianov Viacheslav riastradh on github Ricardo Cadime @@ -2923,6 +2966,7 @@ Richard Levitte Richard Marion Richard Michael Richard Moore +Richard Payne Richard Prescott Richard Silverman Richard Tollerton @@ -3028,6 +3072,7 @@ Ronnie Mose Ron Parker Ron Zapp Rosen Penev +Roshan Ramani Rosimildo da Silva Ross Burton roughtex on github @@ -3036,6 +3081,7 @@ Roy Li Roy Shan RubisetCie on github Rudi Heitbaum +Rudolf Polzer Rui LIU Rui Pinheiro Rune Kleveland @@ -3137,6 +3183,7 @@ Sebastian Haglund Sebastian Mundry Sebastian Neubauer Sebastian Pohlschmidt +Sebastian Raase Sebastian Rasmussen Sebastian Sterk Sebastian Walz @@ -3217,6 +3264,7 @@ Simon Warta simplerobot on github Sinkevich Artem Siva Sivaraman +Skye Soss Slaven Rezić SLDiggie on github Smackd0wn on github @@ -3295,6 +3343,7 @@ Stephen Farrell Stephen Kick Stephen M. Coakley Stephen More +Stephen Psaradellis Stephen Toub Sterling Hughes Steve Green @@ -3340,6 +3389,7 @@ swalkaus at yahoo.com sylgal on github Sylvestre Ledru Symeon Paraschoudis +szedenik-adam on github T200proX7 on github Tadej Vengust Tae Hyoung Ahn @@ -3367,6 +3417,7 @@ Terri Oda Terry Wu Tetetest thanhchungbtc on github +Thanniru Sai Teja TheAssassin on github TheBitBrine The Infinnovation team @@ -3443,6 +3494,7 @@ Tim Verhoeven Tim Yuer Tinus van den Berg tinyboxvk +Tip ten Brink tiymat TJ Saunders Tk Xiong @@ -3544,12 +3596,14 @@ Tupone Alfredo Turiiya T. Yamada Tyler Hall +Tyler Yankee u20221022 on github Ulf Härnhammar Ulf Samuelsson Ulrich Doehner Ulrich Telle Ulrich Zadow +Un1q32 UnicornZhang on github updatede on github UrsusArctos on github @@ -3666,6 +3720,7 @@ wncboy on github Wojciech Zwiefka wolfsage on hackerone Wolf Vollprecht +Wonyoung Jung Wouter Van Rooy wulin-nudt on github Wu Yongzheng @@ -3680,6 +3735,7 @@ Xavier Bouchoux XCas13 xfangfang XhmikosR on github +xhon-pelushi XhstormR on github xiadnoring on github Xiangbin Li @@ -3708,6 +3764,7 @@ yaoy6 on github Yarram Sunil Yasuharu Yamada Yasuhiro Matsumoto +Yechan Bae Yechiel Kalmenson Yedaya Katsman Yehezkel Horowitz @@ -3737,6 +3794,7 @@ Yuriy Chernyshov Yuriy Sosov yushicheng7788 on github Yusuke Nakamura +yuwk Yu Xin Yuyi Wang Yves Arrouye diff --git a/docs/THANKS-filter b/docs/THANKS-filter index f9e7e1c8d2d4..5dd1e6e27cb4 100644 --- a/docs/THANKS-filter +++ b/docs/THANKS-filter @@ -163,3 +163,7 @@ s/Qriist.*/Qriist on github/ s/Viktor Szakatas/Viktor Szakats/ s/Val S\./Valerie Snyder/ s/Andrew Nesbit$/Andrew Nesbitt/ +s/^https:\/\/.*// +s/OSS-Fuzz// +s/Claude// +s/Copilot// diff --git a/docs/TODO.md b/docs/TODO.md index 28f804eb41d6..815d84353008 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -170,10 +170,16 @@ Make sure we do not ever loop because of non-blocking sockets returning - TELNET transfers - GSSAPI authentication for FTP transfers - The "DONE" operation (post transfer protocol-specific actions) for the -protocols SFTP, SMTP, FTP. Fixing `multi_done()` for this is a worthy task. +protocols SFTP, SCP, SMTP, IMAP, FTP. Fixing `multi_done()` for this is a +worthy task. +- The protocol disconnect handlers for FTP, IMAP, POP3, SMTP, SCP and SFTP, +when a connection is closed instead of kept for reuse. - `curl_multi_remove_handle()` for any of the above. - Calling `curl_ws_send()` from a callback +Several of these are also documented in the BLOCKING section +of [`libcurl-multi`](https://curl.se/libcurl/c/libcurl-multi.html). + ## Better support for same name resolves If a name resolve has been initiated for a given name and a second easy handle @@ -449,23 +455,6 @@ the communicating parties. [curl issue 5081](https://github.com/curl/curl/issues/5081) -## TLS channel binding - -TLS 1.2 and 1.3 provide the ability to extract some secret data from the TLS -connection and use it in the client request (usually in some sort of -authentication) to ensure that the data sent is bound to the specific TLS -connection and cannot be successfully intercepted by a proxy. This -functionality can be used in a standard authentication mechanism such as -GSS-API or SCRAM, or in custom approaches like custom HTTP Authentication -headers. - -For TLS 1.2, the binding type is usually `tls-unique`, and for TLS 1.3 it is -`tls-exporter`. - -- https://datatracker.ietf.org/doc/html/rfc5929 -- https://datatracker.ietf.org/doc/html/rfc9266 -- [curl issue 9226](https://github.com/curl/curl/issues/9226) - ## Defeat TLS fingerprinting By changing the order of TLS extensions provided in the TLS handshake, it is @@ -930,13 +919,6 @@ http operations (for which we have test servers). Make the test suite work on more platforms. OpenBSD and macOS. Remove fork()s and it should become even more portable. -## write an SMB test server to replace impacket - -This would allow us to run SMB tests on more platforms and do better and more -covering tests. - -See [curl issue 15697](https://github.com/curl/curl/issues/15697) - ## Use the RFC 6265 test suite A test suite made for HTTP cookies (RFC 6265) by Adam Barth [is diff --git a/docs/URL-SYNTAX.md b/docs/URL-SYNTAX.md index 219e84ee932c..6e6fed4d5aa6 100644 --- a/docs/URL-SYNTAX.md +++ b/docs/URL-SYNTAX.md @@ -141,8 +141,8 @@ curl supports the following schemes on URLs specified to transfer. They are matched case insensitively: `dict`, `file`, `ftp`, `ftps`, `gopher`, `gophers`, `http`, `https`, `imap`, -`imaps`, `ldap`, `ldaps`, `mqtt`, `pop3`, `pop3s`, `rtsp`, `smb`, `smbs`, -`smtp`, `smtps`, `telnet`, `tftp` +`imaps`, `ldap`, `ldaps`, `mqtt`, `pop3`, `pop3s`, `rtsp`, `smtp`, `smtps`, +`telnet`, `tftp` When the URL is specified to identify a proxy, curl recognizes the following schemes: @@ -243,7 +243,7 @@ number based on the provide scheme: DICT 2628, FTP 21, FTPS 990, GOPHER 70, GOPHERS 70, HTTP 80, HTTPS 443, IMAP 143, IMAPS 993, LDAP 389, LDAPS 636, MQTT 1883, POP3 110, POP3S 995, RTSP 554, -SCP 22, SFTP 22, SMB 445, SMBS 445, SMTP 25, SMTPS 465, TELNET 23, TFTP 69 +SCP 22, SFTP 22, SMTP 25, SMTPS 465, TELNET 23, TFTP 69 ## Path @@ -398,17 +398,6 @@ path ends with a slash (`/`) then a directory listing is returned instead of a file. If the path is omitted entirely then the directory listing for the root / home directory is returned. -## SMB -The path part of an SMB request specifies the file to retrieve and from what -share and directory or the share to upload to and as such, may not be omitted. -If the username is embedded in the URL then it must contain the domain name -and as such, the backslash must be URL encoded as %2f. - -When uploading to SMB, the size of the file needs to be known ahead of time, -meaning that you can upload a file passed to curl over a pipe like stdin. - -curl supports SMB version 1 (only) - ## SMTP The path part of an SMTP request specifies the hostname to present during diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index c5db67b75881..84f4077dda35 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -68,7 +68,8 @@ dates. The tool was called `httpget` before 2.0, `urlget` before 4.0 then `curl` since 4.0. `libcurl` and `curl` are always released in sync, using the same version numbers. -- 8.23.0: pending +- 8.24.0: pending +- 8.23.0: October 14, 2026 - 8.22.0: September 2, 2026 - 8.21.0: June 24 2026 - 8.20.0: April 29 2026 @@ -356,3 +357,6 @@ version. - Rock-solid curl 8.18.1: February 25, 2026 - Rock-solid curl 8.18.2: September 2, 2026 +- Rock-solid curl 8.14.2: September 7, 2026 +- Rock-solid curl 8.16.1: September 7, 2026 +- Rock-solid curl 8.20.1: September 7, 2026 diff --git a/docs/cmdline-opts/_DESCRIPTION.md b/docs/cmdline-opts/_DESCRIPTION.md index bb21f0ba325a..ec94e1c9465e 100644 --- a/docs/cmdline-opts/_DESCRIPTION.md +++ b/docs/cmdline-opts/_DESCRIPTION.md @@ -4,8 +4,8 @@ **curl** is a tool for transferring data from or to a server using URLs. It supports these protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, -IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, -SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. +IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMTP, +SMTPS, TELNET, TFTP, WS and WSS. curl is powered by libcurl for all transfer-related features. See *libcurl(3)* for details. diff --git a/docs/cmdline-opts/_PROTOCOLS.md b/docs/cmdline-opts/_PROTOCOLS.md index 6b1918a9b227..b380c2d55bc8 100644 --- a/docs/cmdline-opts/_PROTOCOLS.md +++ b/docs/cmdline-opts/_PROTOCOLS.md @@ -36,8 +36,6 @@ curl supports RTSP 1.0 downloads. curl supports SSH version 2 scp transfers. ## SFTP curl supports SFTP (draft 5) done over SSH version 2. -## SMB(S) -curl supports SMB version 1 for upload and download. ## SMTP(S) Uploading contents to an SMTP server means sending an email. With or without TLS. diff --git a/docs/cmdline-opts/continue-at.md b/docs/cmdline-opts/continue-at.md index 67a137609306..3a9771a1a3fc 100644 --- a/docs/cmdline-opts/continue-at.md +++ b/docs/cmdline-opts/continue-at.md @@ -25,10 +25,8 @@ the FTP server command SIZE is not used by curl. Use "-C -" to instruct curl to automatically find out where/how to resume the transfer. It then uses the given output/input files to figure that out. -When using this option for HTTP uploads using POST or PUT, functionality is -not guaranteed. The HTTP protocol has no standard interoperable resume upload -and curl uses a set of headers for this purpose that once proved working for -some servers and have been left for those who find that useful. +HTTP uploads using POST or PUT cannot be resumed. curl returns an error when +you combine this option with such an upload. This command line option is mutually exclusive with --range: you can only use one of them for a single transfer. diff --git a/docs/cmdline-opts/httpsig-key.md b/docs/cmdline-opts/httpsig-key.md index c0a04051485f..7cd1779593c9 100644 --- a/docs/cmdline-opts/httpsig-key.md +++ b/docs/cmdline-opts/httpsig-key.md @@ -32,7 +32,8 @@ The key is formatted as a series of hexadecimal digits in a single line. For With OpenSSL 3: openssl genpkey -algorithm ED25519 -out k.pem - openssl pkey -in k.pem -outform RAW -out k.raw - xxd -p -c 64 k.raw | tr -d '\n' > k.hex + openssl pkey -in k.pem -text -noout | \ + grep -A3 ^priv: | \ + tr -cd '[:xdigit:]' > k.hex Use `@k.hex` with `--httpsig-key`. diff --git a/docs/cmdline-opts/no-clobber.md b/docs/cmdline-opts/no-clobber.md index 02a74b25e4d4..9225e2455128 100644 --- a/docs/cmdline-opts/no-clobber.md +++ b/docs/cmdline-opts/no-clobber.md @@ -18,9 +18,14 @@ Example: When used in conjunction with the --output, --remote-header-name, --remote-name, or --remote-name-all options, curl avoids overwriting files that already exist. Instead, a dot and a number gets appended to the name of -the file that would be created, up to filename.100 after which it does not +the file that would be created, up to filename.9999 after which it does not create any file. +Starting in curl 8.23.0, if the target filename uses an extension, curl +instead creates the new filename by inserting `-[num]` before the extension. +Downloading to `image.jpg` with --no-clobber, curl first tries to save it as +`image-1.jpg` then `image-2.jpg` etc. + Note that this is the negated option name documented. You can thus use --clobber to enforce the clobbering, even if --remote-header-name is specified. diff --git a/docs/cmdline-opts/output-dir.md b/docs/cmdline-opts/output-dir.md index 468ecc8a1176..4e63035698d7 100644 --- a/docs/cmdline-opts/output-dir.md +++ b/docs/cmdline-opts/output-dir.md @@ -12,6 +12,7 @@ See-also: - remote-header-name Example: - --output-dir "tmp" -O $URL + - --output-dir "tmp" -o filename.html $URL --- # `--output-dir` @@ -24,3 +25,7 @@ command line, up until the first --next. If the specified target directory does not exist, the operation fails unless --create-dirs is also used. + +The specified output directory is prepended to the destination filename that +would otherwise be used, including any directory component. No attempt is made +to neutralize `../` sequences or similar constructs in the resulting path. diff --git a/docs/cmdline-opts/retry-delay.md b/docs/cmdline-opts/retry-delay.md index dbc78e00d854..d89ee9f08107 100644 --- a/docs/cmdline-opts/retry-delay.md +++ b/docs/cmdline-opts/retry-delay.md @@ -26,3 +26,8 @@ By default, curl uses an exponentially increasing timeout between retries. Starting in curl 8.16.0, this option accepts a time as decimal number for parts of seconds. The decimal value needs to be provided using a dot (.) as decimal separator - not the local version even if it might be using another separator. + +curl complies with the Retry-After: response header if one was present to know +when to issue the next retry (added in 7.66.0). That means the server can +override the retry delay. To handle server retry delays that may be longer +than expected, you can use --retry-max-time to limit the total retry time. diff --git a/docs/cmdline-opts/retry.md b/docs/cmdline-opts/retry.md index 4d9e83cd5f40..ac9166ccf559 100644 --- a/docs/cmdline-opts/retry.md +++ b/docs/cmdline-opts/retry.md @@ -31,4 +31,6 @@ algorithm. See also --retry-max-time to limit the total time allowed for retries. curl complies with the Retry-After: response header if one was present to know -when to issue the next retry (added in 7.66.0). +when to issue the next retry (added in 7.66.0). That means the server can +override the retry delay. To handle server retry delays that may be longer +than expected, you can use --retry-max-time to limit the total retry time. diff --git a/docs/cmdline-opts/ssl-sessions.md b/docs/cmdline-opts/ssl-sessions.md index 33ef984a3ec4..8a0cc7da69c3 100644 --- a/docs/cmdline-opts/ssl-sessions.md +++ b/docs/cmdline-opts/ssl-sessions.md @@ -37,3 +37,14 @@ still check if a specific hostname matches one of the values. This feature requires that the underlying libcurl was built with the experimental SSL session import/export feature (SSLS-EXPORT) enabled. + +The sessions in the file identify origin and used TLS configurations. +Configuration parameters identified by a file path are only matched to the +path, not the file's content. Updates to CA, CRL or key file contents do +not invalidate previously saved sessions. + +Most TLS sessions handed out by servers have a short lifetime, which +limits the risk. Also, curl does not persist TLS session in this file +that are tied to authentication, e.g. a client certificate. If that all +is not acceptable, ssl session files should be cleared when older than +the CA/CRL file used or not used at all. diff --git a/docs/examples/Makefile.inc b/docs/examples/Makefile.inc index 8c85fb838232..78f5a1636f7c 100644 --- a/docs/examples/Makefile.inc +++ b/docs/examples/Makefile.inc @@ -29,16 +29,24 @@ check_PROGRAMS = \ address-scope \ altsvc \ anyauthput \ + aws-sigv4 \ block_ip \ + cainfo-blob \ certinfo \ chkspeed \ + clientcert-blob \ + conditional \ connect-to \ + cookie \ + cookiejar \ cookie_interface \ debug \ default-scheme \ + doh \ externalsocket \ fileupload \ ftp-delete \ + ftp-listonly \ ftp-wildcard \ ftpget \ ftpgetinfo \ @@ -65,6 +73,7 @@ check_PROGRAMS = \ httpput \ httpput-postfields \ https \ + httpsig \ imap-append \ imap-authzid \ imap-copy \ @@ -83,6 +92,7 @@ check_PROGRAMS = \ interface \ ipv6 \ keepalive \ + limit-rate \ localport \ log_failed_transfers \ maxconnects \ @@ -93,8 +103,10 @@ check_PROGRAMS = \ multi-legacy \ multi-post \ multi-single \ + nagle \ netrc \ parseurl \ + path-as-is \ persistent \ pop3-authzid \ pop3-dele \ @@ -134,6 +146,7 @@ check_PROGRAMS = \ smtp-vrfy \ sslbackend \ synctime \ + trailer \ unixsocket \ url2file \ urlapi \ diff --git a/docs/examples/aws-sigv4.c b/docs/examples/aws-sigv4.c new file mode 100644 index 000000000000..69b04f3acaf9 --- /dev/null +++ b/docs/examples/aws-sigv4.c @@ -0,0 +1,65 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Get a resource from an S3 service using AWS SigV4 authentication. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + /* Set the S3 object URL */ + curl_easy_setopt(curl, CURLOPT_URL, + "https://mybucket.s3.us-east-1.amazonaws.example/" + "myphoto.jpg"); + + /* Configure AWS SigV4: provider1:provider2:region:service */ + curl_easy_setopt(curl, CURLOPT_AWS_SIGV4, "aws:amz:us-east-1:s3"); + + /* Supply the AWS Access Key ID and Secret Access Key */ + curl_easy_setopt(curl, CURLOPT_USERPWD, + "AKIAIOSFODNN7EXAMPLE:" + "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/cacertinmem.c b/docs/examples/cacertinmem.c index 2e43a783f5dc..841f4447d259 100644 --- a/docs/examples/cacertinmem.c +++ b/docs/examples/cacertinmem.c @@ -26,6 +26,10 @@ * */ +/* This example uses the legacy and discouraged method. Use + CURLOPT_CAINFO_BLOB instead, which is TLS backend agnostic. See the + cainfo-blob.c example */ + /* Requires: USE_OPENSSL */ #include diff --git a/docs/examples/cainfo-blob.c b/docs/examples/cainfo-blob.c new file mode 100644 index 000000000000..2f57bef37992 --- /dev/null +++ b/docs/examples/cainfo-blob.c @@ -0,0 +1,76 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Use CURLOPT_CAINFO_BLOB to provide a CA certificate bundle from memory. + * + */ +#include +#include + +#include + +/* Replace with actual PEM-encoded CA certificate(s) */ +static char ca_pem[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIB/TCCAZSgAwIBAgIUQ4hO7G+Z+7y4G+Q5fK1u0y2K3MwwCgYIKoZIzj0EAwIw\n" + "EjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNDA5MDEwMDAwMDBaFw0zNDA5MDEwMDAw\n" + "MDBaMBIxEDAOBgNVBAMMB1Rlc3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" + "AARwS8N3R+W8d7a1q6Fz4e9j3H0q5r8s+2k6t1w5y4z8n0q2s3t4u5v6w7x8y9z0\n" + "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUjCCAQUw\n" + "DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU1234567890abcdef1234567890ab\n" + "-----END CERTIFICATE-----\n"; + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + struct curl_blob blob; + + /* Fill the curl_blob structure */ + blob.data = (void *)ca_pem; + blob.len = strlen(ca_pem); + blob.flags = CURL_BLOB_COPY; + + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* Pass CA bundle from memory buffer */ + curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/clientcert-blob.c b/docs/examples/clientcert-blob.c new file mode 100644 index 000000000000..916120b1b89e --- /dev/null +++ b/docs/examples/clientcert-blob.c @@ -0,0 +1,81 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Use CURLOPT_SSLCERT_BLOB to provide a client certificate from memory. + * + */ +#include +#include + +#include + +/* Replace with actual PEM-encoded client certificate */ +static char cert_pem[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIB/TCCAZSgAwIBAgIUQ4hO7G+Z+7y4G+Q5fK1u0y2K3MwwCgYIKoZIzj0EAwIw\n" + "EjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNDA5MDEwMDAwMDBaFw0zNDA5MDEwMDAw\n" + "MDBaMBIxEDAOBgNVBAMMB1Rlc3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\n" + "AARwS8N3R+W8d7a1q6Fz4e9j3H0q5r8s+2k6t1w5y4z8n0q2s3t4u5v6w7x8y9z0\n" + "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUjCCAQUw\n" + "DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU1234567890abcdef1234567890ab\n" + "-----END CERTIFICATE-----\n"; + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + struct curl_blob cert_blob; + + /* Fill the curl_blob structure for the client certificate */ + cert_blob.data = (void *)cert_pem; + cert_blob.len = strlen(cert_pem); + cert_blob.flags = CURL_BLOB_COPY; + + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* Pass client certificate from memory */ + curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &cert_blob); + curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM"); + + /* Specify the private key file or use CURLOPT_SSLKEY_BLOB */ + curl_easy_setopt(curl, CURLOPT_SSLKEY, "client.key"); + curl_easy_setopt(curl, CURLOPT_KEYPASSWD, "s3cret"); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/conditional.c b/docs/examples/conditional.c new file mode 100644 index 000000000000..0d930179a2ad --- /dev/null +++ b/docs/examples/conditional.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Use CURLOPT_TIMEVALUE to perform a conditional HTTP request. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + long unmet = 0; + + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* January 1, 2020 00:00:00 UTC */ + curl_easy_setopt(curl, CURLOPT_TIMEVALUE, 1577836800L); + + /* If-Modified-Since the above timestamp */ + curl_easy_setopt(curl, CURLOPT_TIMECONDITION, + (long)CURL_TIMECOND_IFMODSINCE); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + } + else { + /* Check whether the condition was met or unmet + (e.g. 304 Not Modified) */ + if(curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &unmet) == CURLE_OK) + printf("Condition was %sfulfilled\n", unmet ? "NOT " : ""); + } + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/cookie.c b/docs/examples/cookie.c new file mode 100644 index 000000000000..413a49925b3d --- /dev/null +++ b/docs/examples/cookie.c @@ -0,0 +1,59 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Send specific cookies + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* pass on exactly the cookies we want */ + curl_easy_setopt(curl, CURLOPT_COOKIE, "tool=curl; fun=yes;"); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/cookiejar.c b/docs/examples/cookiejar.c new file mode 100644 index 000000000000..836062e7b943 --- /dev/null +++ b/docs/examples/cookiejar.c @@ -0,0 +1,63 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * HTTP with cookies from and to a jar + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* get cookies from an existing file */ + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookie-read.txt"); + + /* export cookies to this file when closing the handle. Can be the + same file as we read from, or a different one */ + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookie-write.txt"); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/crawler.c b/docs/examples/crawler.c index 595375614eb2..46f4ebb531ed 100644 --- a/docs/examples/crawler.c +++ b/docs/examples/crawler.c @@ -136,13 +136,20 @@ static size_t follow_links(CURLM *multi, struct memory *mem, const char *url) return 0; xpath = (xmlChar *)"//a/@href"; context = xmlXPathNewContext(doc); + if(!context) { + xmlFreeDoc(doc); + return 0; + } object = xmlXPathEvalExpression(xpath, context); xmlXPathFreeContext(context); - if(!object) + if(!object) { + xmlFreeDoc(doc); return 0; + } nodeset = object->nodesetval; if(xmlXPathNodeSetIsEmpty(nodeset)) { xmlXPathFreeObject(object); + xmlFreeDoc(doc); return 0; } count = 0; @@ -158,16 +165,22 @@ static size_t follow_links(CURLM *multi, struct memory *mem, const char *url) xmlFree(orig); } link = (char *)href; - if(!link || strlen(link) < 20) + if(!link || strlen(link) < 20) { + xmlFree(link); continue; + } if(!strncmp(link, "http://", 7) || !strncmp(link, "https://", 8)) { - curl_multi_add_handle(multi, make_handle(link)); - if(count++ == max_link_per_page) + if(count >= max_link_per_page) { + xmlFree(link); break; + } + curl_multi_add_handle(multi, make_handle(link)); + count++; } xmlFree(link); } xmlXPathFreeObject(object); + xmlFreeDoc(doc); return count; } diff --git a/docs/examples/doh.c b/docs/examples/doh.c new file mode 100644 index 000000000000..a96820011cde --- /dev/null +++ b/docs/examples/doh.c @@ -0,0 +1,60 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Use CURLOPT_DOH_URL to resolve hostnames using DNS-over-HTTPS. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + /* Set the target URL to fetch */ + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* Specify the DNS-over-HTTPS (DoH) server URL used to resolve names */ + curl_easy_setopt(curl, CURLOPT_DOH_URL, + "https://cloudflare-dns.com/dns-query"); + + /* Perform the request using the configured DoH resolver */ + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* Always cleanup easy handle */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/ftp-listonly.c b/docs/examples/ftp-listonly.c new file mode 100644 index 000000000000..c407bb40a6a2 --- /dev/null +++ b/docs/examples/ftp-listonly.c @@ -0,0 +1,58 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * List only filenames of a remote FTP directory using CURLOPT_DIRLISTONLY. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + /* Set the FTP directory URL (trailing slash designates a directory) */ + curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com/dir/"); + + /* Request names only (causes NLST to be sent instead of LIST) */ + curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/httpput.c b/docs/examples/httpput.c index 90cb81b7de22..0c13490cc36f 100644 --- a/docs/examples/httpput.c +++ b/docs/examples/httpput.c @@ -129,6 +129,10 @@ int main(int argc, const char *argv[]) curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_info.st_size); + /* ask libcurl to use a larger size for the upload buffer */ + curl_easy_setopt(curl, CURLOPT_UPLOAD_BUFFERSIZE, + (long)400000); + /* Now run off and do what you have been told! */ result = curl_easy_perform(curl); /* Check for errors */ diff --git a/docs/examples/httpsig.c b/docs/examples/httpsig.c new file mode 100644 index 000000000000..6d9ebb36ac0e --- /dev/null +++ b/docs/examples/httpsig.c @@ -0,0 +1,78 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Send an HTTP request signed with RFC 9421 HTTP Message Signatures. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + struct curl_slist *headers = NULL; + + /* Add a custom header that gets signed */ + headers = curl_slist_append(NULL, "Content-Type: application/json"); + if(headers) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + /* Select signing algorithm (e.g. Ed25519) */ + curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, CURLHTTPSIG_ED25519); + + /* Hex-encoded 32-byte Ed25519 seed */ + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY, + "9f8362f87a484a954e6e740c5b4c0e84" + "229139a20aa8ab56ff66586f6a7d29c5"); + + /* Key identifier sent in Signature-Input */ + curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id"); + + /* Components and headers to include in the signature */ + curl_easy_setopt(curl, CURLOPT_HTTPSIG_HEADERS, + "method authority path content-type:"); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + curl_slist_free_all(headers); + } + + /* always cleanup */ + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/limit-rate.c b/docs/examples/limit-rate.c new file mode 100644 index 000000000000..b0a72859d370 --- /dev/null +++ b/docs/examples/limit-rate.c @@ -0,0 +1,59 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Download with network speed limit + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* limit the transfer speed to no more than this many bytes per second */ + curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)40000); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/multi-double.c b/docs/examples/multi-double.c index 987f1c05689d..4809abb48e7d 100644 --- a/docs/examples/multi-double.c +++ b/docs/examples/multi-double.c @@ -80,11 +80,9 @@ int main(void) do { msg = curl_multi_info_read(multi, &queued); - if(msg) { - if(msg->msg == CURLMSG_DONE) { - /* a transfer ended */ - fprintf(stderr, "Transfer completed\n"); - } + if(msg && msg->msg == CURLMSG_DONE) { + /* a transfer ended */ + fprintf(stderr, "Transfer completed\n"); } } while(msg); } diff --git a/docs/examples/nagle.c b/docs/examples/nagle.c new file mode 100644 index 000000000000..71ad00b9cdd0 --- /dev/null +++ b/docs/examples/nagle.c @@ -0,0 +1,59 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Switch on the TCP Nagle algorithm with CURLOPT_TCP_NODELAY + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* leave Nagle enabled */ + curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 0L); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/path-as-is.c b/docs/examples/path-as-is.c new file mode 100644 index 000000000000..112c8e0ccba9 --- /dev/null +++ b/docs/examples/path-as-is.c @@ -0,0 +1,60 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Keep dot-dot sequences in the path in HTTP request. + * + */ +#include + +#include + +int main(void) +{ + CURL *curl; + + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, + "https://example.com/../../etc/passwd"); + + /* we actually want to keep the ../ sequence in the path */ + curl_easy_setopt(curl, CURLOPT_PATH_AS_IS, 1L); + + /* Perform the request, result gets the return code */ + result = curl_easy_perform(curl); + /* Check for errors */ + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return 0; +} diff --git a/docs/examples/trailer.c b/docs/examples/trailer.c new file mode 100644 index 000000000000..8ee870cfa457 --- /dev/null +++ b/docs/examples/trailer.c @@ -0,0 +1,95 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * Send HTTP POST with trailing headers using CURLOPT_TRAILERFUNCTION. + * + */ +#include +#include + +#include + +/* Callback invoked right before the final CRLF in chunked transfer */ +static int trailer_cb(struct curl_slist **list, void *userdata) +{ + struct curl_slist *nlist; + (void)userdata; + + /* Append the trailing header; libcurl frees the slist on completion */ + nlist = curl_slist_append(*list, "Checksum: 0123456789abcdef"); + if(!nlist) + return CURL_TRAILERFUNC_ABORT; + + *list = nlist; + return CURL_TRAILERFUNC_OK; +} + +int main(void) +{ + CURL *curl; + CURLcode result = curl_global_init(CURL_GLOBAL_ALL); + if(result != CURLE_OK) + return (int)result; + + curl = curl_easy_init(); + if(curl) { + static const char post_data[] = "field1=value1&field2=value2"; + struct curl_slist *headers = NULL; + struct curl_slist *nheaders; + + /* Declare expected trailers and force chunked transfer-encoding */ + nheaders = curl_slist_append(headers, "Trailer: Checksum"); + if(nheaders) { + headers = nheaders; + nheaders = curl_slist_append(headers, "Transfer-Encoding: chunked"); + } + + if(nheaders) { + headers = nheaders; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/upload"); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + /* Configure the POST data */ + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, + (long)sizeof(post_data) - 1); + + /* Set callback to populate trailing headers */ + curl_easy_setopt(curl, CURLOPT_TRAILERFUNCTION, trailer_cb); + curl_easy_setopt(curl, CURLOPT_TRAILERDATA, NULL); + + result = curl_easy_perform(curl); + if(result != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(result)); + } + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + return (int)result; +} diff --git a/docs/examples/usercertinmem.c b/docs/examples/usercertinmem.c index 3e7865780f15..d9e8e40b5532 100644 --- a/docs/examples/usercertinmem.c +++ b/docs/examples/usercertinmem.c @@ -26,11 +26,10 @@ * */ -/* Written by Ishan SinghLevett, based on Theo Borm's cacertinmem.c. Note that - * to maintain simplicity this example does not use a CA certificate for peer - * verification. Some form of peer verification must be used in real - * circumstances when a secure connection is required. - */ +/* This example uses the legacy and discouraged method. It also disables + server certificate verification; real applications must verify the peer. + Use CURLOPT_SSLCERT_BLOB instead, which is TLS backend agnostic. See the + clientcert-blob.c example. */ /* Requires: USE_OPENSSL */ diff --git a/docs/examples/websocket.c b/docs/examples/websocket.c index 7ae299f4ed6b..ad6fc9e0e827 100644 --- a/docs/examples/websocket.c +++ b/docs/examples/websocket.c @@ -77,10 +77,9 @@ static CURLcode recv_pong(CURL *curl, const char *expected_payload) * in chunks, however. */ if(meta->flags & CURLWS_PONG) { int same = 0; - if(rlen == strlen(expected_payload)) { - if(!memcmp(expected_payload, buffer, rlen)) - same = 1; - } + if(rlen == strlen(expected_payload) && + !memcmp(expected_payload, buffer, rlen)) + same = 1; fprintf(stderr, "ws: received PONG with %s payload back\n", same ? "same" : "different"); } diff --git a/docs/internals/CLIENT-READERS.md b/docs/internals/CLIENT-READERS.md index 11cf86da6857..8b1040698822 100644 --- a/docs/internals/CLIENT-READERS.md +++ b/docs/internals/CLIENT-READERS.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: curl # curl client readers Client readers is a design in the internals of libcurl, not visible in its -public API. They were started in curl v8.7.0. This document describes +public API. They were started in curl 8.7.0. This document describes the concepts, its high level implementation and the motivations. ## Naming diff --git a/docs/internals/CLIENT-WRITERS.md b/docs/internals/CLIENT-WRITERS.md index e00cd263be63..4e28d188ab0e 100644 --- a/docs/internals/CLIENT-WRITERS.md +++ b/docs/internals/CLIENT-WRITERS.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: curl # curl client writers Client writers is a design in the internals of libcurl, not visible in its -public API. They were started in curl v8.5.0. This document describes the +public API. They were started in curl 8.5.0. This document describes the concepts, its high level implementation and the motivations. ## Naming diff --git a/docs/internals/CONNECTION-FILTERS.md b/docs/internals/CONNECTION-FILTERS.md index 1a817a15672a..515624a9fad5 100644 --- a/docs/internals/CONNECTION-FILTERS.md +++ b/docs/internals/CONNECTION-FILTERS.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: curl # curl connection filters Connection filters is a design in the internals of curl, not visible in its -public API. They were added in curl v7.87.0. This document describes the +public API. They were added in curl 7.87.0. This document describes the concepts, its high level implementation and the motivations. ## Filters diff --git a/docs/internals/HASH.md b/docs/internals/HASH.md index 9534eb7d1ac9..03be407328bc 100644 --- a/docs/internals/HASH.md +++ b/docs/internals/HASH.md @@ -19,8 +19,7 @@ Create a hash table. Add items. Retrieve items. Remove items. Destroy table. ~~~c void Curl_hash_init(struct Curl_hash *h, size_t slots, - hash_function hfunc, - comp_function comparator, + Curl_hash_type type, Curl_hash_dtor dtor); ~~~ @@ -28,15 +27,12 @@ The call initializes a `struct Curl_hash`. - `slots` is the number of entries to create in the hash table. Larger is better (faster lookups) but also uses more memory. -- `hfunc` is a function pointer to a function that returns a `size_t` value as - a checksum for an entry in this hash table. Ideally, it returns a unique - value for every entry ever added to the hash table, but hash collisions are - handled. -- `comparator` is a function pointer to a function that compares two hash - table entries. It should return non-zero if the compared items are - identical. +- `type` selects how keys are hashed and compared. `CURL_HASH_TYPE_BYTES` + treats keys as exact-length byte sequences. `CURL_HASH_TYPE_SOCKET` treats + keys as native `curl_socket_t` values and requires `key_len` to equal + `sizeof(curl_socket_t)`. - `dtor` is a function pointer to a destructor called when an entry is removed - from the table + from the table. ## `Curl_hash_add` diff --git a/docs/internals/LLIST.md b/docs/internals/LLIST.md index b77e86598f45..8daf58a36d4a 100644 --- a/docs/internals/LLIST.md +++ b/docs/internals/LLIST.md @@ -28,7 +28,7 @@ list. There are two functions for adding a node to a linked list: 1. Add it last in the list with `Curl_llist_append` -2. Add it after a specific existing node with `Curl_llist_insert_next` +2. Add it after a specific existing node with `llist_insert_next` When a node is added to a list, it stores an associated custom pointer to anything you like and you provide a pointer to a `struct Curl_llist_node` @@ -60,7 +60,7 @@ it: /* add the first struct to the list */ Curl_llist_append(&barlist, &entries[0], &entries[0].storage); -See also `Curl_llist_insert_next`. +See also `llist_insert_next`. ## Remove a node @@ -134,17 +134,17 @@ Adds `node` last in the `list` with a custom pointer to `elem`. The function is infallible. -## `Curl_llist_insert_next` +## `llist_insert_next` ~~~c -void Curl_llist_insert_next(struct Curl_llist *list, - struct Curl_llist_node *node, - const void *elem, - struct Curl_llist_node *node); +void llist_insert_next(struct Curl_llist *list, + struct Curl_llist_node *before, + const void *elem, + struct Curl_llist_node *node); ~~~ Adds `node` to the `list` with a custom pointer to `elem` immediately after -the previous list `node`. +the previous list `before`. The function is infallible. diff --git a/docs/internals/MID.md b/docs/internals/MID.md index 5f1105008dbf..fddc3d3a2fab 100644 --- a/docs/internals/MID.md +++ b/docs/internals/MID.md @@ -52,8 +52,9 @@ The table is implemented in `uint-table.[ch]`. More details in [`UINT_SETS`](UIN There are several places where transfers need to be tracked: -* the multi tracks `process`, `pending` and `msgsent` transfers. A transfer - is in at most one of these at a time. +* the multi tracks `process`, `pending` and `msgsent` transfers. A transfer is + in at most one of these at a time. A `dirty` set, which may overlap with + `process`, tracks transfers that need to run immediately. * connections track the transfers that are *attached* to them. * multi event handling tracks transfers interested in a specific socket. * DoH handles track the handle they perform lookups for (and vice versa). @@ -63,9 +64,9 @@ The first is a bitset optimal for storing a large number of unsigned int values. The second one is a "sparse" variant good for storing a small set of numbers. More details about these in [`UINT_SETS`](UINT_SETS.md). -A multi uses `uint_bset`s for `process`, `pending` and `msgsent`. Connections -and sockets use the sparse variant as both often track only a single transfer -and at most 100 on an HTTP/2 or HTTP/3 connection/socket. +A multi uses `uint_bset`s for `process`, `dirty`, `pending` and `msgsent`. +Connections and sockets use the sparse variant as both often track only a +single transfer and at most 100 on an HTTP/2 or HTTP/3 connection/socket. These sets allow safe iteration while being modified. This allows a multi to iterate over its "process" set while existing transfers are removed diff --git a/docs/internals/SPLAY.md b/docs/internals/SPLAY.md index 4a9e49940c5e..cb7537487426 100644 --- a/docs/internals/SPLAY.md +++ b/docs/internals/SPLAY.md @@ -45,21 +45,21 @@ multi handle carries a "base timestamp", set once when created, and the timeout values are calculated relative to that. This works for about 500,000 years of continued operation of a multi handle. -## `Curl_splay` +## `splay` ~~~c -struct Curl_tree *Curl_splay(struct curltime i, struct Curl_tree *t); +struct Curl_tree *splay(timediff_t key, struct Curl_tree *t); ~~~ -Rearranges the tree `t` after the provide time `i`. +Rearranges the tree `t` after the provide time `key`. -## `Curl_splayinsert` +## `splayinsert` ~~~c -struct Curl_tree *Curl_splayinsert(timediff_t key, - struct Curl_tree *t, - struct Curl_tree *node, - uint32_t id); +struct Curl_tree *splayinsert(timediff_t key, + struct Curl_tree *t, + struct Curl_tree *node, + uint32_t id); ~~~ This function inserts a new `node` in the tree, using the given `key` @@ -72,12 +72,12 @@ has that arranged. It returns a pointer to the new tree root. -## `Curl_splaygetbest` +## `splaygetbest` ~~~c -struct Curl_tree *Curl_splaygetbest(timediff_t key, - struct Curl_tree *tree, - struct Curl_tree **removed); +struct Curl_tree *splaygetbest(timediff_t key, + struct Curl_tree *tree, + struct Curl_tree **removed); ~~~ If there is a node in the `tree` that has a timeout that is less than the @@ -86,12 +86,12 @@ in the `*removed` pointer (or NULL if there was no match). It returns a pointer to the new tree root. -## `Curl_splayremove` +## `splayremove` ~~~c -int Curl_splayremove(struct Curl_tree *tree, - struct Curl_tree *node, - struct Curl_tree **newroot); +int splayremove(struct Curl_tree *tree, + struct Curl_tree *node, + struct Curl_tree **newroot); ~~~ Removes a given `node` from a splay `tree`, and returns the `newroot` @@ -99,15 +99,6 @@ identifying the new tree root. Note that a clean tree without any nodes present implies a NULL pointer. -## `Curl_splayset` - -~~~c -void Curl_splayset(struct Curl_tree *node, uint32_t id); -~~~ - -Sets the `id` in the splay node. This value is not used -by the splay code itself and can be retrieved again with `Curl_splayget`. - ## `Curl_splayget` ~~~c diff --git a/docs/internals/UINT_SETS.md b/docs/internals/UINT_SETS.md index 963cc82d48fe..22ba858c6105 100644 --- a/docs/internals/UINT_SETS.md +++ b/docs/internals/UINT_SETS.md @@ -51,7 +51,7 @@ This iteration has the following properties: ### Memory For storing 1000 entries, the table would allocate one block of 8KB on -a 64-bit system, plus the 2 pointers and 3 `uint32_t` in its base `struct +a 64-bit system, plus the pointer and 3 `uint32_t` in its base `struct uint32_tbl`. A resize allocates a completely new pointer array, copy the existing entries and free the previous one. diff --git a/docs/libcurl/curl_easy_ssls_import.md b/docs/libcurl/curl_easy_ssls_import.md index 3e43972f7053..c15498459148 100644 --- a/docs/libcurl/curl_easy_ssls_import.md +++ b/docs/libcurl/curl_easy_ssls_import.md @@ -44,6 +44,18 @@ Import of session tickets from other curl versions may fail due to changes in the handling of **shmac** or **sdata**. A session ticket which has already expired is silently discarded. +The session key identifies origin and used TLS configurations. Configuration +parameters identified by a file path are only matched to the path, not the +file's content. It is the applications responsibility to handle updates to +CA, CRL or key files, e.g. discarding previous sessions. Otherwise, +imported sessions may establish trusted connections to servers that would fail +to verify otherwise. + +Most TLS sessions handed out by servers have a short lifetime. If +continued use of these sessions is acceptable can only be decided by +the application. Also, TLS session that are tied to client authentication, +e.g. a client certificate, are never exported by libcurl. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/curl_formadd.md b/docs/libcurl/curl_formadd.md index 354957bfbec6..d98469578f98 100644 --- a/docs/libcurl/curl_formadd.md +++ b/docs/libcurl/curl_formadd.md @@ -118,6 +118,10 @@ followed by a long giving the length of the name. Pass this option to set the length of *CURLFORM_COPYNAME* and *CURLFORM_PTRNAME* strings, if they are not null-terminated. +If you pass a 0 (zero) for this option, libcurl calls strlen() on the name to +figure out the size. If you really want to send a zero byte name then you must +make sure strlen() on the name pointer returns zero. + ## CURLFORM_FILECONTENT followed by a filename, causes that file to be read and its contents used diff --git a/docs/libcurl/curl_global_init.md b/docs/libcurl/curl_global_init.md index c57daa534f22..9347bbb5aea5 100644 --- a/docs/libcurl/curl_global_init.md +++ b/docs/libcurl/curl_global_init.md @@ -84,22 +84,26 @@ backend libcurl uses. Doing TLS based transfers without having the TLS layer initialized may lead to unexpected behaviors. -## CURL_GLOBAL_WIN32 +## CURL_GLOBAL_WINSOCK -Initialize the Win32 socket libraries. +Initialize the Windows sockets library (Winsock). The implication here is that if this bit is not set, the initialization of Winsock has to be done by the application or you risk getting undefined behaviors. This option exists for when the initialization is handled outside of libcurl so there is no need for libcurl to do it again. +Prior to 8.23.0 this flag was called CURL_GLOBAL_WIN32. The name was changed +to eliminate confusion. Most of libcurl's Windows initialization is required +and is not controlled by this flag. + ## CURL_GLOBAL_NOTHING Initialize nothing extra. This sets no bit. ## CURL_GLOBAL_DEFAULT -A sensible default. It initializes both SSL and Win32. Right now, this equals +A sensible default. It initializes both SSL and Winsock. Right now, this equals the functionality of the **CURL_GLOBAL_ALL** mask. ## CURL_GLOBAL_ACK_EINTR diff --git a/docs/libcurl/curl_mime_data_cb.md b/docs/libcurl/curl_mime_data_cb.md index d5965c86c769..ba1a34ac6804 100644 --- a/docs/libcurl/curl_mime_data_cb.md +++ b/docs/libcurl/curl_mime_data_cb.md @@ -135,7 +135,7 @@ static size_t read_callback(char *buffer, size_t size, size_t nitems, static int seek_callback(void *arg, curl_off_t offset, int origin) { - struct ctl *p = (struct ctl *) arg; + struct ctl *p = (struct ctl *)arg; switch(origin) { case SEEK_END: diff --git a/docs/libcurl/curl_mprintf.md b/docs/libcurl/curl_mprintf.md index a5a6c9a2c5b6..dc778b595824 100644 --- a/docs/libcurl/curl_mprintf.md +++ b/docs/libcurl/curl_mprintf.md @@ -288,3 +288,6 @@ a newly allocated string, or NULL if it failed. All other functions return the number of characters actually printed (excluding the null byte used to end output to strings). Note that this sometimes differ from how the POSIX versions of these functions work. + +Since libcurl 8.23.0 curl_mfprintf, curl_mprintf, curl_mvfprintf and +curl_mvprintf return -1 when the output fails. diff --git a/docs/libcurl/curl_multi_perform.md b/docs/libcurl/curl_multi_perform.md index e0335d81a868..64ec10c0f4cd 100644 --- a/docs/libcurl/curl_multi_perform.md +++ b/docs/libcurl/curl_multi_perform.md @@ -5,6 +5,7 @@ Title: curl_multi_perform Section: 3 Source: libcurl See-also: + - CURLMOPT_NOTIFYFUNCTION (3) - curl_multi_add_handle (3) - curl_multi_cleanup (3) - curl_multi_fdset (3) @@ -53,8 +54,12 @@ added handle fails quickly, it may never be counted as a running_handle. You could use curl_multi_info_read(3) to track actual status of the added handles in that case. +CURLMOPT_NOTIFYFUNCTION(3) is an alternative to tracking *running_handles* +where you register a callback to be notified when transfers are done and +curl_multi_info_read(3) should be called. + When *running_handles* is set to zero (0) on the return of this function, -there is no longer any transfers in progress. +there are no longer any transfers in progress. When this function returns error, the state of all transfers are uncertain and they cannot be continued. curl_multi_perform(3) should not be called diff --git a/docs/libcurl/curl_multi_remove_handle.md b/docs/libcurl/curl_multi_remove_handle.md index fed6439c4489..a8ecdfec64c6 100644 --- a/docs/libcurl/curl_multi_remove_handle.md +++ b/docs/libcurl/curl_multi_remove_handle.md @@ -40,6 +40,10 @@ handles and transfers remain unaffected. It is fine to remove a handle at any time during a transfer, but not from within any libcurl callback function. +An internal handle, such as one passed to a CURLMOPT_NOTIFYFUNCTION(3) +callback, cannot be removed and makes this function return +CURLM_BAD_EASY_HANDLE. + Removing an easy handle from the multi handle before the corresponding transfer is complete might cause libcurl to close the connection - if the state of it and the internal protocol handler deem it necessary. Otherwise diff --git a/docs/libcurl/curl_url_dup.md b/docs/libcurl/curl_url_dup.md index cfba5cc1b93e..620849444035 100644 --- a/docs/libcurl/curl_url_dup.md +++ b/docs/libcurl/curl_url_dup.md @@ -56,4 +56,5 @@ int main(void) # RETURN VALUE -Returns a pointer to a new `CURLU` handle or NULL if out of memory. +Returns a pointer to a new `CURLU` handle or NULL if out of memory or if +*inhandle* is NULL. diff --git a/docs/libcurl/curl_url_set.md b/docs/libcurl/curl_url_set.md index 35ed109fcd53..b2f818cf063e 100644 --- a/docs/libcurl/curl_url_set.md +++ b/docs/libcurl/curl_url_set.md @@ -131,6 +131,10 @@ you also set the CURLU_URLENCODE flag. Unless *CURLU_NO_AUTHORITY* is set, a blank hostname is not allowed to set. +A numeric IPv4 address (in dotted decimal or a legacy form such as a single +decimal, octal or hexadecimal number) is normalized to dotted decimal, the +same way it is when libcurl parses a full URL. + ## CURLUPART_ZONEID If the hostname is a numeric IPv6 address, this field can also be set. diff --git a/docs/libcurl/libcurl-env-dbg.md b/docs/libcurl/libcurl-env-dbg.md index 7845f5c0f5d5..49741ce5a21b 100644 --- a/docs/libcurl/libcurl-env-dbg.md +++ b/docs/libcurl/libcurl-env-dbg.md @@ -224,3 +224,11 @@ This allows testing of connection reuse in mixed `STARTTLS` needs. ## `CURL_DBG_SUPPRESS_CONNECT_HDS` Existence of this variable suppresses the collection of CONNECT headers. + +## `CURL_DBG_MAX_TOTAL_CONNECTIONS` + +Set the `max_total_connections` for a `multi` handle. + +## `CURL_DBG_FTP_WILDCARD` + +Simulate CURLOPT_WILDCARDMATCH being set. diff --git a/docs/libcurl/libcurl-multi.md b/docs/libcurl/libcurl-multi.md index da1331fae82c..0d56dac769bd 100644 --- a/docs/libcurl/libcurl-multi.md +++ b/docs/libcurl/libcurl-multi.md @@ -178,3 +178,7 @@ the future, you should be aware of the following current restrictions: - Name resolves unless the c-ares or threaded-resolver backends are used - `file://` transfers - TELNET transfers +- FTP, IMAP, SMTP, SCP and SFTP wait for the final server response before the +transfer is reported as done +- FTP, IMAP, POP3, SMTP, SCP and SFTP can block during protocol-specific +shutdown when a connection is closed instead of kept for reuse diff --git a/docs/libcurl/libcurl-tutorial.md b/docs/libcurl/libcurl-tutorial.md index befcf7739968..227f71a76299 100644 --- a/docs/libcurl/libcurl-tutorial.md +++ b/docs/libcurl/libcurl-tutorial.md @@ -109,10 +109,10 @@ initialize. Using *CURL_GLOBAL_ALL* makes it initialize all known internal sub modules, and might be a good default option. The current two bits that are specified are: -## CURL_GLOBAL_WIN32 +## CURL_GLOBAL_WINSOCK which only does anything on Windows machines. When used on a Windows machine, -it makes libcurl initialize the Win32 socket stuff. Without having that +it makes libcurl initialize the Windows socket stuff. Without having that initialized properly, your program cannot use sockets properly. You should only do this once for each application, so if your program already does this or of another library in use does it, you should not tell libcurl to do this diff --git a/docs/libcurl/opts/CURLINFO_EFFECTIVE_URL.md b/docs/libcurl/opts/CURLINFO_EFFECTIVE_URL.md index f7adcd810b63..d04a12976a07 100644 --- a/docs/libcurl/opts/CURLINFO_EFFECTIVE_URL.md +++ b/docs/libcurl/opts/CURLINFO_EFFECTIVE_URL.md @@ -29,13 +29,16 @@ CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_EFFECTIVE_URL, char **urlp); Pass in a pointer to a char pointer and get the last used effective URL. -In cases when you have asked libcurl to follow redirects, it may not be the same -value you set with CURLOPT_URL(3). +In cases when you have asked libcurl to follow redirects, it may not be the +same value you set with CURLOPT_URL(3). The **urlp** pointer is NULL or points to private memory. You **must not** free it. It memory gets freed automatically when you call curl_easy_cleanup(3) on the corresponding curl handle. +The returned string has limited life-time. It might get destroyed at the next +libcurl function call involving this same *handle*. + # %PROTOCOLS% # EXAMPLE diff --git a/docs/libcurl/opts/CURLINFO_PROTOCOL.md b/docs/libcurl/opts/CURLINFO_PROTOCOL.md index 3f9346fa049b..12a82c4da762 100644 --- a/docs/libcurl/opts/CURLINFO_PROTOCOL.md +++ b/docs/libcurl/opts/CURLINFO_PROTOCOL.md @@ -40,8 +40,8 @@ CURLPROTO_GOPHER, CURLPROTO_HTTP, CURLPROTO_HTTPS, CURLPROTO_IMAP, CURLPROTO_IMAPS, CURLPROTO_LDAP, CURLPROTO_LDAPS, CURLPROTO_POP3, CURLPROTO_POP3S, CURLPROTO_RTMP, CURLPROTO_RTMPE, CURLPROTO_RTMPS, CURLPROTO_RTMPT, CURLPROTO_RTMPTE, CURLPROTO_RTMPTS, CURLPROTO_RTSP, -CURLPROTO_SCP, CURLPROTO_SFTP, CURLPROTO_SMB, CURLPROTO_SMBS, CURLPROTO_SMTP, -CURLPROTO_SMTPS, CURLPROTO_TELNET, CURLPROTO_TFTP, CURLPROTO_MQTT +CURLPROTO_SCP, CURLPROTO_SFTP, CURLPROTO_SMTP, CURLPROTO_SMTPS, +CURLPROTO_TELNET, CURLPROTO_TFTP, CURLPROTO_MQTT ~~~ # %PROTOCOLS% diff --git a/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md b/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md index 0e10aa918787..cd57682c24af 100644 --- a/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md +++ b/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md @@ -51,6 +51,11 @@ and curl_multi_cleanup(3) it may call all other methods on the multi and easy handles. This includes adding and removing easy handles to/from the multi handle. +Internal handles are owned by libcurl and the public API rejects them: the +easy interface functions fail with CURLE_BAD_FUNCTION_ARGUMENT, and +curl_multi_add_handle(3) and curl_multi_remove_handle(3) return +CURLM_BAD_EASY_HANDLE. + This callback may get invoked at any time when interacting with libcurl. This may even happen after all transfers are done and *may also* happen *during* a call to curl_multi_cleanup(3) when cached connections diff --git a/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md index 1336133cde0b..c9b587d91960 100644 --- a/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md +++ b/docs/libcurl/opts/CURLMOPT_PUSHFUNCTION.md @@ -44,9 +44,13 @@ pushes are denied automatically. The callback gets its arguments like this: *parent* is the handle of the stream on which this push arrives. The new -handle has been duplicated from the parent, meaning that it has gotten all its -options inherited. It is then up to the application to alter any options if -desired. +handle has the following options duplicated from the parent: +CURLOPT_WRITEFUNCTION(3), CURLOPT_WRITEDATA(3), CURLOPT_HEADERFUNCTION(3), +CURLOPT_HEADERDATA(3), CURLOPT_DEBUGFUNCTION(3), CURLOPT_DEBUGDATA(3), +CURLOPT_PROGRESSFUNCTION(3), CURLOPT_XFERINFOFUNCTION(3), +CURLOPT_PROGRESSDATA(3) and CURLOPT_XFERINFODATA(3). + +It is then up to the application to alter/set any options if desired. *easy* is a newly created handle that represents this upcoming transfer. diff --git a/docs/libcurl/opts/CURLOPT_ABSTRACT_UNIX_SOCKET.md b/docs/libcurl/opts/CURLOPT_ABSTRACT_UNIX_SOCKET.md index d13cf7c18076..48167ae323dd 100644 --- a/docs/libcurl/opts/CURLOPT_ABSTRACT_UNIX_SOCKET.md +++ b/docs/libcurl/opts/CURLOPT_ABSTRACT_UNIX_SOCKET.md @@ -30,9 +30,9 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_ABSTRACT_UNIX_SOCKET, Enables the use of an abstract Unix domain socket instead of establishing a TCP connection to a host. The parameter should be a char * to a null-terminated string holding the path of the socket. The path is set to -*path* prefixed by a NULL byte. This is the convention for abstract +*path* prefixed by a null byte. This is the convention for abstract sockets, however it should be stressed that the path passed to this function -should not contain a leading NULL byte. +should not contain a leading null byte. On non-supporting platforms, the abstract address is interpreted as an empty string and fails gracefully, generating a runtime error. diff --git a/docs/libcurl/opts/CURLOPT_CAINFO.md b/docs/libcurl/opts/CURLOPT_CAINFO.md index 86183677284d..999c9c87ee1c 100644 --- a/docs/libcurl/opts/CURLOPT_CAINFO.md +++ b/docs/libcurl/opts/CURLOPT_CAINFO.md @@ -55,6 +55,10 @@ previous ones. Set it to NULL to disable its use again. The default value for this can be figured out with CURLINFO_CAINFO(3). +For LDAP, this option only has an effect when using the OpenLDAP backend, +which routes TLS through libcurl. It is not supported by Apple's OpenLDAP +fork. WinLDAP uses the Windows CA store for root certificates. + # DEFAULT Built-in system specific. When curl is built with Schannel, this option is not diff --git a/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md b/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md index 1baf35ca0579..d5e8c75d852e 100644 --- a/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md +++ b/docs/libcurl/opts/CURLOPT_DEFAULT_PROTOCOL.md @@ -29,13 +29,13 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_DEFAULT_PROTOCOL, # DESCRIPTION -This option tells libcurl to use *protocol* if the URL is missing a scheme -name. +This option tells libcurl to use *protocol* if the URL set with CURLOPT_URL(3) +misses a scheme name. Use one of these protocol (scheme) names: dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, -pop3s, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp +pop3s, rtsp, scp, sftp, smtp, smtps, telnet, tftp An unknown or unsupported protocol causes error *CURLE_UNSUPPORTED_PROTOCOL* when libcurl parses a URL without a scheme. Parsing happens when @@ -55,6 +55,8 @@ option. Using this option multiple times makes the last set string override the previous ones. Set it to NULL to disable its use again. +This option does *not* adjust the scheme for URLs set with CURLOPT_CURLU(3). + # DEFAULT NULL (make a guess based on the host) diff --git a/docs/libcurl/opts/CURLOPT_FILETIME.md b/docs/libcurl/opts/CURLOPT_FILETIME.md index ccf93af2e0b0..e604e8ee14b2 100644 --- a/docs/libcurl/opts/CURLOPT_FILETIME.md +++ b/docs/libcurl/opts/CURLOPT_FILETIME.md @@ -12,7 +12,6 @@ Protocol: - FTP - SFTP - FILE - - SMB Added-in: 7.5 --- diff --git a/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md b/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md index 107e9e524c0b..130f78f30bd2 100644 --- a/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_HEADERFUNCTION.md @@ -110,7 +110,7 @@ Nothing. static size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) { - /* received header is 'nitems' bytes in 'buffer' NOT NULL-TERMINATED */ + /* received header is 'nitems' bytes in 'buffer' NOT null-terminated */ /* 'userdata' is set with CURLOPT_HEADERDATA */ return nitems; } diff --git a/docs/libcurl/opts/CURLOPT_HSTSWRITEFUNCTION.md b/docs/libcurl/opts/CURLOPT_HSTSWRITEFUNCTION.md index cdbdbe5d2629..180cd39dd2c8 100644 --- a/docs/libcurl/opts/CURLOPT_HSTSWRITEFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_HSTSWRITEFUNCTION.md @@ -49,17 +49,16 @@ This callback function gets called by libcurl repeatedly to allow the application to store the in-memory HSTS cache when libcurl is about to discard it. -Set the *clientp* argument with the CURLOPT_HSTSWRITEDATA(3) option -or it is NULL. -When the callback is invoked, the *sts* pointer points to a populated -struct: Read the hostname to 'name' (it is *namelen* bytes long and null -terminated. The *includeSubDomains* field is non-zero if the entry matches -subdomains. The *expire* string is a date stamp null-terminated string +Set the *clientp* argument with the CURLOPT_HSTSWRITEDATA(3) option or it is +NULL. When the callback is invoked, the *sts* pointer points to a populated +struct: Read the hostname to 'name' (it is *namelen* bytes long and +null-terminated. The *includeSubDomains* field is non-zero if the entry +matches subdomains. The *expire* string is a date stamp null-terminated string using the syntax YYYYMMDD HH:MM:SS. -The callback should return *CURLSTS_OK* if it succeeded and is prepared to -be called again (for another host) or *CURLSTS_DONE* if there is nothing -more to do. It can also return *CURLSTS_FAIL* to signal error. +The callback should return *CURLSTS_OK* if it succeeded and is prepared to be +called again (for another host) or *CURLSTS_DONE* if there is nothing more to +do. It can also return *CURLSTS_FAIL* to signal error. This option does not enable HSTS, you need to use CURLOPT_HSTS_CTRL(3) to do that. diff --git a/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md b/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md index 1f03de2c26e5..94d65353dd39 100644 --- a/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md +++ b/docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md @@ -43,8 +43,10 @@ PEM and other encodings are not supported; pass the raw key material as hex. With OpenSSL 3: - openssl genpkey -algorithm ED25519 -out ed25519.pem - openssl pkey -in ed25519.pem -outform RAW | xxd -p -c 64 | tr -d '\n' > key.hex + openssl genpkey -algorithm ED25519 -out k.pem + openssl pkey -in k.pem -text -noout | \ + grep -A3 ^priv: | \ + tr -cd '[:xdigit:]' > k.hex The `key.hex` file is one line of 64 hexadecimal digits. diff --git a/docs/libcurl/opts/CURLOPT_ISSUERCERT.md b/docs/libcurl/opts/CURLOPT_ISSUERCERT.md index 1e10b1bbb297..a8dfb1d5206b 100644 --- a/docs/libcurl/opts/CURLOPT_ISSUERCERT.md +++ b/docs/libcurl/opts/CURLOPT_ISSUERCERT.md @@ -37,9 +37,12 @@ associated with the certificate provided by the option. This additional check is useful in multi-level PKI where one needs to enforce that the peer certificate is from a specific branch of the tree. +The provided issuer certificate is used only for an issuer check against the +peer certificate; it is not validated or trusted by itself. + This option makes sense only when used in combination with the -CURLOPT_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is -not considered as failure. +CURLOPT_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is not +considered as failure. A specific error code (CURLE_SSL_ISSUER_ERROR) is defined with the option, which is returned if the setup of the SSL/TLS session has failed due to a diff --git a/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.md b/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.md index c0003536196a..3554d622543c 100644 --- a/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_ISSUERCERT_BLOB.md @@ -39,14 +39,16 @@ certificate provided by the option. This additional check is useful in multi-level PKI where one needs to enforce that the peer certificate is from a specific branch of the tree. -This option should be used in combination with the -CURLOPT_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is -not considered as failure. +The provided issuer certificate is used only for an issuer check against the +peer certificate; it is not validated or trusted by itself. + +This option should be used in combination with the CURLOPT_SSL_VERIFYPEER(3) +option. Otherwise, the result of the check is not considered as failure. A specific error code (CURLE_SSL_ISSUER_ERROR) is defined with the option, which is returned if the setup of the SSL/TLS session has failed due to a -mismatch with the issuer of peer certificate (CURLOPT_SSL_VERIFYPEER(3) -has to be set too for the check to fail). +mismatch with the issuer of peer certificate (CURLOPT_SSL_VERIFYPEER(3) has to +be set too for the check to fail). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after diff --git a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md index ed4011d57fa3..0f2a7583c048 100644 --- a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md +++ b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.md @@ -57,10 +57,9 @@ The pinned public key is used to verify the initial origin used in a transfer. If the transfer is set to follow redirects to other origins, they are *not* checked against this key. -This option has no effect on LDAP connections when libcurl uses the legacy LDAP -backend. That backend manages TLS independently of curl's TLS layer. When -libcurl is built with USE_OPENLDAP, the OpenLDAP backend routes TLS through -curl's layer and this option is honored. +For LDAP, this option only has an effect when using the OpenLDAP backend, +which routes TLS through libcurl. It is not supported by Apple's OpenLDAP +fork. # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md index d45121b53f1d..3443a7d21d3f 100644 --- a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md +++ b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE.md @@ -28,12 +28,14 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_POSTFIELDSIZE, long size); # DESCRIPTION -If you want to post static data to the server without having libcurl do a -strlen() to measure the data size, this option must be used. When this option -is used you can post fully binary data, which otherwise is likely to fail. If -this size is set to -1, libcurl uses strlen() to get the size or relies on the +Set the *size* of the data buffer to POST to prevent libcurl from doing a +strlen() to figure out the data size. When this option is used you can post +fully binary data, which otherwise is likely to fail. If this size is set to +-1, libcurl uses strlen() to get the size or relies on the CURLOPT_READFUNCTION(3) (if used) to signal the end of data. +You *must* make sure that the set size is not larger than the provided buffer. + If you post more than 2GB, use CURLOPT_POSTFIELDSIZE_LARGE(3). # DEFAULT diff --git a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE_LARGE.md b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE_LARGE.md index 9f2fcb2f9715..2dc710f56d97 100644 --- a/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE_LARGE.md +++ b/docs/libcurl/opts/CURLOPT_POSTFIELDSIZE_LARGE.md @@ -30,12 +30,14 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_POSTFIELDSIZE_LARGE, # DESCRIPTION -If you want to post static data to the server without having libcurl do a -strlen() to measure the data size, this option must be used. When this option -is used you can post fully binary data, which otherwise is likely to fail. If -this size is set to -1, libcurl uses strlen() to get the size or relies on the +Set the *size* of the data buffer to POST to prevent libcurl from doing a +strlen() to figure out the data size. When this option is used you can post +fully binary data, which otherwise is likely to fail. If this size is set to +-1, libcurl uses strlen() to get the size or relies on the CURLOPT_READFUNCTION(3) (if used) to signal the end of data. +You *must* make sure that the set size is not larger than the provided buffer. + # DEFAULT -1 diff --git a/docs/libcurl/opts/CURLOPT_PROTOCOLS.md b/docs/libcurl/opts/CURLOPT_PROTOCOLS.md index 023773315998..a7d40b2f0ebb 100644 --- a/docs/libcurl/opts/CURLOPT_PROTOCOLS.md +++ b/docs/libcurl/opts/CURLOPT_PROTOCOLS.md @@ -56,17 +56,9 @@ CURLPROTO_LDAPS CURLPROTO_MQTT CURLPROTO_POP3 CURLPROTO_POP3S -CURLPROTO_RTMP -CURLPROTO_RTMPE -CURLPROTO_RTMPS -CURLPROTO_RTMPT -CURLPROTO_RTMPTE -CURLPROTO_RTMPTS CURLPROTO_RTSP CURLPROTO_SCP CURLPROTO_SFTP -CURLPROTO_SMB -CURLPROTO_SMBS CURLPROTO_SMTP CURLPROTO_SMTPS CURLPROTO_TELNET diff --git a/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md b/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md index 969bf88ab7dd..6986b8566733 100644 --- a/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md +++ b/docs/libcurl/opts/CURLOPT_PROTOCOLS_STR.md @@ -42,8 +42,7 @@ set, it returns error. These are the available protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, -MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, -TFTP, WS, WSS +MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET, TFTP, WS, WSS You can set "ALL" as a short-cut to enable all protocols. Note that by setting all, you may enable protocols that were not supported the day you write this @@ -87,6 +86,8 @@ int main(int argc, char *argv[]) RTMP and its related protocol schemes are not supported since curl 8.20.0 +SMB and SMBS were removed in curl 8.23.0 + # %AVAILABILITY% # RETURN VALUE diff --git a/docs/libcurl/opts/CURLOPT_PROXY_CAINFO.md b/docs/libcurl/opts/CURLOPT_PROXY_CAINFO.md index 3eb5af912ac2..b9c8247e6a5d 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_CAINFO.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_CAINFO.md @@ -51,8 +51,7 @@ The application does not have to keep the string around after setting this option. Using this option multiple times makes the last set string override the -previous ones. Set it to NULL to disable its use again and switches back to -internal default. +previous ones. Set it to NULL to disable its use again. The default value for this can be figured out with CURLINFO_CAINFO(3). diff --git a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT.md b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT.md index c3687a46fe87..61d204e5c4c7 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT.md @@ -39,6 +39,9 @@ indeed the one associated with the certificate provided by the option. This additional check is useful in multi-level PKI where one needs to enforce that the peer certificate is from a specific branch of the tree. +The provided issuer certificate is used only for an issuer check against the +peer certificate; it is not validated or trusted by itself. + This option makes sense only when used in combination with the CURLOPT_PROXY_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is not considered as failure. diff --git a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md index 0885ad96e0c9..018d0ea62106 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_ISSUERCERT_BLOB.md @@ -40,14 +40,17 @@ associated with the certificate provided by the option. This additional check is useful in multi-level PKI where one needs to enforce that the peer certificate is from a specific branch of the tree. +The provided issuer certificate is used only for an issuer check against the +peer certificate; it is not validated or trusted by itself. + This option should be used in combination with the -CURLOPT_PROXY_SSL_VERIFYPEER(3) option. Otherwise, the result of the -check is not considered as failure. +CURLOPT_PROXY_SSL_VERIFYPEER(3) option. Otherwise, the result of the check is +not considered as failure. A specific error code (CURLE_SSL_ISSUER_ERROR) is defined with the option, which is returned if the setup of the SSL/TLS session has failed due to a -mismatch with the issuer of peer certificate -(CURLOPT_PROXY_SSL_VERIFYPEER(3) has to be set too for the check to fail). +mismatch with the issuer of peer certificate (CURLOPT_PROXY_SSL_VERIFYPEER(3) +has to be set too for the check to fail). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after diff --git a/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.md b/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.md index 5be7b134a0c0..da4ef48f6458 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_SSLCERT_BLOB.md @@ -13,6 +13,7 @@ Protocol: - TLS TLS-backend: - OpenSSL + - Rustls - Schannel Added-in: 7.71.0 --- @@ -35,8 +36,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROXY_SSLCERT_BLOB, Pass a pointer to a curl_blob structure, which contains information (pointer and size) about a memory block with binary data of the certificate used to connect to the HTTPS proxy. The format must be "P12" on Schannel. The format -must be "P12" or "PEM" on OpenSSL. The string "P12" or "PEM" must be specified -with CURLOPT_PROXY_SSLCERTTYPE(3). +must be "P12" or "PEM" on OpenSSL. The format must be "PEM" on Rustls. The +string "P12" or "PEM" must be specified with CURLOPT_PROXY_SSLCERTTYPE(3). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after diff --git a/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.md b/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.md index 65f940b8610c..230adedee6e4 100644 --- a/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_PROXY_SSLKEY_BLOB.md @@ -12,6 +12,7 @@ Protocol: - TLS TLS-backend: - OpenSSL + - Rustls Added-in: 7.71.0 --- @@ -32,7 +33,7 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROXY_SSLKEY_BLOB, Pass a pointer to a curl_blob structure that contains information (pointer and size) about the private key for connecting to the HTTPS proxy. Compatible with -OpenSSL. The format (like "PEM") must be specified with +OpenSSL and Rustls. The format (like "PEM") must be specified with CURLOPT_PROXY_SSLKEYTYPE(3). If the blob is initialized with the flags member of struct curl_blob set to diff --git a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md index 0803d587865d..0e9098d76303 100644 --- a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md +++ b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS.md @@ -61,17 +61,9 @@ CURLPROTO_LDAPS CURLPROTO_MQTT CURLPROTO_POP3 CURLPROTO_POP3S -CURLPROTO_RTMP -CURLPROTO_RTMPE -CURLPROTO_RTMPS -CURLPROTO_RTMPT -CURLPROTO_RTMPTE -CURLPROTO_RTMPTS CURLPROTO_RTSP CURLPROTO_SCP CURLPROTO_SFTP -CURLPROTO_SMB -CURLPROTO_SMBS CURLPROTO_SMTP CURLPROTO_SMTPS CURLPROTO_TELNET diff --git a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md index 731b506319d9..d262a22b2f95 100644 --- a/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md +++ b/docs/libcurl/opts/CURLOPT_REDIR_PROTOCOLS_STR.md @@ -46,8 +46,7 @@ By default libcurl allows HTTP, HTTPS, FTP and FTPS on redirects (since These are the available protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, -MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, -TFTP, WS, WSS +MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET, TFTP, WS, WSS You can set "ALL" as a short-cut to enable all protocols. Note that by setting all, you may enable protocols that were not supported the day you write this @@ -89,6 +88,12 @@ int main(int argc, char *argv[]) } ~~~ +# HISTORY + +RTMP and its related protocol schemes are not supported since curl 8.20.0 + +SMB and SMBS were removed in curl 8.23.0 + # %AVAILABILITY% # RETURN VALUE diff --git a/docs/libcurl/opts/CURLOPT_RESOLVER_START_FUNCTION.md b/docs/libcurl/opts/CURLOPT_RESOLVER_START_FUNCTION.md index 04e3b0d1b23e..e9961d7c3121 100644 --- a/docs/libcurl/opts/CURLOPT_RESOLVER_START_FUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_RESOLVER_START_FUNCTION.md @@ -34,7 +34,10 @@ Pass a pointer to your callback function, which should match the prototype shown above. This callback function gets called by libcurl every time before a new resolve -request is started. +request is started. A single transfer can trigger more than one such +request, for example an HTTPS RR lookup is a separate request from the +regular A/AAAA lookup, and each gets its own call to this callback before it +is started. *resolver_state* points to a backend-specific resolver state. Currently only the ares resolver backend has a resolver state. It can be used to set up any diff --git a/docs/libcurl/opts/CURLOPT_RESUME_FROM.md b/docs/libcurl/opts/CURLOPT_RESUME_FROM.md index d688430ba67d..4f286863980a 100644 --- a/docs/libcurl/opts/CURLOPT_RESUME_FROM.md +++ b/docs/libcurl/opts/CURLOPT_RESUME_FROM.md @@ -37,6 +37,10 @@ When doing uploads with FTP, the resume position is where in the local/source file libcurl should try to resume the upload from and it then appends the source file to the remote target file. +HTTP uploads using POST or PUT cannot be resumed. curl_easy_perform(3) returns +CURLE_BAD_FUNCTION_ARGUMENT when this option is set to a non-zero value for +such an upload. + If you need to resume a transfer beyond the 2GB limit, use CURLOPT_RESUME_FROM_LARGE(3) instead. diff --git a/docs/libcurl/opts/CURLOPT_RESUME_FROM_LARGE.md b/docs/libcurl/opts/CURLOPT_RESUME_FROM_LARGE.md index da1d9aa53f46..2ffaec446a92 100644 --- a/docs/libcurl/opts/CURLOPT_RESUME_FROM_LARGE.md +++ b/docs/libcurl/opts/CURLOPT_RESUME_FROM_LARGE.md @@ -38,6 +38,10 @@ When doing uploads with FTP, the resume position is where in the local/source file libcurl should try to resume the upload from and it appends the source file to the remote target file. +HTTP uploads using POST or PUT cannot be resumed. curl_easy_perform(3) returns +CURLE_BAD_FUNCTION_ARGUMENT when this option is set to a non-zero value for +such an upload. + # DEFAULT 0, not used diff --git a/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md b/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md index 061e8228f73d..5c12cc5c840b 100644 --- a/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md +++ b/docs/libcurl/opts/CURLOPT_RTSP_SESSION_ID.md @@ -31,7 +31,8 @@ Session ID for the handle. Useful for resuming an in-progress session. Once this value is set to any non-NULL value, libcurl returns *CURLE_RTSP_SESSION_ERROR* if the ID received from the server does not match. If unset (or set to NULL), libcurl automatically sets the ID the first time -the server sets it in a response. +the server sets it in a response. Such an ID is only sent back to the origin +that issued it, unless CURLOPT_UNRESTRICTED_AUTH(3) is set. The application does not have to keep the string around after setting this option. diff --git a/docs/libcurl/opts/CURLOPT_SHARE.md b/docs/libcurl/opts/CURLOPT_SHARE.md index 907e4f280519..da76087607b3 100644 --- a/docs/libcurl/opts/CURLOPT_SHARE.md +++ b/docs/libcurl/opts/CURLOPT_SHARE.md @@ -33,11 +33,6 @@ itself. This enables several curl handles to share data. If the curl handles are used simultaneously in multiple threads, you **must** use the locking methods in the share handle. See curl_share_setopt(3) for details. -If you add a share that is set to share cookies, your easy handle uses that -cookie cache and get the cookie engine enabled. If you stop sharing an object -that was using cookies (or change to another object that does not share -cookies), the easy handle gets its cookie engine disabled. - Data that the share object is not set to share is dealt with the usual way, as if no share was used. diff --git a/docs/libcurl/opts/CURLOPT_SOCKS5_AUTH.md b/docs/libcurl/opts/CURLOPT_SOCKS5_AUTH.md index 2fe3de3f45d7..a1dcc1db8f02 100644 --- a/docs/libcurl/opts/CURLOPT_SOCKS5_AUTH.md +++ b/docs/libcurl/opts/CURLOPT_SOCKS5_AUTH.md @@ -29,9 +29,20 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SOCKS5_AUTH, long bitmask); Pass a long as parameter, which is set to a bitmask, to tell libcurl which authentication method(s) are allowed for SOCKS5 proxy authentication. The only supported flags are *CURLAUTH_BASIC*, which allows username/password -authentication, *CURLAUTH_GSSAPI*, which allows GSS-API authentication, and -*CURLAUTH_NONE*, which allows no authentication. Set the actual username and -password with the CURLOPT_PROXYUSERPWD(3) option. +authentication, and *CURLAUTH_GSSAPI*, which allows GSS-API authentication. +Set the actual username and password with the CURLOPT_PROXYUSERPWD(3) option. + +libcurl offers the SOCKS5 "no authentication" method in addition to the ones +in the bitmask, and accepts it if the proxy selects it. *CURLAUTH_NONE* is zero +and therefore cannot be combined with the other flags; setting the bitmask to +*CURLAUTH_NONE* makes libcurl offer only the no authentication method. + +Add *CURLAUTH_ONLY* to the bitmask to require authentication. libcurl then does +not offer the no authentication method, and rejects it with *CURLE_PROXY* if +the proxy selects it anyway. A connection that was set up without SOCKS5 +authentication is also not reused for such a transfer. Setting *CURLAUTH_ONLY* +without *CURLAUTH_BASIC* or *CURLAUTH_GSSAPI* makes curl_easy_setopt(3) return +*CURLE_BAD_FUNCTION_ARGUMENT*. The specific socks authentication method is an *access property*, it does not change the security context. This means that this option changes how the @@ -76,6 +87,8 @@ int main(void) **CURLAUTH_*** macros became `long` types in 7.26.0, prior to this version a `long` cast was necessary when passed to curl_easy_setopt(3). +*CURLAUTH_ONLY* is supported since 8.23.0. + # %AVAILABILITY% # RETURN VALUE diff --git a/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.md b/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.md index aa69fbbc896a..559058975bc8 100644 --- a/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_SSLCERT_BLOB.md @@ -12,6 +12,7 @@ Protocol: - TLS TLS-backend: - OpenSSL + - Rustls - Schannel - mbedTLS - wolfSSL @@ -36,7 +37,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSLCERT_BLOB, Pass a pointer to a curl_blob structure, which contains (pointer and size) a client certificate. The format must be "P12" on Schannel. The format must be "P12" or "PEM" on OpenSSL. The format must be "DER" or "PEM" on mbedTLS. The -format must be specified with CURLOPT_SSLCERTTYPE(3). +format must be "PEM" on Rustls. The format must be specified with +CURLOPT_SSLCERTTYPE(3). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after diff --git a/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.md b/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.md index a062d39deba8..bf947a9ac10f 100644 --- a/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.md +++ b/docs/libcurl/opts/CURLOPT_SSLKEY_BLOB.md @@ -11,6 +11,7 @@ Protocol: - TLS TLS-backend: - OpenSSL + - Rustls - wolfSSL Added-in: 7.71.0 --- @@ -31,8 +32,8 @@ CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSLKEY_BLOB, # DESCRIPTION Pass a pointer to a curl_blob structure, which contains information (pointer -and size) for a private key. Compatible with OpenSSL. The format (like "PEM") -must be specified with CURLOPT_SSLKEYTYPE(3). +and size) for a private key. Compatible with OpenSSL and Rustls. The format +(like "PEM") must be specified with CURLOPT_SSLKEYTYPE(3). If the blob is initialized with the flags member of struct curl_blob set to CURL_BLOB_COPY, the application does not have to keep the buffer around after diff --git a/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md b/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md index c834cc8bf523..13e84b0ae7a9 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_SSL_CTX_FUNCTION.md @@ -70,13 +70,25 @@ anything about it, which then subsequently can lead to libcurl unknowingly reusing SSL connections with different properties. To remedy this you may set CURLOPT_FORBID_REUSE(3) from the callback function. -A connection that is set up with this callback can be put in the connection +Note that this callback is not only invoked for a connection to the transfer's +URL, but also for DoH requests or redirects (if configured) or if happy +eyeballing is done, for example performing HTTP/2 and/or HTTP/3 connects in +parallel. + +Connections that are set up with this callback can be put in the connection pool by libcurl and then reused in following transfers without the callback -being called. The connection may even be selected from the pool to be used for +being called. The connections may even be selected from the pool to be used for transfers not using this callback. If the callback should only be valid for the specific transfer the callback verifies, it should be marked unsuitable for reuse with CURLOPT_FORBID_REUSE(3). +We strongly discourage setting a client certificate with this option, or any +other sensitive data. The application must prevent both connection and TLS +session reuse when these settings should not outlive the transfer. In addition +to setting CURLOPT_FORBID_REUSE(3), set CURLOPT_SSL_SESSIONID_CACHE(3) to 0 +because a new connection can resume a session established with these settings +even though the callback is invoked again. + If you are using DNS-over-HTTPS (DoH) via CURLOPT_DOH_URL(3) then this callback is also called for those transfers and the curl handle is set to an internal handle. **This behavior is subject to change.** We recommend setting diff --git a/docs/libcurl/opts/CURLOPT_SSL_VERIFYPEER.md b/docs/libcurl/opts/CURLOPT_SSL_VERIFYPEER.md index ad0242ba5860..d77b499db59c 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_VERIFYPEER.md +++ b/docs/libcurl/opts/CURLOPT_SSL_VERIFYPEER.md @@ -69,6 +69,10 @@ HSTS and Alt-Svc information to be stored and used subsequently. Disabling certificate verification can make libcurl trust and use such information from malicious servers. +For LDAP, this option is supported when using the OpenLDAP backend, and with +the WinLDAP backend since 8.22.0. With Apple's OpenLDAP fork, the certificate +chain is still verified when this option is disabled. + # DEFAULT 1 - enabled diff --git a/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.md b/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.md index b1d373eef69c..86557298882c 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.md +++ b/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.md @@ -38,6 +38,10 @@ using the "Certificate Status Request" TLS extension (aka. OCSP stapling). Note that if this option is enabled but the server does not support the TLS extension, the verification fails. +For LDAP, this option only has an effect when using the OpenLDAP backend, +which routes TLS through libcurl. It is not supported by Apple's OpenLDAP +fork. + # DEFAULT 0 diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 65279d642659..73449bdd2b47 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -44,7 +44,8 @@ CURL_GLOBAL_ALL 7.8 CURL_GLOBAL_DEFAULT 7.8 CURL_GLOBAL_NOTHING 7.8 CURL_GLOBAL_SSL 7.8 -CURL_GLOBAL_WIN32 7.8.1 +CURL_GLOBAL_WIN32 7.8.1 8.23.0 +CURL_GLOBAL_WINSOCK 8.23.0 CURL_HAS_DECLSPEC_ATTRIBUTE 8.13.0 CURL_HET_DEFAULT 7.59.0 CURL_HTTP_VERSION_1_0 7.9.1 @@ -991,8 +992,8 @@ CURLPROTO_RTMPTS 7.21.0 8.20.0 CURLPROTO_RTSP 7.20.0 CURLPROTO_SCP 7.19.4 CURLPROTO_SFTP 7.19.4 -CURLPROTO_SMB 7.40.0 -CURLPROTO_SMBS 7.40.0 +CURLPROTO_SMB 7.40.0 8.23.0 +CURLPROTO_SMBS 7.40.0 8.23.0 CURLPROTO_SMTP 7.20.0 CURLPROTO_SMTPS 7.20.0 CURLPROTO_TELNET 7.19.4 diff --git a/docs/tests/CI.md b/docs/tests/CI.md index e68c9bc88509..4fe887c0d137 100644 --- a/docs/tests/CI.md +++ b/docs/tests/CI.md @@ -29,7 +29,6 @@ Consider the following table while looking at pull request failures: | CI platform as shown in PR | State | What to look at next | | ----------------------------------- | ------ | -------------------------- | | Linux / macOS / Windows / ... | stable | all errors and failures | -| Fuzzer | stable | fuzzing results | | Code analyzers | stable | new findings | | checkdocs / checksrc / dist / ... | stable | all errors and failures | | AppVeyor | stable | all errors and failures | @@ -62,13 +61,13 @@ are configured: GitHub Actions runs the following tests: - Tests with a variety of different compilation options, OSes, CPUs. -- Fuzz tests ([see the curl-fuzzer repo for more - info](https://github.com/curl/curl-fuzzer)). - Static analysis and sanitizers: clang-tidy, address sanitizer, memory sanitizer, thread sanitizer, CodeQL, valgrind, torture tests. These are each configured in different files in `.github/workflows`. +Fuzz tests run in the [curl-fuzzer repo](https://github.com/curl/curl-fuzzer). + ### AppVeyor CI AppVeyor runs a variety of different Windows builds, with different compilation diff --git a/docs/tests/FILEFORMAT.md b/docs/tests/FILEFORMAT.md index cb4743b89153..f77228d4c336 100644 --- a/docs/tests/FILEFORMAT.md +++ b/docs/tests/FILEFORMAT.md @@ -193,8 +193,6 @@ Available substitute variables include: - `%RESOLVE` - server/resolve command - `%RTSP6PORT` - IPv6 port number of the RTSP server - `%RTSPPORT` - Port number of the RTSP server -- `%SMBPORT` - Port number of the SMB server -- `%SMBSPORT` - Port number of the SMBS server - `%SMTP6PORT` - IPv6 port number of the SMTP server - `%SMTPPORT` - Port number of the SMTP server - `%SOCKSPORT` - Port number of the SOCKS4/5 server @@ -246,8 +244,9 @@ for users to skip certain classes of tests, if desired, but a few are treated specially by the test harness or build system. When running a unit test and the keywords include `unittest`, the `` -section can be left empty to use the standard unit test tool name `unitN` where -`N` is the test number. +section can be left empty to use the standard unit test tool name `unitN` +where `N` is the test number. Unit tests means *libcurl* unit tests. The +command line tool's unit tests are marked as `tunittest`. The `test-ci` make target automatically skips test with the `flaky` keyword. @@ -453,7 +452,6 @@ What server(s) this test case requires/uses. Available servers: - `rtsp-ipv6` - `scp` - `sftp` -- `smb` - `smtp` - `socks4` - `socks5` @@ -543,7 +541,7 @@ Features testable here are: - `typecheck` - `threadsafe` - `Unicode` -- `unittest` +- `unittest` - that feature needs to be enabled also for the tool unit tests marked as `tunittest` - `UnixSockets` - `verbose-strings` - `wakeup` @@ -570,10 +568,20 @@ is skipped and the (single-line) output is displayed as reason for not running the test. ### `` + Name of tool to invoke instead of "curl". This tool must be built and exist either in the `libtest/` directory (if the tool name starts with `lib`) or in the `unit/` directory (if the tool name starts with `unit`). +If the keyword `unittest` is set in the test case, the default name for the +tool is `unit%TESTNUMBER`. + +If the keyword `tunittest` is set in the test case, the default name for the +tool is `tool%TESTNUMBER`. + +If the keyword `libtest` is set in the test case, the default name for the +tool is `lib%TESTNUMBER`. + ### `` Brief test case description, shown when the test runs. diff --git a/docs/tests/TEST-SUITE.md b/docs/tests/TEST-SUITE.md index 6856eb5f8a21..c648614b0efe 100644 --- a/docs/tests/TEST-SUITE.md +++ b/docs/tests/TEST-SUITE.md @@ -46,30 +46,13 @@ output from the servers used in the test. ## Requires to run - `perl` (and a Unix-style shell) -- `python` (and a Unix-style shell, for SMB and TELNET tests) -- `python-impacket` (for SMB tests) +- `python` (and a Unix-style shell, for TELNET tests) - `diff` (when a test fails, a diff is shown) - `stunnel` (for HTTPS and FTPS tests) - `openssl` (the command line tool, for generating test server certificates) - `openssh` or `SunSSH` (for SCP and SFTP tests) - `nghttpx` (for HTTP/2 and HTTP/3 tests) -### Installation of impacket - -The Python-based test servers support Python 3. - -Please install python-impacket in the correct Python environment. You can -use pip or your OS' package manager to install 'impacket'. - -On Debian/Ubuntu the package name is 'python3-impacket' - -On FreeBSD the package name is 'py311-impacket' - -On any system where pip is available: 'python3 -m pip install impacket' - -You may also need to manually install the Python package 'six' as that may -be a missing requirement for impacket. - ## Event-based If curl is built with `Debug` enabled (see below), then the `runtests.pl` @@ -273,6 +256,7 @@ up. Subsets of tests can now be selected by passing keywords to the runtests.pl script via the make `TFLAGS` variable. New tests are added by finding a free number in `tests/data/Makefile.am`. +The script `scripts/testnum` automates this search for you. ## Write tests diff --git a/docs/wcurl.md b/docs/wcurl.md index 4137e98b35a6..59f426796918 100644 --- a/docs/wcurl.md +++ b/docs/wcurl.md @@ -69,7 +69,7 @@ By default, **wcurl** does: ## * Percent-decode the resulting filename; ## * Use 'index.html' as the default filename - if there is none in the URL. + if there is none in the URL, or if it is '-'. # OPTIONS @@ -107,6 +107,19 @@ Print help message. Any option supported by curl can be set here. This is not used by **wcurl**; it is instead forwarded to the curl invocation. +The value is split on whitespace (spaces, tabs and newlines) and each resulting +word is passed to curl as a separate argument. Quotes and backslashes are not +interpreted, so an argument containing whitespace cannot be passed this way. For +example, **--curl-options='-H "Cookie: a=1; b=2"'** passes the four arguments +`-H`, `"Cookie:`, `a=1;` and `b=2"` to curl, which treats the extra words as +URLs to download. + +To pass arguments that contain whitespace, write them to a curl config file and +pass it with **--curl-options="--config \"**, or use curl directly. + +Everything in \ becomes part of the curl command line, so never +build it from untrusted input. + # URL URL to be downloaded. Anything that is not a parameter is considered diff --git a/include/curl/curl.h b/include/curl/curl.h index 676e1daa733b..9a1595de254a 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -1149,7 +1149,7 @@ typedef CURLSTScode (*curl_hstswrite_callback)(CURL *easy, /* CURLOPT aliases that make no runtime difference */ -/* 'char *' argument to a string with a trailing zero */ +/* 'char *' argument to a string with a null-terminator */ #define CURLOPTTYPE_STRINGPOINT CURLOPTTYPE_OBJECTPOINT /* 'struct curl_slist *' argument */ @@ -3070,8 +3070,9 @@ typedef enum { } curl_closepolicy; #define CURL_GLOBAL_SSL (1 << 0) /* no purpose since 7.57.0 */ -#define CURL_GLOBAL_WIN32 (1 << 1) -#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL | CURL_GLOBAL_WIN32) +#define CURL_GLOBAL_WINSOCK (1 << 1) +#define CURL_GLOBAL_WIN32 CURL_GLOBAL_WINSOCK /* for compatibility */ +#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL | CURL_GLOBAL_WINSOCK) #define CURL_GLOBAL_NOTHING 0 #define CURL_GLOBAL_DEFAULT CURL_GLOBAL_ALL #define CURL_GLOBAL_ACK_EINTR (1 << 2) diff --git a/include/curl/curlver.h b/include/curl/curlver.h index 98d10c4fc2b5..c0370bd57515 100644 --- a/include/curl/curlver.h +++ b/include/curl/curlver.h @@ -32,12 +32,12 @@ /* This is the version number of the libcurl package from which this header file origins: */ -#define LIBCURL_VERSION "8.22.0-DEV" +#define LIBCURL_VERSION "8.23.0-DEV" /* The numeric version number is also available "in parts" by using these defines: */ #define LIBCURL_VERSION_MAJOR 8 -#define LIBCURL_VERSION_MINOR 22 +#define LIBCURL_VERSION_MINOR 23 #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 always @@ -57,7 +57,7 @@ Note: This define is the full hex number and _does not_ use the 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 0x081600 +#define LIBCURL_VERSION_NUM 0x081700 /* * This is the date and time when the full source package was created. The diff --git a/include/curl/multi.h b/include/curl/multi.h index 8cef6e774e46..6a79e0a8bb2f 100644 --- a/include/curl/multi.h +++ b/include/curl/multi.h @@ -538,6 +538,7 @@ CURL_EXTERN CURLMcode curl_multi_waitfds(CURLM *m, */ #define CURLMNOTIFY_INFO_READ 0 #define CURLMNOTIFY_EASY_DONE 1 +#define CURLMNOTIFY_LAST 2 /* last, not used */ /* * Callback to install via CURLMOPT_NOTIFYFUNCTION. diff --git a/lib/Makefile.inc b/lib/Makefile.inc index c4e26849aa20..d48a2b977eb2 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -27,7 +27,7 @@ LIB_CURLX_CFILES = \ curlx/base64.c \ curlx/basename.c \ curlx/dynbuf.c \ - curlx/fopen.c \ + curlx/win32-fopen.c \ curlx/inet_ntop.c \ curlx/inet_pton.c \ curlx/multibyte.c \ @@ -48,7 +48,7 @@ LIB_CURLX_HFILES = \ curlx/base64.h \ curlx/basename.h \ curlx/dynbuf.h \ - curlx/fopen.h \ + curlx/win32-fopen.h \ curlx/inet_ntop.h \ curlx/inet_pton.h \ curlx/multibyte.h \ @@ -265,6 +265,7 @@ LIB_CFILES = \ protocol.c \ proxy.c \ psl.c \ + ptrarray.c \ rand.c \ ratelimit.c \ request.c \ @@ -274,7 +275,6 @@ LIB_CFILES = \ setopt.c \ sha256.c \ slist.c \ - smb.c \ smtp.c \ socketpair.c \ socks.c \ @@ -284,15 +284,14 @@ LIB_CFILES = \ strcase.c \ strequal.c \ strerror.c \ - system_win32.c \ telnet.c \ tftp.c \ thrdpool.c \ thrdqueue.c \ transfer.c \ + u8_strset.c \ + u32_ptrset.c \ uint-bset.c \ - uint-hash.c \ - uint-hashset.c \ uint-spbset.c \ uint-table.c \ url.c \ @@ -398,6 +397,7 @@ LIB_HFILES = \ protocol.h \ proxy.h \ psl.h \ + ptrarray.h \ rand.h \ ratelimit.h \ request.h \ @@ -410,7 +410,6 @@ LIB_HFILES = \ setup-win32.h \ sigpipe.h \ slist.h \ - smb.h \ smtp.h \ sockaddr.h \ socketpair.h \ @@ -418,15 +417,14 @@ LIB_HFILES = \ splay.h \ strcase.h \ strerror.h \ - system_win32.h \ telnet.h \ tftp.h \ thrdpool.h \ thrdqueue.h \ transfer.h \ + u8_strset.h \ + u32_ptrset.h \ uint-bset.h \ - uint-hash.h \ - uint-hashset.h \ uint-spbset.h \ uint-table.h \ url.h \ diff --git a/lib/api.c b/lib/api.c index 15e3789861ab..4ae457365b49 100644 --- a/lib/api.c +++ b/lib/api.c @@ -29,6 +29,13 @@ #include "multiif.h" #include "vtls/vtls_scache.h" +typedef char mapi_fn_ids_fit_uint8[ + (CURL_MAPI_FN_LAST <= CURL_CBAPI_FN_START) ? 1 : -1]; +typedef char cbapi_fn_ids_fit_uint8[ + ((CURL_CBAPI_FN_LAST - 1) <= UINT8_MAX) ? 1 : -1]; +typedef char mapi_depth_fits_uint8[ + (CURL_MAPI_MAX_RECURSION <= UINT8_MAX) ? 1 : -1]; + struct Curl_eapi_fn_props { Curl_eapi_fn fn; uint8_t data_is_killed; /* easy handle is killed in call */ @@ -135,7 +142,7 @@ static bool eapi_in_event_cb(struct Curl_easy *data) size_t i; for(i = 0; i < multi->callstack.count; ++i) { if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START) { - uint16_t fn = multi->callstack.calls[i]; + uint8_t fn = multi->callstack.calls[i]; if((fn < CURL_CBAPI_FN_LAST) && cbapi_fn_props[fn - CURL_CBAPI_FN_START].is_event_cb) return TRUE; @@ -189,7 +196,7 @@ bool Curl_eapi_enter(struct Curl_eapi_guard *guard, guard->depth = 0; /* Verify that we got an easy handle we can work with. */ - if(!GOOD_EASY_HANDLE(data)) { + if(!GOOD_EASY_HANDLE(data) || data->state.internal) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; } @@ -228,7 +235,7 @@ bool Curl_eapi_enter(struct Curl_eapi_guard *guard, #ifdef CURLVERBOSE DEBUGF(curl_mfprintf(stderr, "EAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, - data->callstack.calls[data->callstack.count-1])); + data->callstack.calls[data->callstack.count - 1])); #endif result = CURLE_RECURSIVE_API_CALL; goto out; @@ -239,7 +246,7 @@ bool Curl_eapi_enter(struct Curl_eapi_guard *guard, DEBUGF(curl_mfprintf(stderr, "EAPI guard: calling %hu with multi call to %u ongoing\n", (uint16_t)fn, - data->multi->callstack.calls[data->multi->callstack.count-1])); + data->multi->callstack.calls[data->multi->callstack.count - 1])); #endif result = CURLE_RECURSIVE_API_CALL; goto out; @@ -352,7 +359,7 @@ bool Curl_mapi_enter(struct Curl_mapi_guard *guard, #ifdef CURLVERBOSE DEBUGF(curl_mfprintf(stderr, "MAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, - multi->callstack.calls[multi->callstack.count-1])); + multi->callstack.calls[multi->callstack.count - 1])); #endif mresult = CURLM_RECURSIVE_API_CALL; goto out; @@ -376,7 +383,7 @@ bool Curl_mapi_enter(struct Curl_mapi_guard *guard, mresult = CURLM_RECURSIVE_API_CALL; goto out; } - multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; + multi->callstack.calls[multi->callstack.count] = (uint8_t)fn; ++multi->callstack.count; guard->depth = multi->callstack.count; guard->multi = fn_props->multi_is_killed ? NULL : multi; @@ -400,7 +407,7 @@ void Curl_mapi_leave(struct Curl_mapi_guard *guard) DEBUGASSERT(0); /* someone forgot to clean up */ } /* reset to depth the guard was entered in */ - guard->multi->callstack.count = (uint16_t)(guard->depth - 1); + guard->multi->callstack.count = (uint8_t)(guard->depth - 1); } } } @@ -424,22 +431,13 @@ void Curl_cbapi_enter(struct Curl_mapi_guard *guard, return; } DEBUGASSERT(cbapi_fn_props[fn - CURL_CBAPI_FN_START].fn == fn); - if(multi->callstack.count) { - size_t i; - for(i = multi->callstack.count; i; --i) { - if(multi->callstack.calls[i - 1] == fn) { - /* recursive invocation of the same callback */ - DEBUGASSERT(0); - return; - } - } - } - /* all fine, add to data's callstack */ /* if multi callstack already at max depth, leave */ - if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) + if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) { + DEBUGASSERT(0); return; - multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; + } + multi->callstack.calls[multi->callstack.count] = (uint8_t)fn; ++multi->callstack.count; guard->depth = multi->callstack.count; guard->multi = multi; diff --git a/lib/api.h b/lib/api.h index 337d686bb5b9..b87f5b99cb92 100644 --- a/lib/api.h +++ b/lib/api.h @@ -99,7 +99,7 @@ typedef enum { CURL_MAPI_FN_LAST } Curl_mapi_fn; -#define CURL_CBAPI_FN_START (16 * 1024) +#define CURL_CBAPI_FN_START 128 /* the callback functions */ typedef enum { @@ -169,13 +169,13 @@ CURLHcode Curl_eapi_hcode(CURLcode result); #define CURL_MAPI_MAX_RECURSION 15 struct Curl_mapi_stack { - uint16_t count; - uint16_t calls[CURL_MAPI_MAX_RECURSION]; + uint8_t count; + uint8_t calls[CURL_MAPI_MAX_RECURSION]; }; struct Curl_mapi_guard { struct Curl_multi *multi; /* != NULL if handle stays */ - uint16_t depth; /* > 0 if this guard was entered */ + uint8_t depth; /* > 0 if this guard was entered */ }; bool Curl_mapi_enter(struct Curl_mapi_guard *guard, diff --git a/lib/cf-h1-proxy.c b/lib/cf-h1-proxy.c index 49b157bb3ad8..bf0cc4c92db5 100644 --- a/lib/cf-h1-proxy.c +++ b/lib/cf-h1-proxy.c @@ -72,12 +72,13 @@ struct h1_tunnel_state { BIT(close_connection); BIT(maybe_folded); BIT(leading_unfold); + BIT(udp_tunnel); }; /* Persistent context for the H1-PROXY filter */ struct cf_h1_proxy_ctx { + struct Curl_peer *peer; struct h1_tunnel_state *ts; - BIT(udp_tunnel); }; static bool tunnel_is_established(struct h1_tunnel_state *ts) @@ -93,7 +94,7 @@ static bool tunnel_is_failed(struct h1_tunnel_state *ts) static bool h1_proxy_is_udp(struct Curl_cfilter *cf) { struct cf_h1_proxy_ctx *pctx = cf->ctx; - return (pctx->udp_tunnel ? TRUE : FALSE); + return (bool)(pctx->ts && pctx->ts->udp_tunnel); } static CURLcode tunnel_reinit(struct Curl_cfilter *cf, @@ -111,6 +112,7 @@ static CURLcode tunnel_reinit(struct Curl_cfilter *cf, ts->close_connection = FALSE; ts->maybe_folded = FALSE; ts->leading_unfold = FALSE; + ts->chunked_encoding = FALSE; ts->nsent = 0; ts->headerlines = 0; return CURLE_OK; @@ -232,6 +234,7 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, struct Curl_easy *data, struct h1_tunnel_state *ts) { + struct cf_h1_proxy_ctx *pctx = cf->ctx; struct httpreq *req = NULL; int http_minor; CURLcode result; @@ -241,9 +244,10 @@ static CURLcode start_CONNECT(struct Curl_cfilter *cf, and we do not really use the newly cloned URL here then. Free it. */ curlx_safefree(data->req.newurl); - result = Curl_http_proxy_create_tunnel_request(&req, cf, data, ts->dest, - PROXY_HTTP_V1, - h1_proxy_is_udp(cf)); + result = Curl_http_proxy_create_tunnel_request(&req, cf, data, + pctx->peer, ts->dest, + PROXY_HTTP_V1, + h1_proxy_is_udp(cf)); if(result) goto out; @@ -299,90 +303,28 @@ static CURLcode send_CONNECT(struct Curl_cfilter *cf, return result; } -static CURLcode on_resp_header_udp(struct Curl_cfilter *cf, - struct Curl_easy *data, - struct h1_tunnel_state *ts, - const char *header) +static CURLcode on_resp_status(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h1_tunnel_state *ts, + const char *header) { - CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - if((checkprefix("WWW-Authenticate:", header) && (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) { - - bool proxy = (k->httpcode == 407); - char *auth = Curl_copy_header_value(header); - if(!auth) - return CURLE_OUT_OF_MEMORY; - - CURL_TRC_CF(data, cf, "CONNECT-UDP: fwd auth header '%s'", header); - result = Curl_http_input_auth(data, proxy, auth); - - curlx_free(auth); - - if(result) - return result; - } - else if(checkprefix("Content-Length:", header)) { - if(k->httpcode / 100 == 2 || k->httpcode == 101) { - infof(data, "Ignoring Content-Length in CONNECT-UDP %03d response", - k->httpcode); - } - else { - const char *p = header + CURL_CSTRLEN("Content-Length:"); - if(curlx_str_numblanks(&p, &ts->cl)) { - failf(data, "Unsupported Content-Length value"); - return CURLE_WEIRD_SERVER_REPLY; - } - } - } - else if(checkprefix("Transfer-Encoding:", header)) { - if(k->httpcode / 100 == 2 || k->httpcode == 101) { - infof(data, "Ignoring Transfer-Encoding in " - "CONNECT-UDP %03d response", k->httpcode); - } - else if(Curl_compareheader(header, - STRCONST("Transfer-Encoding:"), - STRCONST("chunked"))) { - CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> " - "Transfer-Encoding: chunked"); - ts->chunked_encoding = TRUE; - /* reset our chunky engine */ - Curl_httpchunk_reset(data, &ts->ch, TRUE); - } - } - else if(checkprefix("Capsule-protocol:", header)) { - if(Curl_compareheader(header, - STRCONST("Capsule-protocol:"), - STRCONST("?1"))) { - CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Capsule-protocol: ?1"); - } - } - else if(Curl_compareheader(header, - STRCONST("Connection:"), STRCONST("close"))) { - ts->close_connection = TRUE; - CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Connection: close"); - } - else if(Curl_compareheader(header, - STRCONST("Proxy-Connection:"), - STRCONST("close"))) { - ts->close_connection = TRUE; - CURL_TRC_CF(data, cf, - "CONNECT-UDP Response --> Proxy-Connection: close"); - } - else if(!strncmp(header, "HTTP/1.", 7) && - ((header[7] == '0') || (header[7] == '1')) && - (header[8] == ' ') && - ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) && - !ISDIGIT(header[12])) { + (void)ts; + if(!strncmp(header, "HTTP/1.", 7) && + ((header[7] == '0') || (header[7] == '1')) && + (header[8] == ' ') && + ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) && + !ISDIGIT(header[12])) { /* store the HTTP code from the proxy */ - data->info.httpproxycode = k->httpcode = - ((header[9] - '0') * 100) + - ((header[10] - '0') * 10) + - (header[11] - '0'); - CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> %d", k->httpcode); + data->info.httpproxycode = k->httpcode = ((header[9] - '0') * 100) + + ((header[10] - '0') * 10) + (header[11] - '0'); + CURL_TRC_CF(data, cf, "CONNECT%s HTTP status %d", + h1_proxy_is_udp(cf) ? "-UDP" : "", k->httpcode); + return CURLE_OK; } - return result; + failf(data, "Invalid response header"); + return CURLE_WEIRD_SERVER_REPLY; } static CURLcode on_resp_header(struct Curl_cfilter *cf, @@ -392,17 +334,17 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, { CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - (void)cf; + bool is_udp = h1_proxy_is_udp(cf); - if((checkprefix("WWW-Authenticate:", header) && (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode))) { + if(checkprefix("Proxy-authenticate:", header) && (407 == k->httpcode)) { bool proxy = (k->httpcode == 407); char *auth = Curl_copy_header_value(header); if(!auth) return CURLE_OUT_OF_MEMORY; - CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header); + CURL_TRC_CF(data, cf, "CONNECT%s: fwd auth header '%s'", + is_udp ? "-UDP" : "", header); result = Curl_http_input_auth(data, proxy, auth); curlx_free(auth); @@ -411,12 +353,17 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, return result; } else if(checkprefix("Content-Length:", header)) { - if(k->httpcode / 100 == 2) { + if(k->httpcode < 300) { + if(k->httpcode < 200) { + /* Informational 1xx responses cannot carry a body. RFC 9110 15.2 */ + failf(data, "Invalid Content-Length: in %03d response", k->httpcode); + return CURLE_WEIRD_SERVER_REPLY; + } /* A client MUST ignore any Content-Length or Transfer-Encoding header fields received in a successful response to CONNECT. "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */ - infof(data, "Ignoring Content-Length in CONNECT %03d response", - k->httpcode); + infof(data, "Ignoring Content-Length in CONNECT%s %03d response", + is_udp ? "-UDP" : "", k->httpcode); } else { const char *p = header + CURL_CSTRLEN("Content-Length:"); @@ -427,37 +374,46 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf, } } else if(Curl_compareheader(header, - STRCONST("Connection:"), STRCONST("close"))) + STRCONST("Connection:"), STRCONST("close"))) { + CURL_TRC_CF(data, cf, "CONNECT%s Connection: close", is_udp ? "-UDP" : ""); ts->close_connection = TRUE; + } else if(checkprefix("Transfer-Encoding:", header)) { - if(k->httpcode / 100 == 2) { + if(k->httpcode < 300) { + if(k->httpcode < 200) { + /* Informational 1xx responses cannot carry a body. RFC 9110 15.2 */ + failf(data, "Invalid Transfer-Encoding: in %03d response", + k->httpcode); + return CURLE_WEIRD_SERVER_REPLY; + } /* A client MUST ignore any Content-Length or Transfer-Encoding header fields received in a successful response to CONNECT. "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */ infof(data, "Ignoring Transfer-Encoding in " - "CONNECT %03d response", k->httpcode); + "CONNECT%s %03d response", is_udp ? "-UDP" : "", k->httpcode); } else if(Curl_compareheader(header, STRCONST("Transfer-Encoding:"), STRCONST("chunked"))) { - infof(data, "CONNECT responded chunked"); + CURL_TRC_CF(data, cf, "CONNECT%s response chunked", + is_udp ? "-UDP" : ""); ts->chunked_encoding = TRUE; - /* reset our chunky engine */ Curl_httpchunk_reset(data, &ts->ch, TRUE); } } + else if(is_udp && checkprefix("Capsule-protocol:", header)) { + if(Curl_compareheader(header, + STRCONST("Capsule-protocol:"), + STRCONST("?1"))) { + CURL_TRC_CF(data, cf, "CONNECT-UDP Response --> Capsule-protocol: ?1"); + } + } else if(Curl_compareheader(header, STRCONST("Proxy-Connection:"), - STRCONST("close"))) + STRCONST("close"))) { + CURL_TRC_CF(data, cf, "CONNECT%s Proxy-Connection: close", + is_udp ? "-UDP" : ""); ts->close_connection = TRUE; - else if(!strncmp(header, "HTTP/1.", 7) && - ((header[7] == '0') || (header[7] == '1')) && - (header[8] == ' ') && - ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) && - !ISDIGIT(header[12])) { - /* store the HTTP code from the proxy */ - data->info.httpproxycode = k->httpcode = ((header[9] - '0') * 100) + - ((header[10] - '0') * 10) + (header[11] - '0'); } return result; } @@ -499,8 +455,11 @@ static CURLcode single_header(struct Curl_cfilter *cf, if(ISNEWLINE(linep[0])) { /* end of response-headers from the proxy */ - - if((407 == k->httpcode) && !data->state.authproblem) { + if(ts->headerlines == 1) { + ts->keepon = KEEPON_DONE; + result = CURLE_WEIRD_SERVER_REPLY; + } + else if((407 == k->httpcode) && !data->state.authproblem) { /* If we get a 407 response code with content length when we have no auth problem, we must ignore the whole response-body */ @@ -529,13 +488,10 @@ static CURLcode single_header(struct Curl_cfilter *cf, return result; } - if(h1_proxy_is_udp(cf)) { - result = on_resp_header_udp(cf, data, ts, linep); - } - else { + if(ts->headerlines == 1) + result = on_resp_status(cf, data, ts, linep); + else result = on_resp_header(cf, data, ts, linep); - } - if(result) return result; @@ -804,12 +760,8 @@ static CURLcode H1_CONNECT(struct Curl_cfilter *cf, /* 2xx response, SUCCESS! */ /* 101 Switching Protocol for CONNECT-UDP */ h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data); - if(h1_proxy_is_udp(cf)) - infof(data, "CONNECT-UDP tunnel established, response %d", - data->info.httpproxycode); - else - infof(data, "CONNECT tunnel established, response %d", - data->info.httpproxycode); + infof(data, "CONNECT%s tunnel established, response %d", + h1_proxy_is_udp(cf) ? "-UDP" : "", data->info.httpproxycode); result = CURLE_OK; out: @@ -905,9 +857,14 @@ static bool cf_h1_proxy_data_pending(struct Curl_cfilter *cf, static void cf_h1_proxy_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { + struct cf_h1_proxy_ctx *pctx = cf->ctx; + CURL_TRC_CF(data, cf, "destroy"); - cf_tunnel_free(cf, data); - curlx_safefree(cf->ctx); + if(pctx) { + cf_tunnel_free(cf, data); + Curl_peer_unlink(&pctx->peer); + curlx_safefree(cf->ctx); + } } static CURLcode cf_h1_proxy_query(struct Curl_cfilter *cf, @@ -956,6 +913,7 @@ struct Curl_cftype Curl_cft_h1_proxy = { CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, int httpversion, bool udp_tunnel) @@ -977,6 +935,7 @@ CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, goto out; } Curl_peer_link(&ts->dest, dest); + ts->udp_tunnel = udp_tunnel; ts->httpversion = httpversion; curlx_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS); curlx_dyn_init(&ts->request_data, DYN_HTTP_REQUEST); @@ -987,10 +946,11 @@ CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, result = CURLE_OUT_OF_MEMORY; goto out; } - pctx->udp_tunnel = udp_tunnel; + Curl_peer_link(&pctx->peer, peer); pctx->ts = ts; result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, pctx); if(result) { + Curl_peer_unlink(&pctx->peer); curlx_free(pctx); goto out; } diff --git a/lib/cf-h1-proxy.h b/lib/cf-h1-proxy.h index 3255bf79f240..3db05822ce77 100644 --- a/lib/cf-h1-proxy.h +++ b/lib/cf-h1-proxy.h @@ -31,6 +31,7 @@ struct Curl_peer; CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, int httpversion, bool udp_tunnel); diff --git a/lib/cf-h2-proxy.c b/lib/cf-h2-proxy.c index fde9e1bc799a..08a1dd7e1913 100644 --- a/lib/cf-h2-proxy.c +++ b/lib/cf-h2-proxy.c @@ -184,6 +184,7 @@ struct cf_h2_proxy_ctx { struct bufq inbufq; /* network receive buffer */ struct bufq outbufq; /* network send buffer */ + struct Curl_peer *peer; /* proxy we talk to */ struct Curl_peer *dest; /* where to tunnel to */ struct tunnel_stream tunnel; /* our tunnel CONNECT stream */ int32_t goaway_error; @@ -208,6 +209,7 @@ static void cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx *ctx) } Curl_bufq_free(&ctx->inbufq); Curl_bufq_free(&ctx->outbufq); + Curl_peer_unlink(&ctx->peer); Curl_peer_unlink(&ctx->dest); tunnel_stream_clear(&ctx->tunnel); memset(ctx, 0, sizeof(*ctx)); @@ -527,11 +529,8 @@ static int proxy_h2_on_frame_recv(nghttp2_session *session, /* Only final status code signals the end of header */ CURL_TRC_CF(data, cf, "[%d] got http status: %d", stream_id, ctx->tunnel.resp->status); - if(!ctx->tunnel.has_final_response) { - if(ctx->tunnel.resp->status / 100 != 1) { - ctx->tunnel.has_final_response = TRUE; - } - } + if(!ctx->tunnel.has_final_response && ctx->tunnel.resp->status / 100 != 1) + ctx->tunnel.has_final_response = TRUE; break; case NGHTTP2_WINDOW_UPDATE: if(CURL_REQ_WANT_SEND(data)) { @@ -581,7 +580,7 @@ static int proxy_h2_on_header(nghttp2_session *session, if(namelen == CURL_CSTRLEN(HTTP_PSEUDO_STATUS) && !memcmp(HTTP_PSEUDO_STATUS, name, namelen)) { - int http_status; + int32_t http_status; struct http_resp *resp; /* status: always comes first, we might get more than one response, @@ -726,7 +725,7 @@ static CURLcode proxy_h2_submit( CURLcode result; (void)cf; - Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&h2_headers, MAX_HTTP_RESP_HEADER_COUNT, DYN_HTTP_REQUEST); result = Curl_http_req_to_h2(&h2_headers, req, data); if(result) goto out; @@ -773,7 +772,8 @@ static CURLcode submit_CONNECT(struct Curl_cfilter *cf, CURLcode result; struct httpreq *req = NULL; - result = Curl_http_proxy_create_tunnel_request(&req, cf, data, ctx->dest, + result = Curl_http_proxy_create_tunnel_request(&req, cf, data, + ctx->peer, ctx->dest, PROXY_HTTP_V2, (bool)ctx->udp_tunnel); if(result) @@ -1487,6 +1487,7 @@ struct Curl_cftype Curl_cft_h2_proxy = { CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, bool udp_tunnel) { @@ -1498,6 +1499,7 @@ CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, ctx = curlx_calloc(1, sizeof(*ctx)); if(!ctx) goto out; + Curl_peer_link(&ctx->peer, peer); Curl_peer_link(&ctx->dest, dest); ctx->udp_tunnel = udp_tunnel; diff --git a/lib/cf-h2-proxy.h b/lib/cf-h2-proxy.h index 07e3c9aedf1a..4b1dde26496d 100644 --- a/lib/cf-h2-proxy.h +++ b/lib/cf-h2-proxy.h @@ -29,6 +29,7 @@ CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, bool udp_tunnel); diff --git a/lib/cf-https-connect.c b/lib/cf-https-connect.c index 2e13a5cc09fe..fbef12390640 100644 --- a/lib/cf-https-connect.c +++ b/lib/cf-https-connect.c @@ -267,6 +267,9 @@ static bool time_to_start_baller2(struct Curl_cfilter *cf, ctx->ballers[1].name); return TRUE; } + /* set the effective hard timeout again */ + Curl_expire_set(data, EXPIRE_ALPN_EYEBALLS, + ctx->hard_eyeballs_timeout_ms - elapsed_ms, pnow); } return FALSE; } @@ -277,7 +280,7 @@ static bool cf_hc_may_h3(struct Curl_cfilter *cf, 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); + Curl_conn_may_http3(data, cf->conn); ctx->checked_h3 = TRUE; } return !ctx->check_h3_result; @@ -692,6 +695,7 @@ static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf, if(!cf->connected) { switch(event) { case CF_CTRL_REPORT_STATS: + case CF_CTRL_CONN_INFO_UPDATE: for(i = 0; i < ctx->baller_count; i++) { /* Make the first baller that connected at network level report */ if(Curl_conn_cf_is_ip_connected(ctx->ballers[i].cf, data)) { diff --git a/lib/cf-ip-happy.c b/lib/cf-ip-happy.c index baca4682f0b0..e3c0944d3979 100644 --- a/lib/cf-ip-happy.c +++ b/lib/cf-ip-happy.c @@ -267,6 +267,7 @@ static CURLcode cf_ip_attempt_connect(struct cf_ip_attempt *a, struct cf_ip_ballers { struct cf_ip_attempt *running; + struct cf_ip_attempt **running_tail; /* &running or &(last->next) */ struct cf_ip_attempt *winner; struct cf_ai_iter addr_iter; #ifdef USE_IPV6 @@ -278,10 +279,12 @@ struct cf_ip_ballers { cf_ip_connect_create *cf_create; /* for creating cf */ struct curltime last_attempt_started; timediff_t attempt_delay_ms; + CURLcode last_dead_result; /* result of last discarded hard failure */ int last_attempt_ai_family; uint32_t max_concurrent; uint8_t transport_peer; uint8_t tunnel_transport; + BIT(had_dead); /* a hard failure was discarded */ }; static CURLcode cf_ip_attempt_restart(struct cf_ip_attempt *a, @@ -322,6 +325,7 @@ static void cf_ip_ballers_clear(struct Curl_easy *data, bs->running = a->next; cf_ip_attempt_free(a, data); } + bs->running_tail = &bs->running; cf_ip_attempt_free(bs->winner, data); bs->winner = NULL; Curl_peer_unlink(&bs->origin); @@ -340,6 +344,7 @@ static CURLcode cf_ip_ballers_init(struct cf_ip_ballers *bs, uint32_t max_concurrent) { memset(bs, 0, sizeof(*bs)); + bs->running_tail = &bs->running; bs->cf_create = get_cf_create(transport_peer, !!tunnel_peer); if(!bs->cf_create) { failf(data, "unsupported transport type %u%s", @@ -375,6 +380,8 @@ static void cf_ip_ballers_prune(struct cf_ip_ballers *bs, a = *panchor; if(!a->result && !a->connected) { *panchor = a->next; + if(!a->next) + bs->running_tail = panchor; a->next = NULL; cf_ip_attempt_free(a, data); --ongoing; @@ -404,11 +411,17 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, return CURLE_OK; evaluate: + if(Curl_timeleft_now_ms(data, Curl_pgrs_now(data)) < 0) { + failf(data, "Connection timeout after %" FMT_OFF_T " ms", + Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE)); + return CURLE_OPERATION_TIMEDOUT; + } + ongoing = inconclusive = 0; /* check if a running baller connects now */ VERBOSE(i = -1); - for(panchor = &bs->running; *panchor; panchor = &(*panchor)->next) { + for(panchor = &bs->running; *panchor;) { VERBOSE(++i); a = *panchor; a->result = cf_ip_attempt_connect(a, data, connected); @@ -425,13 +438,27 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, bs->running = a->next; cf_ip_attempt_free(a, data); } + bs->running_tail = &bs->running; return CURLE_OK; } /* still running */ ++ongoing; + panchor = &(*panchor)->next; } - else if(a->inconclusive) /* failed, but inconclusive */ + else if(a->inconclusive) { /* failed, but inconclusive */ ++inconclusive; + panchor = &(*panchor)->next; + } + else { + /* hard failure, discard right away */ + *panchor = a->next; + if(!a->next) + bs->running_tail = panchor; + a->next = NULL; + bs->last_dead_result = a->result; + bs->had_dead = TRUE; + cf_ip_attempt_free(a, data); + } } if(bs->running) CURL_TRC_CF(data, cf, "checked connect attempts: " @@ -505,10 +532,9 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, DEBUGASSERT(a); /* append to running list */ - panchor = &bs->running; - while(*panchor) - panchor = &(*panchor)->next; - *panchor = a; + *bs->running_tail = a; + a->next = NULL; + bs->running_tail = &a->next; bs->last_attempt_started = *pnow; bs->last_attempt_ai_family = ai_family; /* and run everything again */ @@ -548,13 +574,8 @@ static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs, 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; - VERBOSE(i = 0); - for(a = bs->running; a; a = a->next) { - CURL_TRC_CF(data, cf, "baller %d: result=%d", i, (int)a->result); - if(a->result) - result = a->result; - } + DEBUGASSERT(!bs->running); + result = bs->had_dead ? bs->last_dead_result : CURLE_COULDNT_CONNECT; } } @@ -652,10 +673,9 @@ static int cf_ip_ballers_min_reply_ms(struct cf_ip_ballers *bs, for(a = bs->running; a; a = a->next) { 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; - } + &breply_ms, NULL) && + breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms)) + reply_ms = breply_ms; } return reply_ms; } diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 870a98ecbd5d..68f7f88e9d44 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -177,9 +177,17 @@ static void tcpnodelay(struct Curl_cfilter *cf, (defined(_WIN32) && !defined(TCP_KEEPIDLE)) /* Solaris < 11.4, DragonFlyBSD < 500702 and Windows < 10.0.16299 * use millisecond units. */ -#define KEEPALIVE_FACTOR(x) ((x) *= 1000) +static void alive_unit(int *x) +{ + /* make sure this doesn't wrap */ + if(*x < (INT_MAX / 1000)) + *x *= 1000; + else + /* this is still almost 25 days */ + *x = INT_MAX; +} #else -#define KEEPALIVE_FACTOR(x) +#define alive_unit(x) #endif #endif @@ -250,10 +258,10 @@ static void tcpkeepalive(struct Curl_cfilter *cf, DWORD dummy; vals.onoff = 1; optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); vals.keepalivetime = (u_long)optval; optval = curlx_sltosi(data->set.tcp_keepintvl); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); vals.keepaliveinterval = (u_long)optval; if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID)&vals, sizeof(vals), NULL, 0, &dummy, NULL, NULL) != 0) { @@ -264,7 +272,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, #else /* !USE_WINSOCK */ #ifdef TCP_KEEPIDLE optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPIDLE on fd " @@ -273,7 +281,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, #elif defined(TCP_KEEPALIVE) /* macOS style */ optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPALIVE on fd " @@ -282,7 +290,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, #elif defined(TCP_KEEPALIVE_THRESHOLD) /* Solaris <11.4 style */ optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPALIVE_THRESHOLD on fd " @@ -291,7 +299,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, #endif #ifdef TCP_KEEPINTVL optval = curlx_sltosi(data->set.tcp_keepintvl); - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPINTVL on fd " @@ -318,7 +326,7 @@ static void tcpkeepalive(struct Curl_cfilter *cf, else optval = keepcnt * keepintvl; } - KEEPALIVE_FACTOR(optval); + alive_unit(&optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD, (void *)&optval, sizeof(optval)) < 0) { CURL_TRC_CF(data, cf, "Failed to set TCP_KEEPALIVE_ABORT_THRESHOLD" @@ -577,11 +585,13 @@ int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn, * len [in] - length of the input string. * dev [in/out] - address where a pointer to newly allocated memory * holding the interface-or-host will be stored upon - * completion. + * completion or NULL if the caller is not interested. * iface [in/out] - address where a pointer to newly allocated memory - * holding the interface will be stored upon completion. + * holding the interface will be stored upon completion + * or NULL if the caller is not interested. * host [in/out] - address where a pointer to newly allocated memory - * holding the host will be stored upon completion. + * holding the host will be stored upon completion + * or NULL if the caller is not interested. * * Returns CURLE_OK on success. */ @@ -593,27 +603,36 @@ CURLcode Curl_parse_interface(const char *input, static const char if_host_prefix[] = "ifhost!"; size_t len; - DEBUGASSERT(dev); - DEBUGASSERT(iface); - DEBUGASSERT(host); - len = strlen(input); if(len > 512) return CURLE_BAD_FUNCTION_ARGUMENT; + if(dev) + *dev = NULL; + if(iface) + *iface = NULL; + if(host) + *host = NULL; + if(!strncmp(if_prefix, input, strlen(if_prefix))) { input += strlen(if_prefix); if(!*input) return CURLE_BAD_FUNCTION_ARGUMENT; - *iface = curlx_memdup0(input, len - strlen(if_prefix)); - return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY; + if(iface) { + *iface = curlx_memdup0(input, len - strlen(if_prefix)); + return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } else if(!strncmp(host_prefix, input, strlen(host_prefix))) { input += strlen(host_prefix); if(!*input) return CURLE_BAD_FUNCTION_ARGUMENT; - *host = curlx_memdup0(input, len - strlen(host_prefix)); - return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY; + if(host) { + *host = curlx_memdup0(input, len - strlen(host_prefix)); + return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } else if(!strncmp(if_host_prefix, input, strlen(if_host_prefix))) { const char *host_part; @@ -622,22 +641,30 @@ CURLcode Curl_parse_interface(const char *input, host_part = memchr(input, '!', len); if(!host_part || !*(host_part + 1)) return CURLE_BAD_FUNCTION_ARGUMENT; - *iface = curlx_memdup0(input, host_part - input); - if(!*iface) - return CURLE_OUT_OF_MEMORY; + if(iface) { + *iface = curlx_memdup0(input, host_part - input); + if(!*iface) + return CURLE_OUT_OF_MEMORY; + } ++host_part; - *host = curlx_memdup0(host_part, len - (host_part - input)); - if(!*host) { - curlx_safefree(*iface); - return CURLE_OUT_OF_MEMORY; + if(host) { + *host = curlx_memdup0(host_part, len - (host_part - input)); + if(!*host) { + if(iface) + curlx_safefree(*iface); + return CURLE_OUT_OF_MEMORY; + } } return CURLE_OK; } if(!*input) return CURLE_BAD_FUNCTION_ARGUMENT; - *dev = curlx_memdup0(input, len); - return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY; + if(dev) { + *dev = curlx_memdup0(input, len); + return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; } #ifndef CURL_DISABLE_BINDLOCAL @@ -652,34 +679,42 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, #ifdef USE_IPV6 struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; #endif - struct Curl_dns_entry *h = NULL; unsigned short port = data->set.localport; /* use this port number, 0 for "random" */ /* how many port numbers to try to bind to, increasing one at a time */ int portnum = data->set.localportrange; - const char *dev = CURL_EASY_STR(data, STRING_DEVICE); - const char *iface_input = CURL_EASY_STR(data, STRING_INTERFACE); - const char *host_input = CURL_EASY_STR(data, STRING_BINDHOST); - const char *iface = iface_input ? iface_input : dev; - const char *host = host_input ? host_input : dev; + char *dev_in = NULL, *iface_in = NULL, *host_in = NULL; + const char *iface = NULL, *host = NULL; int sockerr; #ifdef IP_BIND_ADDRESS_NO_PORT int on = 1; #endif + CURLcode result = CURLE_OK; + #ifndef USE_IPV6 (void)scope; #endif + if(conn->interface_opts) { + /************************************************************* + * Select device to bind socket to + *************************************************************/ + result = Curl_parse_interface(conn->interface_opts, + &dev_in, &iface_in, &host_in); + if(result) + goto out; + /* This is how we use the values */ + iface = iface_in ? iface_in : dev_in; + host = host_in ? host_in : dev_in; + } - /************************************************************* - * Select device to bind socket to - *************************************************************/ if(!iface && !host && !port) - /* no local kind of binding was requested */ - return CURLE_OK; - else if(iface && (strlen(iface) >= 255)) - return CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + if(iface && (strlen(iface) >= 255)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); if(iface || host) { @@ -688,32 +723,31 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if2ip_result_t if2ip_result = IF2IP_NOT_FOUND; #ifdef SO_BINDTODEVICE - if(iface) { - /* - * This binds the local socket to a particular interface. This will - * force even requests to other local interfaces to go out the external - * interface. Only bind to the interface when specified as interface, - * not as a hostname or ip address. - * - * The interface might be a VRF, eg: vrf-blue, which means it cannot be - * converted to an IP address and would fail Curl_if2ip. Try to - * use it straight away. - */ - if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, - iface, (curl_socklen_t)strlen(iface) + 1) == 0) { - /* This is often "errno 1, error: Operation not permitted" if you are - * not running as root or another suitable privileged user. If it - * succeeds it means the parameter was a valid interface and not an IP - * address. Return immediately. - */ - if(!host_input) { - infof(data, "socket successfully bound to interface '%s'", iface); - return CURLE_OK; - } - } + /* + * This binds the local socket to a particular interface. This will + * force even requests to other local interfaces to go out the external + * interface. Only bind to the interface when specified as interface, + * not as a hostname or ip address. + * + * The interface might be a VRF, eg: vrf-blue, which means it cannot be + * converted to an IP address and would fail Curl_if2ip. Try to + * use it straight away. + */ + if(iface && + setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, + iface, (curl_socklen_t)strlen(iface) + 1) == 0 && + /* This is often "errno 1, error: Operation not permitted" if you are + * not running as root or another suitable privileged user. If it + * succeeds it means the parameter was a valid interface and not an IP + * address. Return immediately. + */ + !host_in) { + infof(data, "socket successfully bound to interface '%s'", iface); + result = CURLE_OK; + goto out; } #endif - if(!host_input) { + if(!host_in) { /* Discover IP from input device, then bind to it */ if2ip_result = Curl_if2ip(af, #ifdef USE_IPV6 @@ -723,18 +757,20 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, } switch(if2ip_result) { case IF2IP_NOT_FOUND: - if(iface_input && !host_input) { + if(iface_in && !host_in) { /* Do not fall back to treating it as a hostname */ char buffer[STRERROR_LEN]; data->state.os_errno = sockerr = SOCKERRNO; failf(data, "Could not bind to interface '%s' with errno %d: %s", iface, sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); - return CURLE_INTERFACE_FAILED; + result = CURLE_INTERFACE_FAILED; + goto out; } break; case IF2IP_AF_NOT_SUPPORTED: /* Signal the caller to try another address family if available */ - return CURLE_UNSUPPORTED_PROTOCOL; + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; case IF2IP_FOUND: /* * We now have the numerical IP address in the 'myhost' buffer @@ -745,7 +781,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, done = 1; break; } - if(!iface_input || host_input) { + if(!iface_in || host_in) { /* * This was not an interface, resolve the name as a hostname * or IP number @@ -772,7 +808,8 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, if(af != h_af) { /* bad IP version combo, signal the caller to try another address family if available */ - return CURLE_UNSUPPORTED_PROTOCOL; + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; } done = 1; } @@ -805,8 +842,10 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, 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)) - return CURLE_UNSUPPORTED_PROTOCOL; + &scope_id, UINT_MAX)) { + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; + } si6->sin6_scope_id = (unsigned int)scope_id; } #endif @@ -833,7 +872,8 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, data->state.os_errno = sockerr = SOCKERRNO; failf(data, "Could not bind to '%s' with errno %d: %s", host, sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); - return CURLE_INTERFACE_FAILED; + result = CURLE_INTERFACE_FAILED; + goto out; } } else { @@ -860,7 +900,8 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, /* we succeeded to bind */ infof(data, "Local port: %hu", port); conn->bits.bound = TRUE; - return CURLE_OK; + result = CURLE_OK; + goto out; } if(--portnum > 0) { @@ -886,7 +927,13 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, sockerr, curlx_strerror(sockerr, buffer, sizeof(buffer))); } - return CURLE_INTERFACE_FAILED; + result = CURLE_INTERFACE_FAILED; + +out: + curlx_free(dev_in); + curlx_free(iface_in); + curlx_free(host_in); + return result; } #endif @@ -1183,7 +1230,13 @@ static void tcplocalhost(struct Curl_cfilter *cf, DWORD bytes = 0; memset(&rto, 0, sizeof(rto)); rto.Rtt = TCP_INITIAL_RTO_DEFAULT_RTT; - rto.MaxSynRetransmissions = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS; + /* Windows 10, version 1709 (10.0.16299) and later versions can set max + retransmissions to zero, earlier versions could only set 1 */ + if(curlx_verify_windows_version(10, 0, 16299, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) + rto.MaxSynRetransmissions = TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS; + else + rto.MaxSynRetransmissions = 1; (void)WSAIoctl(sockfd, SIO_TCP_INITIAL_RTO, &rto, sizeof(rto), NULL, 0, &bytes, NULL, NULL); } @@ -1368,7 +1421,7 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data, /* while connectx function is available since macOS 10.11 / iOS 9, it did not have the interface declared correctly until Xcode 9 / macOS SDK 10.13 */ - if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) { + if(__builtin_available(macOS 10.11, iOS 9, tvOS 9, watchOS 2, *)) { sa_endpoints_t endpoints; endpoints.sae_srcif = 0; endpoints.sae_srcaddr = NULL; diff --git a/lib/cfilters.c b/lib/cfilters.c index 6ffa262eb165..3e28f8919ebb 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -179,67 +179,10 @@ void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, void Curl_conn_cf_discard_all(struct Curl_easy *data, struct connectdata *conn, int8_t sockindex) { - struct curltime *pt = &conn->shutdown.start[sockindex]; - memset(pt, 0, sizeof(*pt)); + conn->shutdown.start_ms[sockindex] = -1; Curl_conn_cf_discard_chain(&conn->cfilter[sockindex], data); } -CURLcode Curl_conn_shutdown(struct Curl_easy *data, - int8_t sockindex, bool *done) -{ - struct Curl_cfilter *cf; - CURLcode result = CURLE_OK; - timediff_t timeout_ms; - - DEBUGASSERT(data->conn); - - if(!CONN_SOCK_IDX_VALID(sockindex)) - return CURLE_BAD_FUNCTION_ARGUMENT; - - /* Get the first connected filter that is not shut down already. */ - cf = data->conn->cfilter[sockindex]; - while(cf && (!cf->connected || cf->shutdown)) - cf = cf->next; - - if(!cf) { - *done = TRUE; - return CURLE_OK; - } - - *done = FALSE; - if(!Curl_shutdown_started(data->conn, sockindex)) { - Curl_shutdown_start(data, sockindex, 0); - } - else { - timeout_ms = Curl_shutdown_timeleft(data, data->conn, sockindex); - if(timeout_ms < 0) { - /* info message, since this might be regarded as acceptable */ - infof(data, "shutdown timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - - while(cf) { - if(!cf->shutdown) { - bool cfdone = FALSE; - result = cf->cft->do_shutdown(cf, data, &cfdone); - if(result) { - CURL_TRC_CF(data, cf, "shut down failed with %d", (int)result); - return result; - } - else if(!cfdone) { - CURL_TRC_CF(data, cf, "shut down not done yet"); - return CURLE_OK; - } - CURL_TRC_CF(data, cf, "shut down successfully"); - cf->shutdown = TRUE; - } - cf = cf->next; - } - *done = (!result); - return result; -} - CURLcode Curl_cf_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { @@ -756,7 +699,7 @@ CURLcode Curl_conn_adjust_pollset(struct Curl_easy *data, struct easy_pollset *ps) { CURLcode result = CURLE_OK; - bool want_io = !!ps->n; + const bool want_io = !!ps->n; int8_t i; DEBUGASSERT(data); @@ -776,8 +719,8 @@ CURLcode Curl_conn_adjust_pollset(struct Curl_easy *data, * of its own. */ for(i = 0; (i < (int)CURL_ARRAYSIZE(conn->cfilter)) && !result; ++i) { if(conn->cfilter[i] && - (want_io || !Curl_conn_is_connected(conn, i) || - Curl_shutdown_started(conn, i))) + (want_io || !conn->cfilter[i]->connected || + CURL_CONN_IN_SHUTDOWN(conn, i))) result = Curl_conn_cf_adjust_pollset(conn->cfilter[i], data, ps); } return result; @@ -1095,9 +1038,16 @@ CURLcode Curl_conn_send(struct Curl_easy *data, int8_t sockindex, } } #endif - if(data && data->conn && data->conn->send[sockindex]) - return data->conn->send[sockindex](data, sockindex, buf, write_len, eos, - pnwritten); + if(data && data->conn && data->conn->send[sockindex]) { + CURLcode result = data->conn->send[sockindex](data, sockindex, buf, + write_len, eos, pnwritten); + if(!result && (*pnwritten > write_len)) { + DEBUGASSERT(0); + *pnwritten = 0; + return CURLE_SEND_ERROR; + } + return result; + } *pnwritten = 0; return CURLE_FAILED_INIT; } diff --git a/lib/cfilters.h b/lib/cfilters.h index 979275b72979..e9e343113e48 100644 --- a/lib/cfilters.h +++ b/lib/cfilters.h @@ -438,14 +438,6 @@ void Curl_conn_cntrl_report_stats(struct Curl_easy *data, void Curl_conn_remove_setup_filters(struct Curl_easy *data, int8_t sockindex); -/** - * Shutdown the connection at `sockindex` non-blocking, using timeout - * from `data->set.shutdowntimeout`, default DEFAULT_SHUTDOWN_TIMEOUT_MS. - * Return CURLE_OK and *done == FALSE if not finished. - */ -CURLcode Curl_conn_shutdown(struct Curl_easy *data, - int8_t sockindex, bool *done); - /** * Return if data is pending in some connection filter at chain * `sockindex` for connection `data->conn`. diff --git a/lib/config-android-arm32.h b/lib/config-android-arm32.h index 110f4f6e0b9f..98da1b881c89 100644 --- a/lib/config-android-arm32.h +++ b/lib/config-android-arm32.h @@ -17,7 +17,7 @@ #define CURL_DISABLE_VERBOSE_STRINGS 1 #define CURL_EXTERN_SYMBOL __attribute__ ((__visibility__ ("default"))) #define CURL_SA_FAMILY_T sa_family_t -#define ENABLE_IPV6 1 +#define USE_IPV6 1 #define GETHOSTNAME_TYPE_ARG2 unsigned int #define HAVE_ALARM 1 #define HAVE_ALLOCA_H 1 diff --git a/lib/config-android-arm64.h b/lib/config-android-arm64.h index fbc55ae84d25..9f62fa807210 100644 --- a/lib/config-android-arm64.h +++ b/lib/config-android-arm64.h @@ -17,7 +17,7 @@ #define CURL_DISABLE_VERBOSE_STRINGS 1 #define CURL_EXTERN_SYMBOL __attribute__ ((__visibility__ ("default"))) #define CURL_SA_FAMILY_T sa_family_t -#define ENABLE_IPV6 1 +#define USE_IPV6 1 #define GETHOSTNAME_TYPE_ARG2 size_t #define HAVE_ALARM 1 #define HAVE_ALLOCA_H 1 diff --git a/lib/config-android-x86.h b/lib/config-android-x86.h index dccd9427b7e7..8fc53ac024bb 100644 --- a/lib/config-android-x86.h +++ b/lib/config-android-x86.h @@ -17,7 +17,7 @@ #define CURL_DISABLE_VERBOSE_STRINGS 1 #define CURL_EXTERN_SYMBOL __attribute__ ((__visibility__ ("default"))) #define CURL_SA_FAMILY_T sa_family_t -#define ENABLE_IPV6 1 +#define USE_IPV6 1 #define GETHOSTNAME_TYPE_ARG2 unsigned int #define HAVE_ALARM 1 #define HAVE_ALLOCA_H 1 diff --git a/lib/config-android-x86_64.h b/lib/config-android-x86_64.h index 0c725a67ab68..17e658598d4b 100644 --- a/lib/config-android-x86_64.h +++ b/lib/config-android-x86_64.h @@ -17,7 +17,7 @@ #define CURL_DISABLE_VERBOSE_STRINGS 1 #define CURL_EXTERN_SYMBOL __attribute__ ((__visibility__ ("default"))) #define CURL_SA_FAMILY_T sa_family_t -#define ENABLE_IPV6 1 +#define USE_IPV6 1 #define GETHOSTNAME_TYPE_ARG2 size_t #define HAVE_ALARM 1 #define HAVE_ALLOCA_H 1 diff --git a/lib/config-linux.h b/lib/config-linux.h index c220dd8fd36e..acccece589f6 100644 --- a/lib/config-linux.h +++ b/lib/config-linux.h @@ -81,7 +81,7 @@ /* #undef EGD_SOCKET */ /* Define if you want to enable IPv6 support */ -#define ENABLE_IPV6 1 +#define USE_IPV6 1 /* Define to the type of arg 2 for gethostname. */ #define GETHOSTNAME_TYPE_ARG2 size_t diff --git a/lib/config-macos.h b/lib/config-macos.h index fc65914fe2c0..6cbcdd4e6eec 100644 --- a/lib/config-macos.h +++ b/lib/config-macos.h @@ -15,7 +15,7 @@ #define CURL_DISABLE_VERBOSE_STRINGS 1 #define CURL_EXTERN_SYMBOL __attribute__ ((__visibility__ ("default"))) #define CURL_SA_FAMILY_T sa_family_t -#define ENABLE_IPV6 1 +#define USE_IPV6 1 #define GETHOSTNAME_TYPE_ARG2 size_t #define HAVE_ALARM 1 #define HAVE_ALLOCA_H 1 diff --git a/lib/config-os400.h b/lib/config-os400.h index 95db6d424b65..d4bce9633177 100644 --- a/lib/config-os400.h +++ b/lib/config-os400.h @@ -55,6 +55,9 @@ /* if you have */ #define HAVE_DIRENT_H +/* Define to 1 if you have the 'fchmod' function. */ +#define HAVE_FCHMOD 1 + /* Define to 1 if you have the fcntl function. */ #define HAVE_FCNTL 1 diff --git a/lib/config-qnx.h b/lib/config-qnx.h index 8c522221344b..dd30fd0dd227 100644 --- a/lib/config-qnx.h +++ b/lib/config-qnx.h @@ -81,7 +81,7 @@ /* #undef EGD_SOCKET */ /* Define if you want to enable IPv6 support */ -#define ENABLE_IPV6 1 +#define USE_IPV6 1 /* Define to the type of arg 2 for gethostname. */ #define GETHOSTNAME_TYPE_ARG2 size_t diff --git a/lib/conncache.c b/lib/conncache.c index 01681d4ebbc9..157db0f2d83a 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -34,6 +34,7 @@ #include "conncache.h" #include "curl_share.h" #include "sigpipe.h" +#include "uint-spbset.h" #define CPOOL_IS_LOCKED(c) ((c) && (c)->locked) @@ -59,121 +60,127 @@ } \ } while(0) -/* A list of connections to the same destination. */ +/* A set of connection pool ids to the same destination. */ struct cpool_bundle { - struct Curl_llist conns; /* connections in the bundle */ - size_t dest_len; /* total length of destination, including NUL */ - char dest[1]; /* destination of bundle, allocated to keep dest_len bytes */ + struct uint32_spbset ids; /* bitset of connection `pool_id`s */ + uint32_t refcount; }; -static struct cpool_bundle *cpool_bundle_create(const char *dest) +static void cpool_bundle_unlink(struct cpool_bundle **pbundle) { - struct cpool_bundle *bundle; - size_t dest_len = strlen(dest) + 1; - - bundle = curlx_calloc(1, sizeof(*bundle) + dest_len - 1); - if(!bundle) - return NULL; - Curl_llist_init(&bundle->conns, NULL); - bundle->dest_len = dest_len; - memcpy(bundle->dest, dest, bundle->dest_len); - return bundle; -} - -static void cpool_bundle_destroy(struct cpool_bundle *bundle) -{ - DEBUGASSERT(!Curl_llist_count(&bundle->conns)); - curlx_free(bundle); + if(*pbundle) { + struct cpool_bundle *bundle = *pbundle; + + DEBUGASSERT(bundle->refcount); + *pbundle = NULL; + if(bundle->refcount) + bundle->refcount--; + if(!bundle->refcount) { + Curl_uint32_spbset_destroy(&bundle->ids); + curlx_free(bundle); + } + } } -/* Add a connection to a bundle */ -static void cpool_bundle_add(struct cpool_bundle *bundle, - struct connectdata *conn) +static void cpool_bundle_link(struct cpool_bundle **pdest, + struct cpool_bundle *src) { - DEBUGASSERT(!Curl_node_llist(&conn->cpool_node)); - Curl_llist_append(&bundle->conns, conn, &conn->cpool_node); - conn->bits.in_cpool = TRUE; + if(*pdest != src) { + cpool_bundle_unlink(pdest); + *pdest = src; + if(src) { + DEBUGASSERT(src->refcount < UINT32_MAX); + src->refcount++; + } + } } -/* Remove a connection from a bundle */ -static void cpool_bundle_remove(struct cpool_bundle *bundle, - struct connectdata *conn) +static struct cpool_bundle *cpool_create_bundle(void) { - (void)bundle; - DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns); - Curl_node_remove(&conn->cpool_node); - conn->bits.in_cpool = FALSE; -} + /* created with refcount == 1 */ + struct cpool_bundle *bundle = curlx_calloc(1, sizeof(*bundle)); + if(!bundle) + return NULL; -static void cpool_bundle_free_entry(void *freethis) -{ - cpool_bundle_destroy((struct cpool_bundle *)freethis); + Curl_uint32_spbset_init(&bundle->ids); + bundle->refcount = 1; + return bundle; } -void Curl_cpool_init(struct cpool *cpool, - struct Curl_share *share, - size_t size) +/* Add a connection to a bundle */ +static bool cpool_bundle_add_id(struct cpool *cpool, + struct cpool_bundle *bundle, + const char *destination, + uint32_t pool_id) { - Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str, - curlx_str_key_compare, cpool_bundle_free_entry); - - cpool->share = share; - cpool->initialized = TRUE; + if(!Curl_uint32_spbset_add(&bundle->ids, pool_id)) + return FALSE; + if(Curl_uint32_spbset_count(&bundle->ids) == 1) { + /* first one added, register bundle */ + struct cpool_bundle *new_ref = NULL; + cpool_bundle_link(&new_ref, bundle); + if(!Curl_hash_add(&cpool->dest2bundle, + destination, strlen(destination), new_ref)) { + cpool_bundle_unlink(&new_ref); + return FALSE; + } + } + return TRUE; } -/* Return the "first" connection in the pool or NULL. */ -static struct connectdata *cpool_get_first(struct cpool *cpool) +/* Remove a connection from a bundle */ +static void cpool_bundle_remove_id(struct cpool *cpool, + struct cpool_bundle *bundle, + const char *destination, + uint32_t pool_id) { - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; - struct cpool_bundle *bundle; - struct Curl_llist_node *conn_node; - - Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - for(he = Curl_hash_next_element(&iter); he; - he = Curl_hash_next_element(&iter)) { - bundle = he->ptr; - conn_node = Curl_llist_head(&bundle->conns); - if(conn_node) - return Curl_node_elem(conn_node); + Curl_uint32_spbset_remove(&bundle->ids, pool_id); + if(!Curl_uint32_spbset_count(&bundle->ids)) { + /* Now empty, unregister bundle */ + Curl_hash_delete(&cpool->dest2bundle, destination, strlen(destination)); } - return NULL; } -static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool, - struct connectdata *conn) +static void cpool_bundle_hash_dtor(void *entry) { - return Curl_hash_pick(&cpool->dest2bundle, - conn->destination, strlen(conn->destination) + 1); + struct cpool_bundle *bundle = entry; + cpool_bundle_unlink(&bundle); } -static void cpool_remove_bundle(struct cpool *cpool, - struct cpool_bundle *bundle) +static struct cpool_bundle *cpool_get_bundle(struct cpool *cpool, + const char *destination) { - if(!cpool) - return; - Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len); + struct cpool_bundle *bundle = NULL; + cpool_bundle_link(&bundle, Curl_hash_pick( + &cpool->dest2bundle, destination, strlen(destination))); + return bundle; } static void cpool_remove_conn(struct cpool *cpool, struct connectdata *conn) { - struct Curl_llist *list = Curl_node_llist(&conn->cpool_node); + uint32_t cpid = conn->cpid; + DEBUGASSERT(cpool); - if(list) { - /* The connection is certainly in the pool, but where? */ - struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn); - if(bundle && (list == &bundle->conns)) { - cpool_bundle_remove(bundle, conn); - if(!Curl_llist_count(&bundle->conns)) - cpool_remove_bundle(cpool, bundle); - conn->bits.in_cpool = FALSE; - cpool->num_conn--; - } - else { - /* Should have been in the bundle list */ - DEBUGASSERT(NULL); + if(cpid != UINT32_MAX) { + struct cpool_bundle *bundle; + +#ifdef DEBUGBUILD + { + struct connectdata *pooled_conn = + Curl_uint32_tbl_get(&cpool->conns, cpid); + DEBUGASSERT(conn == pooled_conn); } +#endif + Curl_uint32_tbl_remove(&cpool->conns, cpid); + Curl_uint32_bset_remove(&cpool->idles, cpid); + conn->cpid = UINT32_MAX; + + bundle = cpool_get_bundle(cpool, conn->destination); + DEBUGASSERT(bundle); + if(bundle) + cpool_bundle_remove_id(cpool, bundle, conn->destination, cpid); + cpool_bundle_unlink(&bundle); } } @@ -182,24 +189,22 @@ static void cpool_discard_conn(struct cpool *cpool, struct connectdata *conn, bool aborted) { - struct cshutdn *cshutdn; struct Curl_easy *admin; bool done = FALSE; DEBUGASSERT(data); DEBUGASSERT(!data->conn); DEBUGASSERT(cpool); - DEBUGASSERT(!conn->bits.in_cpool); + DEBUGASSERT(conn->cpid == UINT32_MAX); admin = Curl_get_admin(data); - /* - * If this connection is not marked to force-close, leave it open if there - * are other users of it - */ - if(CONN_INUSE(conn) && !aborted) { + /* We MUST NOT discard a connection that is still in use and would + * trigger Use-After-Free. */ + if(CONN_INUSE(conn)) { CURL_TRC_M(admin, "[CPOOL] not discarding #%" FMT_OFF_T " still in use by %u transfers", conn->connection_id, conn->attached_xfers); + DEBUGASSERT(0); return; } @@ -218,38 +223,67 @@ static void cpool_discard_conn(struct cpool *cpool, done = TRUE; if(!done) { /* Attempt to shutdown the connection right away. */ - Curl_conn_shutdown_once(admin, conn, &done); + Curl_cshutdn_try_once(admin, conn, &done); } - cshutdn = Curl_cshutdn_get(data); - if(done || !cshutdn) - Curl_conn_terminate(admin, conn, FALSE); - else - Curl_cshutdn_add(cshutdn, conn, cpool->num_conn); + if(done || !data->multi) + Curl_cshutdn_terminate(admin, conn, FALSE); + else { + struct Curl_multi *multi = data->multi; + size_t max_shutdowns = multi->max_total_connections; + uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns); + + if(num_conns < max_shutdowns) + max_shutdowns -= num_conns; + else if(max_shutdowns) + max_shutdowns = 1; + else /* no connection limit set, let's restrict growth nevertheless */ + max_shutdowns = CURLMAX(num_conns / 4, 128); + Curl_cshutdn_add(&multi->cshutdn, multi, conn, max_shutdowns); + } +} + +void Curl_cpool_init(struct cpool *cpool, + struct Curl_share *share, + size_t size) +{ + Curl_uint32_tbl_init(&cpool->conns); + Curl_uint32_bset_init(&cpool->idles); + Curl_hash_init(&cpool->dest2bundle, size, CURL_HASH_TYPE_BYTES, + cpool_bundle_hash_dtor); + cpool->share = share; + cpool->initialized = TRUE; + cpool->in_shutdown = FALSE; + cpool->locked = FALSE; } void Curl_cpool_destroy(struct cpool *cpool, struct Curl_easy *admin) { if(cpool && cpool->initialized && admin) { - struct connectdata *conn; - struct Curl_sigpipe_ctx pipe_ctx; + void *entry; + uint32_t cpid; - CURL_TRC_M(admin, "%s[CPOOL] destroy, %zu connections", - cpool->share ? "[SHARE] " : "", cpool->num_conn); + CURL_TRC_M(admin, "%s[CPOOL] destroy, %u connections", + cpool->share ? "[SHARE] " : "", + Curl_uint32_tbl_count(&cpool->conns)); /* Move all connections to the shutdown list */ - sigpipe_init(&pipe_ctx); CPOOL_LOCK(cpool, admin); - conn = cpool_get_first(cpool); - if(conn) + if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) { + struct Curl_sigpipe_ctx pipe_ctx; + + sigpipe_init(&pipe_ctx); sigpipe_apply(admin, &pipe_ctx); - while(conn) { - cpool_remove_conn(cpool, conn); - cpool_discard_conn(cpool, admin, conn, FALSE); - conn = cpool_get_first(cpool); + do { + struct connectdata *conn = entry; + cpool_remove_conn(cpool, conn); + cpool_discard_conn(cpool, admin, conn, FALSE); + } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry)); + sigpipe_restore(&pipe_ctx); } CPOOL_UNLOCK(cpool, admin); - sigpipe_restore(&pipe_ctx); Curl_hash_destroy(&cpool->dest2bundle); + Curl_uint32_bset_destroy(&cpool->idles); + Curl_uint32_tbl_destroy(&cpool->conns); } } @@ -281,7 +315,8 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) data->id = cpool->next_easy_id++; if(cpool->next_easy_id == CURL_OFF_T_MAX) cpool->next_easy_id = 0; - data->state.lastconnect_id = -1; + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; CPOOL_UNLOCK(cpool, data); } @@ -289,87 +324,74 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) /* We should not get here, but in a non-debug build, do something */ DEBUGASSERT(0); data->id = 0; - data->state.lastconnect_id = -1; + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; } } -static struct cpool_bundle *cpool_add_bundle(struct cpool *cpool, - struct connectdata *conn) +static bool cpool_conn_can_be_closed(struct connectdata *conn) { - struct cpool_bundle *bundle; - - bundle = cpool_bundle_create(conn->destination); - if(!bundle) - return NULL; - - if(!Curl_hash_add(&cpool->dest2bundle, - bundle->dest, bundle->dest_len, bundle)) { - cpool_bundle_destroy(bundle); - return NULL; - } - return bundle; + /* CONNECT_ONLY sockets remain in use by the application. */ + return (conn && !CONN_INUSE(conn) && !conn->bits.connect_only); } static struct connectdata *cpool_bundle_get_oldest_idle( + struct cpool *cpool, struct cpool_bundle *bundle, const struct curltime *pnow) { - struct Curl_llist_node *curr; - timediff_t highscore = -1; - timediff_t score; - struct connectdata *oldest_idle = NULL; - struct connectdata *conn; - - curr = Curl_llist_head(&bundle->conns); - while(curr) { - conn = Curl_node_elem(curr); - - if(!CONN_INUSE(conn)) { - /* Set higher score for the age passed since the connection was used */ - score = curlx_ptimediff_ms(pnow, &conn->lastused); - - if(score > highscore) { - highscore = score; - oldest_idle = conn; + uint32_t cpid, oldest_id = UINT32_MAX; + timediff_t unused_ms; + timediff_t oldest_ms = -1; + + if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) { + do { + struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid); + if(cpool_conn_can_be_closed(conn)) { + /* Set higher score for the age passed since the connection was used */ + unused_ms = + curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; + if(unused_ms > oldest_ms) { + oldest_ms = unused_ms; + oldest_id = cpid; + } } - } - curr = Curl_node_next(curr); + else if(!conn) { + DEBUGASSERT(0); + Curl_uint32_spbset_remove(&bundle->ids, cpid); + } + } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid)); } - return oldest_idle; + return Curl_uint32_tbl_get(&cpool->conns, oldest_id); } static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool, const struct curltime *pnow, timediff_t min_age_ms) { - struct Curl_hash_iterator iter; - struct Curl_llist_node *curr; - struct Curl_hash_element *he; - struct cpool_bundle *bundle; - struct connectdata *oldest_idle = NULL; + uint32_t cpid, oldest_id = UINT32_MAX; timediff_t oldest_idle_ms = -1; timediff_t idle_ms; - Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - - for(he = Curl_hash_next_element(&iter); he; - he = Curl_hash_next_element(&iter)) { - struct connectdata *conn; - bundle = he->ptr; - - 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->bits.connect_only) - continue; - idle_ms = curlx_ptimediff_ms(pnow, &conn->lastused); - if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) { - oldest_idle_ms = idle_ms; - oldest_idle = conn; + /* Iterate over all connections that once were idle. They may + * no longer be, in which case we remove them from the set again. */ + if(Curl_uint32_bset_first(&cpool->idles, &cpid)) { + do { + struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid); + if(cpool_conn_can_be_closed(conn)) { + idle_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; + if((idle_ms >= min_age_ms) && (idle_ms > oldest_idle_ms)) { + oldest_idle_ms = idle_ms; + oldest_id = cpid; + } } - } + else { + DEBUGASSERT(conn); + Curl_uint32_bset_remove(&cpool->idles, cpid); + } + } while(Curl_uint32_bset_next(&cpool->idles, cpid, &cpid)); } - return oldest_idle; + return Curl_uint32_tbl_get(&cpool->conns, oldest_id); } static void cpool_conn_close(struct cpool *cpool, @@ -400,9 +422,9 @@ static void cpool_conn_close(struct cpool *cpool, if(do_lock) CPOOL_LOCK(cpool, admin); - if(conn->bits.in_cpool) { + if(conn->cpid != UINT32_MAX) { cpool_remove_conn(cpool, conn); - DEBUGASSERT(!conn->bits.in_cpool); + DEBUGASSERT(conn->cpid == UINT32_MAX); } /* treat the connection as aborted in CONNECT_ONLY situations, @@ -419,7 +441,7 @@ static void cpool_conn_close(struct cpool *cpool, else { /* No multi available, terminate */ infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); - Curl_conn_terminate(admin, conn, !aborted); + Curl_cshutdn_terminate(admin, conn, !aborted); } if(do_lock) @@ -444,141 +466,200 @@ static void cpool_evict_conn(struct cpool *cpool, { if(cpool->share) { cpool_remove_conn(cpool, conn); - Curl_conn_terminate(admin, conn, TRUE); + Curl_cshutdn_terminate(admin, conn, TRUE); } else cpool_conn_close(cpool, admin, conn, FALSE); } -int Curl_cpool_check_limits(struct Curl_easy *data, - struct connectdata *conn, - const struct curltime *pnow) +#define CPOOL_LIMIT_OK 0 +#define CPOOL_LIMIT_DEST 1 +#define CPOOL_LIMIT_TOTAL 2 + +static int cpool_check_limits(struct Curl_easy *data, + uint32_t max_total, + struct connectdata *to_add, + uint32_t max_host, + struct cpool_bundle *bundle, + const struct curltime *pnow) { struct cpool *cpool = cpool_get_instance(data); - struct cshutdn *cshutdn = Curl_cshutdn_get(data); - struct Curl_easy *admin; - struct cpool_bundle *bundle; - size_t dest_limit = 0; - size_t total_limit = 0; - size_t shutdowns; - int res = CPOOL_LIMIT_OK; + struct cshutdn *cshutdn = data->multi ? &data->multi->cshutdn : NULL; + struct Curl_easy *admin = NULL; if(!cpool) return CPOOL_LIMIT_OK; - /* multi determines the limits, no matter who owns the pool */ - if(data->multi) { - dest_limit = data->multi->max_host_connections; - total_limit = data->multi->max_total_connections; - } - - if(!dest_limit && !total_limit) + if(!max_total && !max_host) return CPOOL_LIMIT_OK; - admin = Curl_get_admin(data); - CPOOL_LOCK(cpool, admin); - if(dest_limit) { - size_t live; - - bundle = cpool_find_bundle(cpool, conn); - live = bundle ? Curl_llist_count(&bundle->conns) : 0; - shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination); - while((live + shutdowns) >= dest_limit) { - if(shutdowns) { - /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(cshutdn, conn->destination)) - break; - } - else if(!bundle) - break; - else { - struct connectdata *oldest_idle = NULL; - /* The bundle is full. Extract the oldest connection that may - * be removed now, if there is one. */ - oldest_idle = cpool_bundle_get_oldest_idle(bundle, pnow); - if(!oldest_idle) - break; - /* disconnect the old conn and continue */ - CURL_TRC_M(admin, "Discarding connection #%" FMT_OFF_T - " from %zu to reach destination limit of %zu", - oldest_idle->connection_id, - Curl_llist_count(&bundle->conns), dest_limit); - cpool_evict_conn(cpool, admin, oldest_idle); - - /* in case the bundle was destroyed in disconnect, look it up again */ - bundle = cpool_find_bundle(cpool, conn); - live = bundle ? Curl_llist_count(&bundle->conns) : 0; + if(max_host) { + size_t live = 0, shutdowns = 0; + /* if we are at or above `dest_limit`, try to get rid of connections + * in shutdown and, if that does not lower it, evict idle connections + * from the pool. */ + admin = Curl_get_admin(data); + if(bundle) { + live = Curl_uint32_spbset_count(&bundle->ids); + if(live >= max_host) { + size_t over = live - max_host + 1; + for(; over; --over) { + struct connectdata *oldest_idle = + cpool_bundle_get_oldest_idle(cpool, bundle, pnow); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + CURL_TRC_M(admin, "Shutting down connection #%" FMT_OFF_T + " to '%s' due to destination limit of %u", + oldest_idle->connection_id, oldest_idle->destination, + max_host); + cpool_evict_conn(cpool, admin, oldest_idle); + } + live = Curl_uint32_spbset_count(&bundle->ids); } - shutdowns = Curl_cshutdn_dest_count(cshutdn, conn->destination); } - if((live + shutdowns) >= dest_limit) { - res = CPOOL_LIMIT_DEST; - goto out; + if(cshutdn) { + shutdowns = Curl_cshutdn_dest_count(cshutdn, to_add->destination); + if(shutdowns && ((live + shutdowns) >= max_host)) { + size_t over = live + shutdowns - max_host + 1; + shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin, + to_add->destination, over); + } } + /* We may not have gotten rid of as many connections as we wanted to */ + if((live + shutdowns) >= max_host) + return CPOOL_LIMIT_DEST; } - if(total_limit) { - shutdowns = Curl_cshutdn_count(cshutdn); - while((cpool->num_conn + shutdowns) >= total_limit) { - if(shutdowns) { - /* close one connection in shutdown right away, if we can */ - if(!Curl_cshutdn_close_oldest(cshutdn, NULL)) - break; - } - else { + /* Internal transfers are outside the total limit */ + if(max_total && !data->state.internal) { + size_t shutdowns = 0; + uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns); + + if(!admin) + admin = Curl_get_admin(data); + if(num_conns >= max_total) { + size_t over = num_conns - max_total + 1; + for(; over; --over) { struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool, pnow, 0); if(!oldest_idle) break; /* disconnect the old conn and continue */ - CURL_TRC_M(admin, "Discarding connection #%" - FMT_OFF_T " from %zu to reach total " - "limit of %zu", - oldest_idle->connection_id, cpool->num_conn, total_limit); + CURL_TRC_M(admin, "Shutting down idle connection #%" + FMT_OFF_T " to '%s' due to total limit of %u", + oldest_idle->connection_id, oldest_idle->destination, + max_total); cpool_evict_conn(cpool, admin, oldest_idle); } - shutdowns = Curl_cshutdn_count(cshutdn); } - if((cpool->num_conn + shutdowns) >= total_limit) { - res = CPOOL_LIMIT_TOTAL; - goto out; + + num_conns = Curl_uint32_tbl_count(&cpool->conns); + if(cshutdn) { + shutdowns = Curl_cshutdn_count(cshutdn); + if(shutdowns && (num_conns + shutdowns) >= max_total) { + size_t over = num_conns + shutdowns - max_total + 1; + shutdowns -= Curl_cshutdn_close_oldest(cshutdn, admin, NULL, over); + } } + if((num_conns + shutdowns) >= max_total) + return CPOOL_LIMIT_TOTAL; } -out: - CPOOL_UNLOCK(cpool, admin); - return res; + return CPOOL_LIMIT_OK; } CURLcode Curl_cpool_add(struct Curl_easy *data, - struct connectdata *conn) + struct connectdata *conn, + uint32_t max_total, + uint32_t max_host, + const struct curltime *pnow) { CURLcode result = CURLE_OK; struct cpool_bundle *bundle = NULL; struct cpool *cpool = cpool_get_instance(data); + uint32_t capacity, cpid; DEBUGASSERT(conn); DEBUGASSERT(cpool); if(!cpool) return CURLE_FAILED_INIT; + DEBUGASSERT(conn->cpid == UINT32_MAX); + conn->created = *pnow; + conn->shutdown.start_ms[FIRSTSOCKET] = + conn->shutdown.start_ms[SECONDARYSOCKET] = -1; + CPOOL_LOCK(cpool, data); - bundle = cpool_find_bundle(cpool, conn); + /* Find the bundle, should it exist, and check the limits */ + bundle = cpool_get_bundle(cpool, conn->destination); + + switch(cpool_check_limits(data, max_total, conn, max_host, bundle, pnow)) { + case CPOOL_LIMIT_DEST: + infof(data, "No more connections allowed to host"); + result = CURLE_NO_CONNECTION_AVAILABLE; + goto out; + case CPOOL_LIMIT_TOTAL: + infof(data, "No connections available, total of %u reached.", + data->multi->max_total_connections); + result = CURLE_NO_CONNECTION_AVAILABLE; + goto out; + default: + break; + } + + /* We add the connection */ + capacity = Curl_uint32_tbl_capacity(&cpool->conns); + if(capacity == UINT32_MAX) { + infof(data, "No connections available, pool at max capacity"); + result = CURLE_NO_CONNECTION_AVAILABLE; + goto out; + } + if(Curl_uint32_tbl_count(&cpool->conns) >= capacity) { + uint32_t growth, ncapacity; + + growth = CURLMAX(capacity, 16); + if((UINT32_MAX - growth) >= capacity) + ncapacity = capacity + growth; + else + ncapacity = UINT32_MAX; + /* Grow bitset first, so table resize failure does not leave a too + * short bitset should that one fail. */ + if(Curl_uint32_bset_resize(&cpool->idles, ncapacity) || + Curl_uint32_tbl_resize(&cpool->conns, ncapacity)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + if(!bundle) { - bundle = cpool_add_bundle(cpool, conn); + bundle = cpool_create_bundle(); if(!bundle) { result = CURLE_OUT_OF_MEMORY; goto out; } } - cpool_bundle_add(bundle, conn); + if(!Curl_uint32_tbl_add(&cpool->conns, conn, &cpid)) { + DEBUGASSERT(0); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + conn->cpid = cpid; + + if(!cpool_bundle_add_id(cpool, bundle, conn->destination, cpid)) { + Curl_uint32_tbl_remove(&cpool->conns, cpid); + conn->cpid = UINT32_MAX; + result = CURLE_OUT_OF_MEMORY; + goto out; + } + conn->connection_id = cpool->next_connection_id++; - cpool->num_conn++; CURL_TRC_M(data, "[CPOOL] added connection %" FMT_OFF_T ". " - "The cache now contains %zu members", - conn->connection_id, cpool->num_conn); + "The cache now contains %u members", + conn->connection_id, Curl_uint32_tbl_count(&cpool->conns)); out: + cpool_bundle_unlink(&bundle); CPOOL_UNLOCK(cpool, data); return result; @@ -603,31 +684,18 @@ static bool cpool_foreach(struct Curl_easy *data, struct Curl_easy *data, struct connectdata *conn, void *param)) { - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; + void *entry; + uint32_t cpid; if(!cpool) return FALSE; - Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - struct Curl_llist_node *curr; - struct cpool_bundle *bundle = he->ptr; - he = Curl_hash_next_element(&iter); - - curr = Curl_llist_head(&bundle->conns); - while(curr) { - /* Yes, we need to update curr before calling func(), because func() - might decide to remove the connection */ - struct connectdata *conn = Curl_node_elem(curr); - curr = Curl_node_next(curr); - - if(func(cpool, data, conn, param) == 1) { + if(Curl_uint32_tbl_first(&cpool->conns, &cpid, &entry)) { + do { + struct connectdata *conn = entry; + if(conn && func(cpool, data, conn, param) == 1) return TRUE; - } - } + } while(Curl_uint32_tbl_next(&cpool->conns, cpid, &cpid, &entry)); } return FALSE; } @@ -638,23 +706,26 @@ static bool cpool_foreach(struct Curl_easy *data, * * Return TRUE if idle connection kept in pool, FALSE if closed. */ -bool Curl_cpool_conn_now_idle(struct Curl_easy *data, - struct connectdata *conn) +static bool cpool_conn_now_idle(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) { - unsigned int maxconnects; struct connectdata *oldest_idle = NULL; - struct cpool *cpool = cpool_get_instance(data); struct Curl_easy *admin; + uint32_t maxconnects; bool kept = TRUE; timediff_t min_age_ms = 0; if(!data || !data->multi) return kept; + Curl_uint32_bset_add(&cpool->idles, conn->cpid); + if(!data->multi->maxconnects) { /* Attached transfers is a weak indicator of business. */ uint32_t attached = Curl_multi_xfers_attached(data->multi); - maxconnects = (attached <= UINT_MAX / 2) ? attached * 2 : UINT_MAX; + maxconnects = (attached <= UINT32_MAX / 2) ? attached * 2 : UINT32_MAX; /* We are guessing. So, only evict a "seemingly superfluous" connection * when has not been used for this long, */ min_age_ms = 1000; @@ -663,40 +734,58 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data, maxconnects = data->multi->maxconnects; } - /* remember times, connection had been used just before */ - conn->lastchecked = conn->lastupkeep = conn->lastused = *Curl_pgrs_now(data); - if(cpool && maxconnects) { - /* may be called form a callback already under lock */ - bool do_lock = !CPOOL_IS_LOCKED(cpool); - + if(cpool && maxconnects && (maxconnects < UINT32_MAX)) { + uint32_t num_conns = Curl_uint32_tbl_count(&cpool->conns); admin = Curl_get_admin(data); - if(do_lock) - CPOOL_LOCK(cpool, admin); - if(cpool->num_conn > maxconnects) { - infof(data, "Connection pool is full, closing the oldest of %zu/%u", - cpool->num_conn, maxconnects); - - oldest_idle = cpool_get_oldest_idle(cpool, &conn->lastused, min_age_ms); + if(num_conns > maxconnects) { + infof(data, "Connection pool is full, closing the oldest of %u/%u", + num_conns, maxconnects); + oldest_idle = cpool_get_oldest_idle(cpool, pnow, min_age_ms); kept = (oldest_idle != conn); if(oldest_idle) { cpool_evict_conn(cpool, admin, oldest_idle); } } - if(do_lock) - CPOOL_UNLOCK(cpool, admin); } return kept; } +static void cpool_prune_dead(struct cpool *cpool, + struct Curl_easy *data, + const struct curltime *pnow); + +static bool cpool_find_act(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + cpool_match_result match) +{ + switch(match) { + case CPOOL_MATCH_FOUND: + return TRUE; + case CPOOL_MATCH_TOO_OLD: + if(!cpool_conn_can_be_closed(conn)) + return FALSE; + FALLTHROUGH(); + case CPOOL_MATCH_CLOSE: + cpool_conn_close(cpool, data, conn, FALSE); + break; + default: + break; + } + return FALSE; +} + bool Curl_cpool_find(struct Curl_easy *data, const char *destination, + bool prune_dead, + const struct curltime *pnow, Curl_cpool_conn_match_cb *conn_cb, Curl_cpool_done_match_cb *done_cb, void *userdata) { struct cpool *cpool = cpool_get_instance(data); - struct cpool_bundle *bundle; + struct cpool_bundle *bundle = NULL; bool found = FALSE; DEBUGASSERT(cpool); @@ -705,33 +794,60 @@ bool Curl_cpool_find(struct Curl_easy *data, return FALSE; CPOOL_LOCK(cpool, data); - bundle = Curl_hash_pick(&cpool->dest2bundle, - CURL_UNCONST(destination), - strlen(destination) + 1); + + if(prune_dead) + cpool_prune_dead(cpool, data, pnow); + + if(data->state.last_cpid != UINT32_MAX) { + struct connectdata *conn = + Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid); + if(conn && (data->state.last_conn_id == conn->connection_id)) { + /* Indeed the last connection used by `data` still exists. + * Check that first, so that connection use gets "sticky". */ + found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata)); + if(found) + goto out; + } + else { + /* No longer found here. */ + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; + } + } + + bundle = cpool_get_bundle(cpool, destination); if(bundle) { - struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns); - while(curr) { - struct connectdata *conn = Curl_node_elem(curr); - /* Get next node now. callback might discard current */ - curr = Curl_node_next(curr); - - if(conn_cb(conn, userdata)) { - found = TRUE; - break; - } + uint32_t cpid; + if(Curl_uint32_spbset_first(&bundle->ids, &cpid)) { + do { + struct connectdata *conn = Curl_uint32_tbl_get(&cpool->conns, cpid); + if(conn) { + if(data->state.last_conn_id != conn->connection_id) { + found = cpool_find_act(cpool, data, conn, conn_cb(conn, userdata)); + if(found) + goto out; + } + } + else { + DEBUGASSERT(0); + Curl_uint32_spbset_remove(&bundle->ids, cpid); + } + } while(Curl_uint32_spbset_next(&bundle->ids, cpid, &cpid)); } } +out: if(done_cb) { found = done_cb(userdata); } + cpool_bundle_unlink(&bundle); CPOOL_UNLOCK(cpool, data); return found; } struct cpool_reaper_ctx { size_t reaped; - struct curltime now; + const struct curltime *pnow; }; static int cpool_reap_dead_cb(struct cpool *cpool, @@ -740,13 +856,12 @@ static int cpool_reap_dead_cb(struct cpool *cpool, { struct cpool_reaper_ctx *reaper = param; - if(!CONN_INUSE(conn)) { + if(cpool_conn_can_be_closed(conn)) { if(conn->bits.no_reuse || conn->bits.close || - !Curl_cpool_conn_seems_healthy(conn, admin, &reaper->now)) { + !Curl_cpool_conn_seems_healthy(conn, admin, reaper->pnow)) { /* terminate conn and stop the iteration */ reaper->reaped++; cpool_conn_close(cpool, admin, conn, FALSE); - return 1; } } return 0; /* continue iteration */ @@ -756,32 +871,22 @@ static int cpool_reap_dead_cb(struct cpool *cpool, * This function scans the data's connection pool for half-open/dead * connections, closes and removes them. * The cleanup is done at most once per second. - * - * When called, this transfer has no connection attached. */ -void Curl_cpool_prune_dead(struct cpool *cpool, - struct Curl_easy *data) +static void cpool_prune_dead(struct cpool *cpool, + struct Curl_easy *data, + const struct curltime *pnow) { - struct Curl_easy *admin; - timediff_t elapsed; - - if(!cpool) - return; + timediff_t elapsed_ms = curlx_ptimediff_ms(pnow, &cpool->last_cleanup); - admin = Curl_get_admin(data); - CPOOL_LOCK(cpool, admin); - elapsed = curlx_ptimediff_ms(Curl_pgrs_now(admin), &cpool->last_cleanup); - - if(elapsed >= 1000L) { + if(elapsed_ms >= 1000L) { + struct Curl_easy *admin = Curl_get_admin(data); struct cpool_reaper_ctx reaper; memset(&reaper, 0, sizeof(reaper)); - reaper.now = *Curl_pgrs_now(admin); - while(cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb)) - ; - cpool->last_cleanup = *Curl_pgrs_now(admin); + reaper.pnow = pnow; + cpool_foreach(admin, cpool, &reaper, cpool_reap_dead_cb); + cpool->last_cleanup = *pnow; } - CPOOL_UNLOCK(cpool, admin); } static int conn_upkeep(struct cpool *cpool, @@ -792,19 +897,18 @@ static int conn_upkeep(struct cpool *cpool, const struct curltime *pnow = Curl_pgrs_now(admin); (void)param; - if(curlx_ptimediff_ms(pnow, &conn->lastupkeep) >= + if((curlx_ptimediff_ms(pnow, &conn->created) - conn->lastupkeep_ms) >= admin->set.upkeep_interval_ms) { CURLcode result; - conn->lastupkeep = *pnow; + conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created); /* briefly attach for action */ Curl_attach_connection(admin, conn, FALSE); result = Curl_conn_keep_alive(admin, conn); Curl_detach_connection(admin); - if(result && !CONN_INUSE(conn)) { + if(result && cpool_conn_can_be_closed(conn)) { cpool_conn_close(cpool, admin, conn, FALSE); - return 1; } } return 0; /* continue iteration */ @@ -819,70 +923,66 @@ CURLcode Curl_cpool_upkeep(struct Curl_easy *data) return CURLE_OK; CPOOL_LOCK(cpool, admin); - while(cpool_foreach(admin, cpool, NULL, conn_upkeep)) - ; + cpool_foreach(admin, cpool, NULL, conn_upkeep); CPOOL_UNLOCK(cpool, admin); return CURLE_OK; } -struct cpool_find_ctx { - curl_off_t id; - struct connectdata *conn; -}; - -static int cpool_find_conn(struct cpool *cpool, - struct Curl_easy *data, - struct connectdata *conn, void *param) +struct connectdata *Curl_cpool_get_last_conn(struct Curl_easy *data) { - struct cpool_find_ctx *fctx = param; - (void)cpool; - (void)data; - if(conn->connection_id == fctx->id) { - fctx->conn = conn; - return 1; + struct cpool *cpool = cpool_get_instance(data); + if(cpool && (data->state.last_cpid != UINT32_MAX)) { + struct connectdata *conn; + + CPOOL_LOCK(cpool, data); + conn = Curl_uint32_tbl_get(&cpool->conns, data->state.last_cpid); + CPOOL_UNLOCK(cpool, data); + if(conn && (data->state.last_conn_id == conn->connection_id)) + return conn; + data->state.last_cpid = UINT32_MAX; + data->state.last_conn_id = -1; } - return 0; + return NULL; } -struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, - curl_off_t conn_id) +void Curl_cpool_return(struct Curl_easy *data, + struct connectdata *conn, + Curl_cpool_return_cb *cb, void *cbdata, + const struct curltime *pnow) { struct cpool *cpool = cpool_get_instance(data); - struct cpool_find_ctx fctx; - if(!cpool) - return NULL; - fctx.id = conn_id; - fctx.conn = NULL; CPOOL_LOCK(cpool, data); - cpool_foreach(data, cpool, &fctx, cpool_find_conn); - CPOOL_UNLOCK(cpool, data); - return fctx.conn; -} -void Curl_cpool_do_locked(struct Curl_easy *data, - struct connectdata *conn, - Curl_cpool_conn_do_cb *cb, void *cbdata) -{ - struct cpool *cpool = cpool_get_instance(data); - if(cpool) { - CPOOL_LOCK(cpool, data); - cb(conn, data, cbdata); - CPOOL_UNLOCK(cpool, data); + /* remember times, connection had been used just before */ + conn->lastchecked_ms = conn->lastupkeep_ms = conn->lastused_ms = + curlx_ptimediff_ms(pnow, &conn->created); + + switch(cb(data, conn, cbdata, pnow)) { + case CPOOL_DO_KEEP: + break; + case CPOOL_DO_IDLE: + /* the connection is no longer in use by any transfer */ + if(cpool_conn_now_idle(cpool, data, conn, pnow)) { + /* connection kept in the cpool */ + infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact", + conn->connection_id, conn->origin->user_hostname, + conn->origin->port); + } + else { /* connection was removed from the cpool and destroyed. */ + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; + } + break; + case CPOOL_DO_CLOSE: + Curl_conn_close(data, conn, FALSE); + break; + case CPOOL_DO_TERMINATE: + Curl_conn_close(data, conn, TRUE); + break; } - else - cb(conn, data, cbdata); -} -static int cpool_mark_stale(struct cpool *cpool, - struct Curl_easy *admin, - struct connectdata *conn, void *param) -{ - (void)cpool; - (void)admin; - (void)param; - conn->bits.no_reuse = TRUE; - return 0; + CPOOL_UNLOCK(cpool, data); } static int cpool_reap_no_reuse(struct cpool *cpool, @@ -890,10 +990,9 @@ static int cpool_reap_no_reuse(struct cpool *cpool, struct connectdata *conn, void *param) { (void)param; - if(!CONN_INUSE(conn) && conn->bits.no_reuse) { + conn->bits.no_reuse = TRUE; + if(cpool_conn_can_be_closed(conn)) cpool_conn_close(cpool, admin, conn, FALSE); - return 1; - } return 0; /* continue iteration */ } @@ -901,9 +1000,7 @@ void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin) { if(cpool && admin) { CPOOL_LOCK(cpool, admin); - cpool_foreach(admin, cpool, NULL, cpool_mark_stale); - while(cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse)) - ; + cpool_foreach(admin, cpool, NULL, cpool_reap_no_reuse); CPOOL_UNLOCK(cpool, admin); } } @@ -911,14 +1008,14 @@ void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin) /* A connection has to have been idle for less than 'conn_max_idle_ms' (the success rate is too low after this), or created less than 'conn_max_age_ms' ago, to be subject for reuse. */ -static bool cpool_conn_maxage(struct Curl_easy *data, - struct connectdata *conn, - const struct curltime *pnow) +static bool cpool_conn_too_old(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) { timediff_t age_ms; if(data->set.conn_max_idle_ms) { - age_ms = curlx_ptimediff_ms(pnow, &conn->lastused); + age_ms = curlx_ptimediff_ms(pnow, &conn->created) - conn->lastused_ms; if(age_ms > data->set.conn_max_idle_ms) { infof(data, "Too old connection (%" FMT_TIMEDIFF_T " ms idle, max idle is %" FMT_TIMEDIFF_T " ms), disconnect it", @@ -947,11 +1044,20 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, { struct Curl_easy *admin; bool healthy = TRUE; + bool poll_input; DEBUGASSERT(!data->conn); - if(!CONN_INUSE(conn) && cpool_conn_maxage(data, conn, pnow)) /* too old? */ + if(cpool_conn_can_be_closed(conn) && cpool_conn_too_old(data, conn, pnow)) return FALSE; - if(curlx_ptimediff_ms(pnow, &conn->lastchecked) < 1000) + + /* An idle, non-multiplexed connection must not be reused when it has + * input pending (RFC 9112, section 9.2), so that is polled on every + * reuse. The once a second limit only applies to the other checks. */ + poll_input = cpool_conn_can_be_closed(conn) && + !Curl_conn_is_multiplex(conn, FIRSTSOCKET) && + !conn->scheme->run->connection_is_dead; + if(!poll_input && + (curlx_ptimediff_ms(pnow, &conn->created) - conn->lastchecked_ms) < 1000) return TRUE; admin = Curl_get_admin(data); @@ -966,8 +1072,7 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, Curl_attach_connection(admin, conn, FALSE); healthy = Curl_conn_is_alive(admin, conn, &input_pending); Curl_detach_connection(admin); - if(healthy && input_pending && - !CONN_INUSE(conn) && !Curl_conn_is_multiplex(conn, FIRSTSOCKET)) { + if(healthy && input_pending && poll_input) { /* Non-multiplexed connections without attached transfers should * not have input pending. The input might be a TLS Notify Close, * for all we know. */ @@ -977,43 +1082,22 @@ bool Curl_cpool_conn_seems_healthy(struct connectdata *conn, } if(healthy) - conn->lastchecked = *pnow; + conn->lastchecked_ms = curlx_ptimediff_ms(pnow, &conn->created); return healthy; } -#if 0 -/* Useful for debugging the connection pool */ -void Curl_cpool_print(struct cpool *cpool) +void Curl_cpool_conn_was_used(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) { - struct Curl_hash_iterator iter; - struct Curl_llist_node *curr; - struct Curl_hash_element *he; - - if(!cpool) - return; - - curl_mfprintf(stderr, "=Bundle cache=\n"); - - Curl_hash_start_iterate(cpool->dest2bundle, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - struct cpool_bundle *bundle; - struct connectdata *conn; - - bundle = he->ptr; - - curl_mfprintf(stderr, "%s -", he->key); - curr = Curl_llist_head(bundle->conns); - while(curr) { - conn = Curl_node_elem(curr); - - curl_mfprintf(stderr, " [%p %d]", (void *)conn, conn->refcount); - curr = Curl_node_next(curr); - } - curl_mfprintf(stderr, "\n"); + (void)data; + conn->lastupkeep_ms = curlx_ptimediff_ms(pnow, &conn->created); +} - he = Curl_hash_next_element(&iter); - } +timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow) +{ + (void)data; + return curlx_ptimediff_ms(pnow, &conn->created); } -#endif diff --git a/lib/conncache.h b/lib/conncache.h index 5e766c99d73e..caf240ad4e5b 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -24,6 +24,8 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "uint-bset.h" +#include "uint-table.h" #include "curlx/timeval.h" struct connectdata; @@ -47,15 +49,16 @@ void Curl_conn_close(struct Curl_easy *data, bool aborted); struct cpool { - /* the pooled connections, bundled per destination */ - struct Curl_hash dest2bundle; - size_t num_conn; + struct uint32_tbl conns; /* connections added to this pool */ + struct uint32_bset idles; /* pool_ids of conns being idle */ + struct Curl_hash dest2bundle; /* conn destination sets */ curl_off_t next_connection_id; curl_off_t next_easy_id; struct curltime last_cleanup; struct Curl_share *share; /* != NULL if pool belongs to share */ BIT(locked); BIT(initialized); + BIT(in_shutdown); }; /* Get connection pool instance for data or NULL if none exists */ @@ -76,29 +79,37 @@ void Curl_cpool_destroy(struct cpool *cpool, * Assigns `data->id`. */ void Curl_cpool_xfer_init(struct Curl_easy *data); -/* Get the connection with the given id from `data`'s conn pool. */ -struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, - curl_off_t conn_id); +/* Get the connection last used by data, + * if there was one and it still exists. */ +struct connectdata *Curl_cpool_get_last_conn(struct Curl_easy *data); /* Add the connection to the pool. */ CURLcode Curl_cpool_add(struct Curl_easy *data, - struct connectdata *conn) WARN_UNUSED_RESULT; - -/** - * Return if the pool has reached its configured limits for adding - * the given connection. Try to discard the oldest, idle connections - * to make space. - */ -#define CPOOL_LIMIT_OK 0 -#define CPOOL_LIMIT_DEST 1 -#define CPOOL_LIMIT_TOTAL 2 -int Curl_cpool_check_limits(struct Curl_easy *data, - struct connectdata *conn, - const struct curltime *pnow); + struct connectdata *conn, + uint32_t max_total, + uint32_t max_host, + const struct curltime *pnow) WARN_UNUSED_RESULT; + +/* Connection was used at the given time. */ +void Curl_cpool_conn_was_used(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow); + +/* Return connection age in milliseconds since its creation. */ +timediff_t Curl_cpool_conn_age_ms(struct Curl_easy *data, + struct connectdata *conn, + const struct curltime *pnow); + +typedef enum { + CPOOL_MATCH_FOUND, /* The passed `conn` matches, stop looking further */ + CPOOL_MATCH_CONT, /* No match, continue looking */ + CPOOL_MATCH_CLOSE, /* No match, close `conn`, continue looking */ + CPOOL_MATCH_TOO_OLD /* No match, `conn` is too old, close when idle */ +} cpool_match_result; /* Return of conn is suitable. If so, stops iteration. */ -typedef bool Curl_cpool_conn_match_cb(struct connectdata *conn, - void *userdata); +typedef cpool_match_result + Curl_cpool_conn_match_cb(struct connectdata *conn, void *userdata); /* Act on the result of the find, may override it. */ typedef bool Curl_cpool_done_match_cb(void *userdata); @@ -108,6 +119,8 @@ typedef bool Curl_cpool_done_match_cb(void *userdata); * All callbacks are invoked while the pool's lock is held. * @param data current transfer * @param destination match against `conn->destination` in pool + * @param prune_dead perform reaping of dead connections at start + * @param pnow timestamp of operation * @param conn_cb must be present, called for each connection in the * bundle until it returns TRUE * @return combined result of last conn_db and result_cb or FALSE if no @@ -115,35 +128,28 @@ typedef bool Curl_cpool_done_match_cb(void *userdata); */ bool Curl_cpool_find(struct Curl_easy *data, const char *destination, + bool prune_dead, + const struct curltime *pnow, Curl_cpool_conn_match_cb *conn_cb, Curl_cpool_done_match_cb *done_cb, void *userdata); -/* - * A connection (already in the pool) is now idle. Do any - * cleanups in regard to the pool's limits. - * - * Return TRUE if idle connection kept in pool, FALSE if closed. - */ -bool Curl_cpool_conn_now_idle(struct Curl_easy *data, - struct connectdata *conn); - -/** - * Scans the connection pool for half-open/dead - * connections, closes and removes them. - * The cleanup is done at most once per second. - */ -void Curl_cpool_prune_dead(struct cpool *cpool, - struct Curl_easy *data); - /** * Perform upkeep actions on connections in the transfer's pool. */ CURLcode Curl_cpool_upkeep(struct Curl_easy *data); -typedef void Curl_cpool_conn_do_cb(struct connectdata *conn, - struct Curl_easy *data, - void *cbdata); +typedef enum { + CPOOL_DO_KEEP, /* Keep the connection, still in use */ + CPOOL_DO_IDLE, /* Connection is idle, may get closed now */ + CPOOL_DO_CLOSE, /* Close the connection (clean) */ + CPOOL_DO_TERMINATE /* Terminate the connection (unclean) */ +} cpool_do_result; + +typedef cpool_do_result Curl_cpool_return_cb(struct Curl_easy *data, + struct connectdata *conn, + void *cbdata, + const struct curltime *pnow); /** * Invoked the callback for the given data + connection under the @@ -151,9 +157,10 @@ typedef void Curl_cpool_conn_do_cb(struct connectdata *conn, * The callback is always invoked, even if the transfer has no connection * pool associated. */ -void Curl_cpool_do_locked(struct Curl_easy *data, - struct connectdata *conn, - Curl_cpool_conn_do_cb *cb, void *cbdata); +void Curl_cpool_return(struct Curl_easy *data, + struct connectdata *conn, + Curl_cpool_return_cb *cb, void *cbdata, + const struct curltime *pnow); /* Close all unused connections, prevent reuse of existing ones. */ void Curl_cpool_nw_changed(struct cpool *cpool, struct Curl_easy *admin); diff --git a/lib/connect.c b/lib/connect.c index cb3647544ef8..c2816b4882f7 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -50,10 +50,8 @@ enum alpnid Curl_alpn2alpnid(const unsigned char *name, size_t len) if(!memcmp(name, "h3", 2)) return ALPN_h3; } - else if(len == 8) { - if(!memcmp(name, "http/1.1", 8)) - return ALPN_h1; - } + else if(len == 8 && !memcmp(name, "http/1.1", 8)) + return ALPN_h1; return ALPN_none; /* unknown, probably rubbish input */ } @@ -75,8 +73,8 @@ UNITTEST timediff_t timeleft_now_ms(struct Curl_easy *data, timediff_t timeleft_ms = 0; timediff_t ctimeleft_ms = 0; - if(data->conn && Curl_shutdown_started(data->conn, FIRSTSOCKET)) - return Curl_shutdown_timeleft(data, data->conn, FIRSTSOCKET); + if(data->conn && CURL_CONN_IN_SHUTDOWN(data->conn, FIRSTSOCKET)) + return Curl_cshutdn_timeleft_ms(data, data->conn, FIRSTSOCKET); else if(Curl_is_connecting(data)) { timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ? data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; @@ -114,69 +112,6 @@ timediff_t Curl_timeleft_now_ms(struct Curl_easy *data, return timeleft_now_ms(data, pnow); } -void Curl_shutdown_start(struct Curl_easy *data, int8_t sockindex, - int timeout_ms) -{ - struct connectdata *conn = data->conn; - const struct curltime *pnow = Curl_pgrs_now(data); - - DEBUGASSERT(conn); - conn->shutdown.start[sockindex] = *pnow; - conn->shutdown.timeout_ms = (timeout_ms > 0) ? - (timediff_t)timeout_ms : - ((data->set.shutdowntimeout > 0) ? - data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS); - /* Set a timer, unless we operate on the admin handle */ - if(data->mid) - Curl_expire_set(data, EXPIRE_SHUTDOWN, conn->shutdown.timeout_ms, pnow); - CURL_TRC_M(data, "shutdown start on%s connection", - sockindex ? " secondary" : ""); -} - -timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, - struct connectdata *conn, - int8_t sockindex) -{ - timediff_t left_ms; - - if(!conn->shutdown.start[sockindex].tv_sec || - (conn->shutdown.timeout_ms <= 0)) - return 0; /* not started or no limits */ - - left_ms = conn->shutdown.timeout_ms - - curlx_ptimediff_ms(Curl_pgrs_now(data), - &conn->shutdown.start[sockindex]); - return left_ms ? left_ms : -1; -} - -timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, - struct connectdata *conn) -{ - timediff_t left_ms = 0, ms; - int8_t i; - - for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { - if(!conn->shutdown.start[i].tv_sec) - continue; - ms = Curl_shutdown_timeleft(data, conn, i); - if(ms && (!left_ms || ms < left_ms)) - left_ms = ms; - } - return left_ms; -} - -void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex) -{ - struct curltime *pt = &data->conn->shutdown.start[sockindex]; - memset(pt, 0, sizeof(*pt)); -} - -bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex) -{ - const struct curltime *pt = &conn->shutdown.start[sockindex]; - return (pt->tv_sec > 0) || (pt->tv_usec > 0); -} - /* * Used to extract socket and connectdata struct for the most recent * transfer on the given Curl_easy. @@ -186,31 +121,15 @@ bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex) curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, struct connectdata **connp) { - DEBUGASSERT(data); - /* this works for an easy handle: * - that has been used for curl_easy_perform() * - that is associated with a multi handle, and whose connection * was detached with CURLOPT_CONNECT_ONLY */ - if(data->state.lastconnect_id != -1) { - struct connectdata *conn; - - conn = Curl_cpool_get_conn(data, data->state.lastconnect_id); - if(!conn) { - data->state.lastconnect_id = -1; - if(connp) - *connp = NULL; - return CURL_SOCKET_BAD; - } - - if(connp) - *connp = conn; - return conn->sock[FIRSTSOCKET]; - } + struct connectdata *conn = Curl_cpool_get_last_conn(data); if(connp) - *connp = NULL; - return CURL_SOCKET_BAD; + *connp = conn; + return conn ? conn->sock[FIRSTSOCKET] : CURL_SOCKET_BAD; } void Curl_conncontrol(struct connectdata *conn, int ctrl) @@ -321,6 +240,18 @@ static void conn_report_stats(struct Curl_easy *data, int sockindex) } } +void Curl_conn_report_failed(struct Curl_easy *data, int8_t sockindex) +{ + if(!data->conn || (sockindex != FIRSTSOCKET)) + return; + /* Only filters connected at IP level have info to update. Filters + * still racing for a connection, like happy eyeballing, pass it on + * to a connected one only. */ + (void)Curl_conn_cf_cntrl(data->conn->cfilter[sockindex], data, TRUE, + CF_CTRL_CONN_INFO_UPDATE, 0, NULL); + conn_report_stats(data, sockindex); +} + CURLcode Curl_conn_connect(struct Curl_easy *data, int8_t sockindex, bool blocking, @@ -381,7 +312,7 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, * socket and ip related information. */ Curl_conn_cntrl_update_info(data, data->conn); conn_report_stats(data, sockindex); - data->conn->lastupkeep = *Curl_pgrs_now(data); + Curl_cpool_conn_was_used(data, data->conn, Curl_pgrs_now(data)); VERBOSE(result = conn_connect_trace(data, cf)); VERBOSE(Curl_conn_trc_filters(data, sockindex, "connected")); Curl_conn_remove_setup_filters(data, sockindex); @@ -392,7 +323,7 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, CURL_TRC_CF(data, cf, "Curl_conn_connect(), filter returned %d", (int)result); VERBOSE(Curl_conn_trc_filters(data, sockindex, "failed to connect")); - conn_report_stats(data, sockindex); + Curl_conn_report_failed(data, sockindex); goto out; } @@ -426,7 +357,8 @@ CURLcode Curl_conn_connect(struct Curl_easy *data, goto out; rc = Curl_poll(cpfds.pfds, cpfds.n, - CURLMIN(timeout_ms, (cpfds.n ? 1000 : 10))); + CURLMIN(timeout_ms ? timeout_ms : 1000, + (cpfds.n ? 1000 : 10))); CURL_TRC_CF(data, cf, "Curl_conn_connect(block=1), Curl_poll() -> %d", rc); if(rc < 0) { diff --git a/lib/connect.h b/lib/connect.h index bc8c53aa83b5..a05c1b8ea0b0 100644 --- a/lib/connect.h +++ b/lib/connect.h @@ -41,27 +41,6 @@ timediff_t Curl_timeleft_now_ms(struct Curl_easy *data, #define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */ -#define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000) - -void Curl_shutdown_start(struct Curl_easy *data, int8_t sockindex, - int timeout_ms); - -/* return how much time there is left to shutdown the connection at - * sockindex. Returns 0 if there is no limit or shutdown has not started. */ -timediff_t Curl_shutdown_timeleft(struct Curl_easy *data, - struct connectdata *conn, - int8_t sockindex); - -/* return how much time there is left to shutdown the connection. - * Returns 0 if there is no limit or shutdown has not started. */ -timediff_t Curl_conn_shutdown_timeleft(struct Curl_easy *data, - struct connectdata *conn); - -void Curl_shutdown_clear(struct Curl_easy *data, int8_t sockindex); - -/* TRUE iff shutdown has been started */ -bool Curl_shutdown_started(struct connectdata *conn, int8_t sockindex); - /* * Used to extract socket and connectdata struct for the most recent * transfer on the given Curl_easy. @@ -110,6 +89,13 @@ CURLcode Curl_conn_setup(struct Curl_easy *data, CURLcode Curl_conn_connect(struct Curl_easy *data, int8_t sockindex, bool blocking, bool *done); +/** + * The filter chain at `sockindex` failed to connect, e.g. the TLS handshake + * timed out. Let the filters that did connect, like the TCP socket, report + * their IP information and stats to the transfer. + */ +void Curl_conn_report_failed(struct Curl_easy *data, int8_t sockindex); + /* Set conn to allow multiplexing. */ void Curl_conn_set_multiplex(struct connectdata *conn); diff --git a/lib/cookie.c b/lib/cookie.c index 3255668ec7b9..cea93d6142ac 100644 --- a/lib/cookie.c +++ b/lib/cookie.c @@ -448,29 +448,6 @@ static bool parse_first_pair(struct Curl_easy *data, struct Cookie *co, return TRUE; } -static bool parse_flag(struct Curl_easy *data, struct Cookie *co, - const struct CookieInfo *ci, - struct Curl_str *name, bool secure) -{ - /* - * secure cookies are only allowed to be set when the connection is - * using a secure protocol, or when the cookie is being set by - * reading from file - */ - if(curlx_str_casecompare(name, "secure")) { - if(secure || !ci->running) - co->secure = TRUE; - else { - infof(data, "skipped cookie because not 'secure'"); - return FALSE; - } - } - else if(curlx_str_casecompare(name, "httponly")) - co->httponly = TRUE; - - return TRUE; -} - static bool parse_domain(struct Curl_easy *data, struct Cookie *co, struct Curl_str *cookie_domain, struct Curl_str *val, @@ -587,7 +564,6 @@ static void parse_expires(struct Cookie *co, struct Curl_str *val, static CURLcode parse_cookie_header(struct Curl_easy *data, struct Cookie *co, - const struct CookieInfo *ci, bool *okay, /* if the cookie was fine */ const char *ptr, /* the header */ const char *domain, /* default domain */ @@ -611,7 +587,8 @@ parse_cookie_header(struct Curl_easy *data, struct Curl_str name; /* we have a = pair or a stand-alone word here */ - if(!curlx_str_cspn(&ptr, &name, ";\r\n=")) { + int rc = curlx_str_cspn(&ptr, &name, ";\r\n="); + if(!rc || (rc == STRE_SHORT)) { struct Curl_str val; bool sep = FALSE; curlx_str_trimblanks(&name); @@ -638,20 +615,32 @@ parse_cookie_header(struct Curl_easy *data, if(!parse_first_pair(data, co, cookie, &name, &val, sep)) return CURLE_OK; } - else if(!sep) { - if(!parse_flag(data, co, ci, &name, secure_origin)) + + /* whether there's separator or not */ + else if(curlx_str_casecompare(&name, "secure")) { + if(secure_origin) + co->secure = TRUE; + else { + infof(data, "skipped cookie because not 'secure'"); return CURLE_OK; + } } - else if(curlx_str_casecompare(&name, "path")) - cookie[COOKIE_PATH] = val; - else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) { - if(!parse_domain(data, co, &cookie[COOKIE_DOMAIN], &val, &domain)) - return CURLE_OK; + else if(curlx_str_casecompare(&name, "httponly")) + co->httponly = TRUE; + else if(sep) { + /* only if a separating equals sign is present */ + if(curlx_str_casecompare(&name, "path")) + cookie[COOKIE_PATH] = val; + else if(curlx_str_casecompare(&name, "domain")) { + if(curlx_strlen(&val) && + !parse_domain(data, co, &cookie[COOKIE_DOMAIN], &val, &domain)) + return CURLE_OK; + } + else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) + parse_maxage(co, &val, &now); + else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) + parse_expires(co, &val, &now); } - else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) - parse_maxage(co, &val, &now); - else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) - parse_expires(co, &val, &now); } } while(!curlx_str_single(&ptr, ';')); @@ -847,12 +836,11 @@ static bool is_public_suffix(struct Curl_easy *data, co->tailmatch = FALSE; } else { - /* libpsl says localhost is a PSL, we think not */ - acceptable = - curl_strequal(lcookie, "localhost") || - /* note that this PSL function returns the opposite value than - psl_is_cookie_domain_acceptable() does */ - !psl_is_public_suffix(psl, lcookie); + bool suffix = psl_is_public_suffix(psl, lcookie); + acceptable = TRUE; + if(suffix) + /* switch off tailmatching when set for a PSL domain */ + co->tailmatch = FALSE; } Curl_psl_release(data); } @@ -1018,7 +1006,7 @@ CURLcode Curl_cookie_add(struct Curl_easy *data, memset(co, 0, sizeof(comem)); if(flags & COOKIE_HTTPHEADER) - result = parse_cookie_header(data, co, ci, &okay, + result = parse_cookie_header(data, co, &okay, lineptr, domain, path, flags & COOKIE_SECURE); else result = parse_netscape(co, ci, &okay, lineptr, flags & COOKIE_SECURE); @@ -1181,7 +1169,9 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, if(fp) { struct dynbuf buf; bool eof = FALSE; - curlx_dyn_init(&buf, MAX_COOKIE_LINE); + /* accept a little longer lines so that a full cookie fits and does not + abort the file read */ + curlx_dyn_init(&buf, MAX_COOKIE_LINE + 48); do { result = Curl_get_line(&buf, fp, &eof); if(!result) { @@ -1198,8 +1188,9 @@ static CURLcode cookie_load(struct Curl_easy *data, const char *file, (headerline ? COOKIE_HTTPHEADER : 0) | COOKIE_NOEXPIRE | COOKIE_SECURE | (flags & COOKIE_NOPSL)); - /* File reading cookie failures are not propagated back to the - caller because there is no way to do that */ + /* Ignore individual cookie problems unless we ran out of memory */ + if(result != CURLE_OUT_OF_MEMORY) + result = CURLE_OK; } } while(!result && !eof); curlx_dyn_free(&buf); /* free the line buffer */ diff --git a/lib/cshutdn.c b/lib/cshutdn.c index 10cafdd065e3..a5ca88405282 100644 --- a/lib/cshutdn.c +++ b/lib/cshutdn.c @@ -38,11 +38,62 @@ #include "curlx/strparse.h" -struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data) +#define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000) + +void Curl_cshutdn_start_timer(struct Curl_easy *data, int8_t sockindex, + int timeout_ms) +{ + struct connectdata *conn = data->conn; + const struct curltime *pnow = Curl_pgrs_now(data); + + DEBUGASSERT(conn); + conn->shutdown.start_ms[sockindex] = + curlx_ptimediff_ms(pnow, &conn->created); + conn->shutdown.timeout_ms = (timeout_ms > 0) ? + (timediff_t)timeout_ms : + ((data->set.shutdowntimeout > 0) ? + data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS); + /* Set a timer, unless we operate on the admin handle */ + if(data->mid) + Curl_expire_set(data, EXPIRE_SHUTDOWN, conn->shutdown.timeout_ms, pnow); + CURL_TRC_M(data, "shutdown start on%s connection", + sockindex ? " secondary" : ""); +} + +timediff_t Curl_cshutdn_timeleft_ms(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex) +{ + timediff_t left_ms; + + if(conn->shutdown.start_ms[sockindex] < 0) + return 0; /* not started or no limits */ + + left_ms = conn->shutdown.timeout_ms - + (curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) - + conn->shutdown.start_ms[sockindex]); + return left_ms ? left_ms : -1; +} + +static timediff_t cshutdn_conn_timeleft(struct Curl_easy *data, + struct connectdata *conn) +{ + timediff_t left_ms = 0, ms; + int8_t i; + + for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { + if(conn->shutdown.start_ms[i] < 0) + continue; + ms = Curl_cshutdn_timeleft_ms(data, conn, i); + if(ms && (!left_ms || ms < left_ms)) + left_ms = ms; + } + return left_ms; +} + +void Curl_cshutdn_clear_timer(struct Curl_easy *data, int8_t sockindex) { - if(data && data->multi) - return &data->multi->cshutdn; - return NULL; + data->conn->shutdown.start_ms[sockindex] = -1; } static void cshutdn_run_conn_handler(struct Curl_easy *data, @@ -73,6 +124,62 @@ static void cshutdn_run_conn_handler(struct Curl_easy *data, } } +CURLcode Curl_cshutdn_try_once_idx(struct Curl_easy *data, + int8_t sockindex, bool *done) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + + DEBUGASSERT(data->conn); + + if(!CONN_SOCK_IDX_VALID(sockindex)) + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* Get the first connected filter that is not shut down already. */ + cf = data->conn->cfilter[sockindex]; + while(cf && (!cf->connected || cf->shutdown)) + cf = cf->next; + + if(!cf) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + if(!CURL_CONN_IN_SHUTDOWN(data->conn, sockindex)) { + Curl_cshutdn_start_timer(data, sockindex, 0); + } + else { + timeout_ms = Curl_cshutdn_timeleft_ms(data, data->conn, sockindex); + if(timeout_ms < 0) { + /* info message, since this might be regarded as acceptable */ + infof(data, "shutdown timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + + while(cf) { + if(!cf->shutdown) { + bool cfdone = FALSE; + result = cf->cft->do_shutdown(cf, data, &cfdone); + if(result) { + CURL_TRC_CF(data, cf, "shut down failed with %d", (int)result); + return result; + } + else if(!cfdone) { + CURL_TRC_CF(data, cf, "shut down not done yet"); + return CURLE_OK; + } + CURL_TRC_CF(data, cf, "shut down successfully"); + cf->shutdown = TRUE; + } + cf = cf->next; + } + *done = (!result); + return result; +} + static void cshutdn_run_once(struct Curl_easy *data, struct connectdata *conn, bool *done) @@ -83,8 +190,8 @@ static void cshutdn_run_once(struct Curl_easy *data, /* We expect to be attached when called */ DEBUGASSERT(data->conn == conn); - if(!Curl_shutdown_started(conn, FIRSTSOCKET)) { - Curl_shutdown_start(data, FIRSTSOCKET, 0); + if(!CURL_CONN_IN_SHUTDOWN(conn, FIRSTSOCKET)) { + Curl_cshutdn_start_timer(data, FIRSTSOCKET, 0); } cshutdn_run_conn_handler(data, conn); @@ -95,14 +202,14 @@ static void cshutdn_run_once(struct Curl_easy *data, } if(!conn->bits.connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET)) - r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1); + r1 = Curl_cshutdn_try_once_idx(data, FIRSTSOCKET, &done1); else { r1 = CURLE_OK; done1 = TRUE; } if(!conn->bits.connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET)) - r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2); + r2 = Curl_cshutdn_try_once_idx(data, SECONDARYSOCKET, &done2); else { r2 = CURLE_OK; done2 = TRUE; @@ -114,7 +221,7 @@ static void cshutdn_run_once(struct Curl_easy *data, conn->bits.shutdown_filters = TRUE; } -void Curl_conn_shutdown_once(struct Curl_easy *admin, +void Curl_cshutdn_try_once(struct Curl_easy *admin, struct connectdata *conn, bool *done) { @@ -126,16 +233,16 @@ void Curl_conn_shutdown_once(struct Curl_easy *admin, Curl_detach_connection(admin); } -void Curl_conn_terminate(struct Curl_easy *admin, - struct connectdata *conn, - bool do_shutdown) +void Curl_cshutdn_terminate(struct Curl_easy *admin, + struct connectdata *conn, + bool do_shutdown) { bool done; /* there must be a connection to close */ DEBUGASSERT(conn); /* it must be removed from the connection pool */ - DEBUGASSERT(!conn->bits.in_cpool); + DEBUGASSERT(conn->cpid == UINT32_MAX); /* the transfer must be detached from the connection */ DEBUGASSERT(admin && !admin->conn); DEBUGASSERT(!admin->mid); @@ -165,45 +272,46 @@ void Curl_conn_terminate(struct Curl_easy *admin, } } -static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, - const char *destination) +static size_t cshutdn_destroy_oldest(struct cshutdn *cshutdn, + struct Curl_easy *admin, + const char *destination, + size_t count) { - struct Curl_llist_node *e; - struct connectdata *conn; - - e = Curl_llist_head(&cshutdn->list); - while(e) { - conn = Curl_node_elem(e); - if(!destination || !strcmp(destination, conn->destination)) - break; - e = Curl_node_next(e); - } + struct Curl_sigpipe_ctx sigpipe_ctx; + size_t removed = 0; + uint32_t i = 0; - if(e) { - struct Curl_sigpipe_ctx sigpipe_ctx; - conn = Curl_node_elem(e); - Curl_node_remove(e); - sigpipe_init(&sigpipe_ctx); - sigpipe_apply(cshutdn->multi->admin, &sigpipe_ctx); - Curl_conn_terminate(cshutdn->multi->admin, conn, FALSE); - sigpipe_restore(&sigpipe_ctx); - return TRUE; + sigpipe_init(&sigpipe_ctx); + while((removed < count) && (i < CURL_PTRARRAY_COUNT(&cshutdn->conns))) { + struct connectdata *conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); + if(!destination || !strcmp(destination, conn->destination)) { + Curl_ptrarray_remove(&cshutdn->conns, i); + sigpipe_apply(admin, &sigpipe_ctx); + Curl_cshutdn_terminate(admin, conn, FALSE); + ++removed; + } + else + ++i; } - return FALSE; + sigpipe_restore(&sigpipe_ctx); + return removed; } -bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, - const char *destination) +size_t Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, + struct Curl_easy *admin, + const char *destination, + size_t count) { if(cshutdn) { - return cshutdn_destroy_oldest(cshutdn, destination); + return cshutdn_destroy_oldest(cshutdn, admin, destination, count); } - return FALSE; + return 0; } #define NUM_POLLS_ON_STACK 10 static CURLcode cshutdn_wait(struct cshutdn *cshutdn, + struct Curl_easy *admin, int timeout_ms) { struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; @@ -212,7 +320,7 @@ static CURLcode cshutdn_wait(struct cshutdn *cshutdn, Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); - result = Curl_cshutdn_add_pollfds(cshutdn, &cpfds); + result = Curl_cshutdn_add_pollfds(cshutdn, admin, &cpfds); if(result) goto out; @@ -224,37 +332,35 @@ static CURLcode cshutdn_wait(struct cshutdn *cshutdn, } static void cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_sigpipe_ctx *sigpipe_ctx) { - struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); - struct Curl_llist_node *enext; - struct Curl_easy *admin = cshutdn->multi->admin; struct connectdata *conn; timediff_t next_expire_ms = 0, ms; + uint32_t i = 0; bool done; - if(!e) + if(!CURL_PTRARRAY_COUNT(&cshutdn->conns)) return; - CURL_TRC_M(admin, "[SHUTDOWN] perform on %zu connections", - Curl_llist_count(&cshutdn->list)); + CURL_TRC_M(admin, "[SHUTDOWN] perform on %u connections", + CURL_PTRARRAY_COUNT(&cshutdn->conns)); sigpipe_apply(admin, sigpipe_ctx); - while(e) { - enext = Curl_node_next(e); - conn = Curl_node_elem(e); - Curl_conn_shutdown_once(admin, conn, &done); + while(i < CURL_PTRARRAY_COUNT(&cshutdn->conns)) { + conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); + Curl_cshutdn_try_once(admin, conn, &done); if(done) { - Curl_node_remove(e); - Curl_conn_terminate(admin, conn, FALSE); + Curl_ptrarray_remove(&cshutdn->conns, i); + Curl_cshutdn_terminate(admin, conn, FALSE); } else { + ++i; /* idata has one timer list, but maybe more than one connection. * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ - ms = Curl_conn_shutdown_timeleft(admin, conn); + ms = cshutdn_conn_timeleft(admin, conn); if(ms && (!next_expire_ms || (ms < next_expire_ms))) next_expire_ms = ms; } - e = enext; } if(next_expire_ms) @@ -262,23 +368,22 @@ static void cshutdn_perform(struct cshutdn *cshutdn, } static void cshutdn_terminate_all(struct cshutdn *cshutdn, + struct Curl_easy *admin, int timeout_ms) { - struct Curl_easy *admin = cshutdn->multi->admin; struct curltime started = *Curl_pgrs_now(admin); - struct Curl_llist_node *e; struct Curl_sigpipe_ctx sigpipe_ctx; CURL_TRC_M(admin, "[SHUTDOWN] shutdown all"); sigpipe_init(&sigpipe_ctx); - while(Curl_llist_head(&cshutdn->list)) { + while(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { timediff_t spent_ms; int remain_ms; - cshutdn_perform(cshutdn, &sigpipe_ctx); + cshutdn_perform(cshutdn, admin, &sigpipe_ctx); - if(!Curl_llist_head(&cshutdn->list)) { + if(!CURL_PTRARRAY_COUNT(&cshutdn->conns)) { CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished cleanly"); break; } @@ -292,43 +397,34 @@ static void cshutdn_terminate_all(struct cshutdn *cshutdn, } remain_ms = timeout_ms - (int)spent_ms; - if(cshutdn_wait(cshutdn, remain_ms)) { + if(cshutdn_wait(cshutdn, admin, remain_ms)) { CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, aborted"); break; } } /* Terminate any remaining. */ - e = Curl_llist_head(&cshutdn->list); - while(e) { - struct connectdata *conn = Curl_node_elem(e); - Curl_node_remove(e); - Curl_conn_terminate(admin, conn, FALSE); - e = Curl_llist_head(&cshutdn->list); + while(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { + struct connectdata *conn = Curl_ptrarray_remove(&cshutdn->conns, 0); + Curl_cshutdn_terminate(admin, conn, FALSE); } - DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); + DEBUGASSERT(!CURL_PTRARRAY_COUNT(&cshutdn->conns)); sigpipe_restore(&sigpipe_ctx); } -int Curl_cshutdn_init(struct cshutdn *cshutdn, - struct Curl_multi *multi) +void Curl_cshutdn_init(struct cshutdn *cshutdn) { - DEBUGASSERT(multi); - cshutdn->multi = multi; - Curl_llist_init(&cshutdn->list, NULL); - cshutdn->initialized = TRUE; - return 0; /* good */ + Curl_ptrarray_init(&cshutdn->conns); } void Curl_cshutdn_destroy(struct cshutdn *cshutdn, struct Curl_easy *admin) { - if(cshutdn->initialized && admin) { + if(admin) { int timeout_ms = 0; /* for testing, run graceful shutdown */ #ifdef DEBUGBUILD - DEBUGASSERT(!admin->mid); { const char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); if(p) { @@ -338,19 +434,18 @@ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, } } #endif - - CURL_TRC_M(admin, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", - Curl_llist_count(&cshutdn->list), timeout_ms); - cshutdn_terminate_all(cshutdn, timeout_ms); + if(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { + CURL_TRC_M(admin, "[SHUTDOWN] destroy, %u connections, timeout=%dms", + CURL_PTRARRAY_COUNT(&cshutdn->conns), timeout_ms); + cshutdn_terminate_all(cshutdn, admin, timeout_ms); + } } - cshutdn->multi = NULL; - cshutdn->initialized = FALSE; } size_t Curl_cshutdn_count(struct cshutdn *cshutdn) { if(cshutdn) { - return Curl_llist_count(&cshutdn->list); + return CURL_PTRARRAY_COUNT(&cshutdn->conns); } return 0; } @@ -360,84 +455,81 @@ size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, { if(cshutdn) { size_t n = 0; - struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); - while(e) { - struct connectdata *conn = Curl_node_elem(e); + uint32_t i = 0; + for(i = 0; i < CURL_PTRARRAY_COUNT(&cshutdn->conns); ++i) { + struct connectdata *conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); if(!strcmp(destination, conn->destination)) ++n; - e = Curl_node_next(e); } return n; } return 0; } -static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn, +static CURLMcode cshutdn_update_ev(struct Curl_multi *multi, struct connectdata *conn) { - struct Curl_easy *admin = cshutdn->multi->admin; CURLMcode mresult; - - DEBUGASSERT(cshutdn); - DEBUGASSERT(cshutdn->multi->socket_cb); - - Curl_attach_connection(admin, conn, FALSE); - mresult = Curl_multi_ev_assess_conn(cshutdn->multi, admin, conn); - Curl_detach_connection(admin); + Curl_attach_connection(multi->admin, conn, FALSE); + mresult = Curl_multi_ev_assess_conn(multi, multi->admin, conn); + Curl_detach_connection(multi->admin); return mresult; } void Curl_cshutdn_add(struct cshutdn *cshutdn, + struct Curl_multi *multi, struct connectdata *conn, - size_t conns_in_pool) + size_t max_shutdowns) { - struct Curl_easy *admin = cshutdn->multi->admin; - size_t max_total = cshutdn->multi->max_total_connections; + struct Curl_easy *admin = multi->admin; /* Add the connection to our shutdown list for non-blocking shutdown * during multi processing. */ - if(max_total > 0 && - (max_total <= (conns_in_pool + Curl_llist_count(&cshutdn->list)))) { + if(max_shutdowns <= CURL_PTRARRAY_COUNT(&cshutdn->conns)) { CURL_TRC_M(admin, "[SHUTDOWN] discarding oldest shutdown connection " - "due to connection limit of %zu", max_total); - cshutdn_destroy_oldest(cshutdn, NULL); + "due to shutdown limit of %zu", max_shutdowns); + cshutdn_destroy_oldest(cshutdn, admin, NULL, 1); } - if(cshutdn->multi->socket_cb) { - if(cshutdn_update_ev(cshutdn, conn)) { - CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%" - FMT_OFF_T, conn->connection_id); - Curl_conn_terminate(admin, conn, FALSE); - return; - } + if(multi->socket_cb && cshutdn_update_ev(multi, conn)) { + CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%" + FMT_OFF_T, conn->connection_id); + Curl_cshutdn_terminate(admin, conn, FALSE); + return; } - Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); + if(Curl_ptrarray_add(&cshutdn->conns, conn)) { + CURL_TRC_M(admin, "[SHUTDOWN] adding failed, discarding #%" + FMT_OFF_T, conn->connection_id); + Curl_cshutdn_terminate(admin, conn, FALSE); + return; + } CURL_TRC_M(admin, "[SHUTDOWN] added #%" FMT_OFF_T - " to shutdowns, now %zu conns in shutdown", - conn->connection_id, Curl_llist_count(&cshutdn->list)); + " to shutdowns, now %u conns in shutdown", + conn->connection_id, CURL_PTRARRAY_COUNT(&cshutdn->conns)); } void Curl_cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_sigpipe_ctx *sigpipe_ctx) { - cshutdn_perform(cshutdn, sigpipe_ctx); + cshutdn_perform(cshutdn, admin, sigpipe_ctx); } /* return fd_set info about the shutdown connections */ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, fd_set *read_fd_set, fd_set *write_fd_set, int *maxfd) { - if(Curl_llist_head(&cshutdn->list)) { - struct Curl_easy *admin = cshutdn->multi->admin; - struct Curl_llist_node *e; + if(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { struct easy_pollset ps; + uint32_t i; Curl_pollset_init(&ps); - for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { - unsigned int i; - struct connectdata *conn = Curl_node_elem(e); + for(i = 0; i < CURL_PTRARRAY_COUNT(&cshutdn->conns); ++i) { + unsigned int j; + struct connectdata *conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); CURLcode result; Curl_pollset_reset(&ps); @@ -448,15 +540,15 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, if(result) continue; - for(i = 0; i < ps.n; i++) { - curl_socket_t sock = ps.sockets[i]; + for(j = 0; j < ps.n; j++) { + curl_socket_t sock = ps.sockets[j]; if(!FDSET_SOCK(sock)) continue; - if(ps.actions[i] & CURL_POLL_IN) + if(ps.actions[j] & CURL_POLL_IN) FD_SET(sock, read_fd_set); - if(ps.actions[i] & CURL_POLL_OUT) + if(ps.actions[j] & CURL_POLL_OUT) FD_SET(sock, write_fd_set); - if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) && + if((ps.actions[j] & (CURL_POLL_OUT | CURL_POLL_IN)) && ((int)sock > *maxfd)) *maxfd = (int)sock; } @@ -467,20 +559,20 @@ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, /* return information about the shutdown connections */ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_waitfds *cwfds) { unsigned int need = 0; - if(Curl_llist_head(&cshutdn->list)) { - struct Curl_llist_node *e; - struct Curl_easy *admin = cshutdn->multi->admin; + if(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { struct easy_pollset ps; struct connectdata *conn; + uint32_t i; CURLcode result; Curl_pollset_init(&ps); - for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { - conn = Curl_node_elem(e); + for(i = 0; i < CURL_PTRARRAY_COUNT(&cshutdn->conns); ++i) { + conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); Curl_pollset_reset(&ps); Curl_attach_connection(admin, conn, FALSE); result = Curl_conn_adjust_pollset(admin, conn, &ps); @@ -495,19 +587,19 @@ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, } CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct curl_pollfds *cpfds) { CURLcode result = CURLE_OK; - if(Curl_llist_head(&cshutdn->list)) { - struct Curl_llist_node *e; + if(CURL_PTRARRAY_COUNT(&cshutdn->conns)) { struct easy_pollset ps; - struct Curl_easy *admin = cshutdn->multi->admin; struct connectdata *conn; + uint32_t i; Curl_pollset_init(&ps); - for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { - conn = Curl_node_elem(e); + for(i = 0; i < CURL_PTRARRAY_COUNT(&cshutdn->conns); ++i) { + conn = CURL_PTRARRAY_GET(&cshutdn->conns, i); Curl_pollset_reset(&ps); Curl_attach_connection(admin, conn, FALSE); result = Curl_conn_adjust_pollset(admin, conn, &ps); diff --git a/lib/cshutdn.h b/lib/cshutdn.h index a0338f64882f..d42d2cb9f6db 100644 --- a/lib/cshutdn.h +++ b/lib/cshutdn.h @@ -24,6 +24,8 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "ptrarray.h" + struct connectdata; struct Curl_easy; struct curl_pollfds; @@ -32,37 +34,53 @@ struct Curl_multi; struct Curl_share; struct Curl_sigpipe_ctx; -/* Run the shutdown of the connection once. + +/* Terminates the connection, e.g. closes and destroys it. + * If `do_shutdown` is TRUE, the shutdown will be run once. + * Takes ownership of `conn`. `conn` MUST no longer be in + * a pool or on a shutdown list. */ +void Curl_cshutdn_terminate(struct Curl_easy *admin, + struct connectdata *conn, + bool do_shutdown); + +/* Start the shutdown timer, + * marks the connection sockindex as being shut down. */ +void Curl_cshutdn_start_timer(struct Curl_easy *data, int8_t sockindex, + int timeout_ms); +/* Clear the shutdown timer at sockindex again. */ +void Curl_cshutdn_clear_timer(struct Curl_easy *data, int8_t sockindex); + +/* return how much time there is left to shutdown the connection at + * sockindex. Returns 0 if there is no limit or shutdown has not started. */ +timediff_t Curl_cshutdn_timeleft_ms(struct Curl_easy *data, + struct connectdata *conn, + int8_t sockindex); + +#define CURL_CONN_IN_SHUTDOWN(c,i) ((c)->shutdown.start_ms[(i)] >= 0) + +/* Shutdown the connection at `sockindex` non-blocking. + * Will start the shutdown timer if not already set. + * Return CURLE_OK and *done == FALSE if not finished. */ +CURLcode Curl_cshutdn_try_once_idx(struct Curl_easy *data, + int8_t sockindex, bool *done); + +/* Run the shutdown of the connection once non-blocking for both + * socket indices. Will start the shutdown timer if not already set. * Shortly attach/detach the admin handle 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_conn_shutdown_once(struct Curl_easy *admin, - struct connectdata *conn, - bool *done); - -/* Terminates the connection, e.g. closes and destroys it. - * If `do_shutdown` is TRUE, the shutdown will be run once before - * terminating it. - * Takes ownership of `conn`. */ -void Curl_conn_terminate(struct Curl_easy *admin, - struct connectdata *conn, - bool do_shutdown); +void Curl_cshutdn_try_once(struct Curl_easy *admin, + struct connectdata *conn, bool *done); /* A `cshutdown` is always owned by a multi handle to maintain * the connections to be shut down. It registers timers and * sockets to monitor via the multi handle. */ struct cshutdn { - struct Curl_llist list; /* connections being shut down */ - struct Curl_multi *multi; /* the multi owning this */ - BIT(initialized); + struct ptrarray conns; /* connections being shut down */ }; -/* Get the cshutdn instance relevant for `data` or NULL if there is none */ -struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data); - /* Init as part of the given multi handle. */ -int Curl_cshutdn_init(struct cshutdn *cshutdn, - struct Curl_multi *multi); +void Curl_cshutdn_init(struct cshutdn *cshutdn); /* Terminate all remaining connections and free resources. */ void Curl_cshutdn_destroy(struct cshutdn *cshutdn, @@ -75,31 +93,38 @@ size_t Curl_cshutdn_count(struct cshutdn *cshutdn); size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn, const char *destination); -/* Close the oldest connection in shutdown to destination or, +/* Close the oldest `count` connections in shutdown to destination or, * when destination is NULL for any destination. - * Return TRUE if a connection has been closed. */ -bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, - const char *destination); + * Return the number of connections closed. */ +size_t Curl_cshutdn_close_oldest(struct cshutdn *cshutdn, + struct Curl_easy *admin, + const char *destination, + size_t count); /* Add a connection to have it shut down. Terminate the oldest - * connection when total connection limit of multi is being reached. */ + * connection when shutdowns exceed max_shutdowns. */ void Curl_cshutdn_add(struct cshutdn *cshutdn, + struct Curl_multi *multi, struct connectdata *conn, - size_t conns_in_pool); + size_t max_shutdowns); /* Add sockets and POLLIN/OUT flags for connections being shut down. */ CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct curl_pollfds *cpfds); unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_waitfds *cwfds); void Curl_cshutdn_setfds(struct cshutdn *cshutdn, + struct Curl_easy *admin, fd_set *read_fd_set, fd_set *write_fd_set, int *maxfd); /* Run maintenance on all connections. */ void Curl_cshutdn_perform(struct cshutdn *cshutdn, + struct Curl_easy *admin, struct Curl_sigpipe_ctx *sigpipe_ctx); #endif /* HEADER_CURL_CSHUTDN_H */ diff --git a/lib/curl_addrinfo.c b/lib/curl_addrinfo.c index cf366ce4538f..83faa6fed000 100644 --- a/lib/curl_addrinfo.c +++ b/lib/curl_addrinfo.c @@ -328,7 +328,7 @@ struct Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port) addr = (void *)ai->ai_addr; /* storage area for this info */ memcpy(&addr->sin_addr, curr, sizeof(struct in_addr)); - addr->sin_family = (CURL_SA_FAMILY_T)(he->h_addrtype); + addr->sin_family = (CURL_SA_FAMILY_T)he->h_addrtype; addr->sin_port = htons((unsigned short)port); break; @@ -337,7 +337,7 @@ struct Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port) addr6 = (void *)ai->ai_addr; /* storage area for this info */ memcpy(&addr6->sin6_addr, curr, sizeof(struct in6_addr)); - addr6->sin6_family = (CURL_SA_FAMILY_T)(he->h_addrtype); + addr6->sin6_family = (CURL_SA_FAMILY_T)he->h_addrtype; addr6->sin6_port = htons((unsigned short)port); break; #endif diff --git a/lib/curl_config-cmake.h.in b/lib/curl_config-cmake.h.in index 0e50e839e43c..fb4e0352d9db 100644 --- a/lib/curl_config-cmake.h.in +++ b/lib/curl_config-cmake.h.in @@ -148,9 +148,6 @@ /* disabled shuffle DNS feature */ #cmakedefine CURL_DISABLE_SHUFFLE_DNS 1 -/* disables SMB */ -#cmakedefine CURL_ENABLE_SMB 1 - /* disables SMTP */ #cmakedefine CURL_DISABLE_SMTP 1 @@ -206,6 +203,9 @@ /* Define to 1 if you have the `accept4' function. */ #cmakedefine HAVE_ACCEPT4 1 +/* Define to 1 if you have the `fchmod' function. */ +#cmakedefine HAVE_FCHMOD 1 + /* Define to 1 if you have the `fnmatch' function. */ #cmakedefine HAVE_FNMATCH 1 @@ -698,7 +698,7 @@ ${SIZEOF_TIME_T_CODE} /* if libpsl is in use */ #cmakedefine USE_LIBPSL 1 -/* if you want to use OpenLDAP code instead of legacy ldap implementation */ +/* if you want to use OpenLDAP code instead of legacy LDAP implementation */ #cmakedefine USE_OPENLDAP 1 /* if OpenSSL is in use */ diff --git a/lib/curl_fnmatch.c b/lib/curl_fnmatch.c index d165554f8db5..58475a25f2f6 100644 --- a/lib/curl_fnmatch.c +++ b/lib/curl_fnmatch.c @@ -248,99 +248,132 @@ static int setcharset(const unsigned char **p, unsigned char *charset) return SETCHARSET_FAIL; } -static int loop(const unsigned char *pattern, const unsigned char *string, - int maxstars) +/* match one pattern token against one string character, advancing both */ +static int matchtoken(const unsigned char **pp, const unsigned char **sp) +{ + const unsigned char *p = *pp; + const unsigned char *s = *sp; + + switch(*p) { + case '?': + break; + case '\\': + if(p[1]) + p++; + if(*s != *p) + return 0; + break; + case '[': { + unsigned char charset[CURLFNM_CHSET_SIZE]; + const unsigned char *pp2 = p + 1; /* Copy in case of syntax error. */ + bool found = FALSE; + if(!setcharset(&pp2, charset)) + return -1; /* Syntax error in set; mismatch! */ + if(charset[(unsigned int)*s]) + found = TRUE; + else if(charset[CURLFNM_ALNUM]) + found = ISALNUM(*s); + else if(charset[CURLFNM_ALPHA]) + found = ISALPHA(*s); + else if(charset[CURLFNM_DIGIT]) + found = ISDIGIT(*s); + else if(charset[CURLFNM_XDIGIT]) + found = ISXDIGIT(*s); + else if(charset[CURLFNM_PRINT]) + found = ISPRINT(*s); + else if(charset[CURLFNM_SPACE]) + found = ISBLANK(*s); + else if(charset[CURLFNM_UPPER]) + found = ISUPPER(*s); + else if(charset[CURLFNM_LOWER]) + found = ISLOWER(*s); + else if(charset[CURLFNM_BLANK]) + found = ISBLANK(*s); + else if(charset[CURLFNM_GRAPH]) + found = ISGRAPH(*s); + + if(charset[CURLFNM_NEGATE]) + found = !found; + + if(!found) + return 0; + *pp = pp2 + 1; + *sp = s + 1; + return 1; + } + default: + if(*p != *s) + return 0; + break; + } + *pp = p + 1; + *sp = s + 1; + return 1; +} + +/* greedy match with backtracking to the most recent '*' */ +static int loop(const unsigned char *pattern, const unsigned char *string) { const unsigned char *p = pattern; const unsigned char *s = string; - unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 }; - - for(;;) { - const unsigned char *pp; + const unsigned char *star_p = NULL; + const unsigned char *star_s = NULL; + int maxstars = 2; - switch(*p) { - case '*': + while(*s) { + if(*p == '*') { if(!maxstars) return CURL_FNMATCH_NOMATCH; - /* Regroup consecutive stars and question marks. This can be done because - '*?*?*' can be expressed as '??*'. */ + maxstars--; for(;;) { - if(*++p == '\0') + p++; + if(!*p) return CURL_FNMATCH_MATCH; if(*p == '?') { - if(!*s++) + if(!*s) return CURL_FNMATCH_NOMATCH; + s++; } else if(*p != '*') break; } - /* Skip string characters until we find a match with pattern suffix. */ - for(maxstars--; *s; s++) { - if(loop(p, s, maxstars) == CURL_FNMATCH_MATCH) - return CURL_FNMATCH_MATCH; - } - return CURL_FNMATCH_NOMATCH; - case '?': - if(!*s) - return CURL_FNMATCH_NOMATCH; - s++; - p++; - break; - case '\0': - return *s ? CURL_FNMATCH_NOMATCH : CURL_FNMATCH_MATCH; - case '\\': - if(p[1]) - p++; - if(*s++ != *p++) + star_p = p; + star_s = s; + } + else if(*p) { + const unsigned char *np = p; + const unsigned char *ns = s; + int r = matchtoken(&np, &ns); + if(r < 0) return CURL_FNMATCH_NOMATCH; - break; - case '[': - pp = p + 1; /* Copy in case of syntax error in set. */ - if(setcharset(&pp, charset)) { - bool found = FALSE; - if(!*s) - return CURL_FNMATCH_NOMATCH; - if(charset[(unsigned int)*s]) - found = TRUE; - else if(charset[CURLFNM_ALNUM]) - found = ISALNUM(*s); - else if(charset[CURLFNM_ALPHA]) - found = ISALPHA(*s); - else if(charset[CURLFNM_DIGIT]) - found = ISDIGIT(*s); - else if(charset[CURLFNM_XDIGIT]) - found = ISXDIGIT(*s); - else if(charset[CURLFNM_PRINT]) - found = ISPRINT(*s); - else if(charset[CURLFNM_SPACE]) - found = ISBLANK(*s); - else if(charset[CURLFNM_UPPER]) - found = ISUPPER(*s); - else if(charset[CURLFNM_LOWER]) - found = ISLOWER(*s); - else if(charset[CURLFNM_BLANK]) - found = ISBLANK(*s); - else if(charset[CURLFNM_GRAPH]) - found = ISGRAPH(*s); - - if(charset[CURLFNM_NEGATE]) - found = !found; - - if(!found) - return CURL_FNMATCH_NOMATCH; - p = pp + 1; - s++; - break; + if(r) { + p = np; + s = ns; + continue; } - /* Syntax error in set; mismatch! */ - return CURL_FNMATCH_NOMATCH; - - default: - if(*p++ != *s++) + if(!star_p) return CURL_FNMATCH_NOMATCH; - break; + p = star_p; + s = ++star_s; } + else if(star_p) { + p = star_p; + s = ++star_s; + } + else + return CURL_FNMATCH_NOMATCH; + } + while(*p == '*') { + if(!maxstars) + return CURL_FNMATCH_NOMATCH; + maxstars--; + do { + p++; + if(*p == '?') + return CURL_FNMATCH_NOMATCH; + } while(*p == '*'); } + return *p ? CURL_FNMATCH_NOMATCH : CURL_FNMATCH_MATCH; } /* @@ -354,7 +387,7 @@ int Curl_fnmatch(void *ptr, const char *pattern, const char *string) return CURL_FNMATCH_FAIL; } return loop((const unsigned char *)pattern, - (const unsigned char *)string, 2); + (const unsigned char *)string); } #else /* HAVE_FNMATCH */ diff --git a/lib/curl_fopen.c b/lib/curl_fopen.c index 75054ffb77bf..10c04179443a 100644 --- a/lib/curl_fopen.c +++ b/lib/curl_fopen.c @@ -102,13 +102,6 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, 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; @@ -133,17 +126,25 @@ CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, #ifdef _WIN32 fd = curlx_open(tempstore, _O_WRONLY | _O_CREAT | _O_EXCL, _S_IREAD | _S_IWRITE); -#elif (defined(ANDROID) || defined(__ANDROID__)) && \ - (defined(__i386__) || defined(__arm__)) - fd = curlx_open(tempstore, O_WRONLY | O_CREAT | O_EXCL, - (mode_t)(S_IRUSR | S_IWUSR | sb.st_mode)); #else fd = curlx_open(tempstore, O_WRONLY | O_CREAT | O_EXCL, - S_IRUSR | S_IWUSR | sb.st_mode); + (mode_t)(S_IRUSR | S_IWUSR | 0600)); #endif if(fd == -1) goto fail; +#ifdef HAVE_FCHMOD + { + curlx_struct_stat nsb; + if((curlx_fstat(fd, &nsb) != -1) && + (nsb.st_uid == sb.st_uid) && (nsb.st_gid == sb.st_gid)) { + /* if the user and group are the same, clone the original mode */ + if(fchmod(fd, (mode_t)sb.st_mode) == -1) + goto fail; + } + } +#endif + *fh = curlx_fdopen(fd, FOPEN_WRITETEXT); if(!*fh) goto fail; diff --git a/lib/curl_fopen.h b/lib/curl_fopen.h index bd402d44894a..5bdd2f20f8d9 100644 --- a/lib/curl_fopen.h +++ b/lib/curl_fopen.h @@ -23,7 +23,7 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, FILE **fh, char **tempname); diff --git a/lib/curl_setup.h b/lib/curl_setup.h index e91ab3b6b15f..c10fbf9ec0df 100644 --- a/lib/curl_setup.h +++ b/lib/curl_setup.h @@ -125,11 +125,6 @@ #define SECURITY_WIN32 /* for */ #endif -/* Compatibility */ -#ifdef ENABLE_IPV6 -#define USE_IPV6 1 -#endif - /* * Include configuration script results or hand-crafted * configuration file for platforms which lack config tool. @@ -257,54 +252,6 @@ # include #endif -/* - * Disable other protocols when http is the only one desired. - */ -#ifdef HTTP_ONLY -# ifndef CURL_DISABLE_DICT -# define CURL_DISABLE_DICT -# endif -# ifndef CURL_DISABLE_FILE -# define CURL_DISABLE_FILE -# endif -# ifndef CURL_DISABLE_FTP -# define CURL_DISABLE_FTP -# endif -# ifndef CURL_DISABLE_GOPHER -# define CURL_DISABLE_GOPHER -# endif -# ifndef CURL_DISABLE_IMAP -# define CURL_DISABLE_IMAP -# endif -# ifndef CURL_DISABLE_LDAP -# define CURL_DISABLE_LDAP -# endif -# ifndef CURL_DISABLE_LDAPS -# define CURL_DISABLE_LDAPS -# endif -# ifndef CURL_DISABLE_MQTT -# define CURL_DISABLE_MQTT -# endif -# ifndef CURL_DISABLE_POP3 -# define CURL_DISABLE_POP3 -# endif -# ifndef CURL_DISABLE_RTSP -# define CURL_DISABLE_RTSP -# endif -# ifndef CURL_DISABLE_SMTP -# define CURL_DISABLE_SMTP -# endif -# ifndef CURL_DISABLE_TELNET -# define CURL_DISABLE_TELNET -# endif -# ifndef CURL_DISABLE_TFTP -# define CURL_DISABLE_TFTP -# endif -# ifndef CURL_DISABLE_WEBSOCKETS -# define CURL_DISABLE_WEBSOCKETS -# endif -#endif - /* * When HTTP is disabled, disable HTTP-only features */ @@ -394,6 +341,30 @@ #include +#if !defined(HAVE_PIPE2) && \ + ((defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ + __MAC_OS_X_VERSION_MIN_REQUIRED >= 270000) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ + __IPHONE_OS_VERSION_MIN_REQUIRED >= 270000) || \ + (defined(__TV_OS_VERSION_MAX_ALLOWED) && \ + __TV_OS_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__TV_OS_VERSION_MIN_REQUIRED) && \ + __TV_OS_VERSION_MIN_REQUIRED >= 270000) || \ + (defined(__WATCH_OS_VERSION_MAX_ALLOWED) && \ + __WATCH_OS_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__WATCH_OS_VERSION_MIN_REQUIRED) && \ + __WATCH_OS_VERSION_MIN_REQUIRED >= 270000) || \ + (defined(__VISION_OS_VERSION_MAX_ALLOWED) && \ + __VISION_OS_VERSION_MAX_ALLOWED >= 270000 && \ + defined(__VISION_OS_VERSION_MIN_REQUIRED) && \ + __VISION_OS_VERSION_MIN_REQUIRED >= 270000)) +#define HAVE_PIPE2 +#endif + /* Helper macro to expand and concatenate two macros. * Direct macros concatenation does not work because macros * are not expanded before direct concatenation. @@ -1161,7 +1132,7 @@ typedef unsigned int curl_bit; #define SOCKEISCONN WSAEISCONN #define SOCKEMSGSIZE WSAEMSGSIZE /* Use literal value to work around clang-tidy <=20 misreporting - 'readability-uppercase-literal-suffix' with mingw-w64 headers */ + 'readability-uppercase-literal-suffix' with mingw-w64 headers */ #define SOCKENOMEM 8L /* WSA_NOT_ENOUGH_MEMORY */ #define SOCKETIMEDOUT WSAETIMEDOUT #define SOCKEWOULDBLOCK WSAEWOULDBLOCK @@ -1620,8 +1591,8 @@ typedef struct sockaddr_un { /* OpenSSL 3 marks these functions deprecated but we have no replacements (yet) so tell the compiler to not warn for them: - DES_* (for NTLM) - - EVP_PKEY_get1_RSA, MD5_*, RSA_flags, RSA_free (auto-skipped for OpenSSL - built with no-deprecated) */ + - EVP_PKEY_get1_RSA, RSA_flags, RSA_free (auto-skipped for OpenSSL built + with no-deprecated) */ # define OPENSSL_SUPPRESS_DEPRECATED # ifdef _WIN32 /* Silence LibreSSL warnings about wincrypt.h collision. Works in 3.8.2+ */ diff --git a/lib/curl_share.c b/lib/curl_share.c index cbadd57d3795..21ed3e9ec679 100644 --- a/lib/curl_share.c +++ b/lib/curl_share.c @@ -379,10 +379,9 @@ CURLSHcode Curl_share_lock_share(struct Curl_share *share, if(!share) return CURLSHE_INVALID; - if(share->specifier & (unsigned int)(1 << type)) { - if(share->lockfunc) /* only call this if set! */ - share->lockfunc(data, type, accesstype, share->clientdata); - } + if(share->specifier & (unsigned int)(1 << type) && + share->lockfunc) /* only call this if set! */ + share->lockfunc(data, type, accesstype, share->clientdata); /* else if we do not share this, pretend successful lock */ return CURLSHE_OK; @@ -401,10 +400,9 @@ CURLSHcode Curl_share_unlock_share(struct Curl_share *share, if(!share) return CURLSHE_INVALID; - if(share->specifier & (unsigned int)(1 << type)) { - if(share->unlockfunc) /* only call this if set! */ - share->unlockfunc(data, type, share->clientdata); - } + if(share->specifier & (unsigned int)(1 << type) && + share->unlockfunc) /* only call this if set! */ + share->unlockfunc(data, type, share->clientdata); return CURLSHE_OK; } @@ -426,7 +424,8 @@ CURLcode Curl_share_easy_unlink(struct Curl_easy *data) if((share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) { if(data->conn) Curl_detach_connection(data); - data->state.lastconnect_id = -1; + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; } #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) @@ -459,6 +458,12 @@ CURLcode Curl_share_easy_link(struct Curl_easy *data, if(share) { bool locked = share_lock_acquire(share, data); + /* Forget identifiers from the previously used connection cache. */ + if((share->specifier & (1 << CURL_LOCK_DATA_CONNECT))) { + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; + } + share_ref_inc(share); data->share = share; diff --git a/lib/curl_trc.c b/lib/curl_trc.c index 2bf81e811964..62bac14fe6d7 100644 --- a/lib/curl_trc.c +++ b/lib/curl_trc.c @@ -92,7 +92,7 @@ static struct curl_trc_feat Curl_trc_feat_ids = { static size_t trc_print_ids(struct Curl_easy *data, char *buf, size_t maxlen) { - curl_off_t cid = data->state.lastconnect_id; + curl_off_t cid = data->state.last_conn_id; if(data->id >= 0) { if(cid >= 0) return curl_msnprintf(buf, maxlen, CURL_TRC_FMT_IDSDC, data->id, cid); @@ -109,7 +109,7 @@ static size_t trc_print_ids(struct Curl_easy *data, char *buf, size_t maxlen) static size_t trc_end_buf(char *buf, size_t len, size_t maxlen, bool addnl) { /* make sure we end the trace line in `buf` properly. It needs - * to end with a terminating '\0' or '\n\0' */ + * to end with a null-terminator '\0' or '\n\0' */ if(len >= (maxlen - (addnl ? 2 : 1))) { len = maxlen - 5; buf[len++] = '.'; @@ -316,6 +316,7 @@ static const char * const Curl_trc_timer_names[] = { "FTP_ACCEPT", "ALPN_EYEBALLS", "SHUTDOWN", + "SERVER_RESPONSE", }; static const char *trc_timer_name(int tid) diff --git a/lib/curlx/base64.c b/lib/curlx/base64.c index 519b045cfe30..c0d842fef2da 100644 --- a/lib/curlx/base64.c +++ b/lib/curlx/base64.c @@ -37,12 +37,39 @@ const char curlx_base64encdec[] = static const char base64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; -static const unsigned char decodetable[] = { - 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, - 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +static const unsigned char decodetable[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 62, 0xff, 0xff, 0xff, 63, + 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /* * curlx_base64_decode() @@ -69,7 +96,6 @@ CURLcode curlx_base64_decode(const char *src, size_t rawlen = 0; unsigned char *pos; unsigned char *newstr; - unsigned char lookup[256]; *outptr = NULL; *outlen = 0; @@ -102,25 +128,19 @@ CURLcode curlx_base64_decode(const char *src, pos = newstr; - memset(lookup, 0xff, sizeof(lookup)); - memcpy(&lookup['+'], decodetable, sizeof(decodetable)); - /* Decode the complete quantums first */ for(i = 0; i < fullQuantums; i++) { - unsigned char val; - unsigned int x = 0; - int j; - - for(j = 0; j < 4; j++) { - val = lookup[(unsigned char)*src++]; - if(val == 0xff) /* bad symbol */ - goto bad; - x = (x << 6) | val; - } - pos[2] = x & 0xff; - pos[1] = (x >> 8) & 0xff; - pos[0] = (x >> 16) & 0xff; + unsigned char v0 = decodetable[(unsigned char)src[0]]; + unsigned char v1 = decodetable[(unsigned char)src[1]]; + unsigned char v2 = decodetable[(unsigned char)src[2]]; + unsigned char v3 = decodetable[(unsigned char)src[3]]; + if((v0 | v1 | v2 | v3) & 0x80) + goto bad; + pos[0] = (unsigned char)((v0 << 2) | (v1 >> 4)); + pos[1] = (unsigned char)((v1 << 4) | (v2 >> 2)); + pos[2] = (unsigned char)((v2 << 6) | v3); pos += 3; + src += 4; } if(padding) { /* this means either 8 or 16 bits output */ @@ -137,15 +157,15 @@ CURLcode curlx_base64_decode(const char *src, goto bad; } else { - val = lookup[(unsigned char)*src++]; + val = decodetable[(unsigned char)*src++]; if(val == 0xff) /* bad symbol */ goto bad; x = (x << 6) | val; } } if(padding == 1) - pos[1] = (x >> 8) & 0xff; - pos[0] = (x >> 16) & 0xff; + pos[1] = (unsigned char)((x >> 8) & 0xff); + pos[0] = (unsigned char)((x >> 16) & 0xff); pos += 3 - padding; } @@ -187,10 +207,15 @@ static CURLcode base64_encode(const char *table64, return CURLE_OUT_OF_MEMORY; while(insize >= 3) { - *output++ = table64[in[0] >> 2]; - *output++ = table64[((in[0] & 0x03) << 4) | (in[1] >> 4)]; - *output++ = table64[((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6)]; - *output++ = table64[in[2] & 0x3F]; + /* Load all three input bytes before storing output, avoiding reloads when + the compiler cannot rule out aliasing. Extract four 6-bit indices from + the packed value; byte-wise loads avoid alignment and endian issues. */ + uint32_t bits = ((uint32_t)in[0] << 16) | + ((uint32_t)in[1] << 8) | in[2]; + *output++ = table64[bits >> 18]; + *output++ = table64[(bits >> 12) & 0x3F]; + *output++ = table64[(bits >> 6) & 0x3F]; + *output++ = table64[bits & 0x3F]; insize -= 3; in += 3; } diff --git a/lib/curlx/inet_pton.c b/lib/curlx/inet_pton.c index 23e76725fe68..c2a6eecbb939 100644 --- a/lib/curlx/inet_pton.c +++ b/lib/curlx/inet_pton.c @@ -121,9 +121,8 @@ static int inet_pton6(const char *src, unsigned char *dst) endp = tp + IN6ADDRSZ; colonp = NULL; /* Leading :: requires some special handling. */ - if(*src == ':') - if(*++src != ':') - return 0; + if(*src == ':' && *++src != ':') + return 0; curtok = src; saw_xdigit = 0; val = 0; @@ -143,6 +142,8 @@ static int inet_pton6(const char *src, unsigned char *dst) colonp = tp; continue; } + if(!*src) + return 0; if(tp + INT16SZ > endp) return 0; *tp++ = (unsigned char)((val >> 8) & 0xff); diff --git a/lib/curlx/strparse.c b/lib/curlx/strparse.c index 9e8fe4963d53..a17aa3c137e0 100644 --- a/lib/curlx/strparse.c +++ b/lib/curlx/strparse.c @@ -191,12 +191,17 @@ static int str_num_base(const char **linep, curl_off_t *nump, curl_off_t max, } while(valid_digit(*p, m)); } else { - do { - int n = curlx_hexval(*p++); - if(num > ((max - n) / base)) - return STRE_OVERFLOW; - num = (num * base) + n; - } while(valid_digit(*p, m)); + curl_off_t cutoff = (max - (base - 1)) / base; + while(valid_digit(*p, m)) { + if(num <= cutoff) + num = (num * base) + curlx_hexval(*p++); + else { + int n = curlx_hexval(*p++); + if(num > ((max - n) / base)) + return STRE_OVERFLOW; + num = (num * base) + n; + } + } } *nump = num; *linep = p; diff --git a/lib/curlx/warnless.c b/lib/curlx/warnless.c index 5fc36ac9a876..be26650bb41a 100644 --- a/lib/curlx/warnless.c +++ b/lib/curlx/warnless.c @@ -48,22 +48,18 @@ #define CURL_MASK_USIZE_T ((size_t)~0) #define CURL_MASK_SSIZE_T (CURL_MASK_USIZE_T >> 1) -/* - * unsigned long to unsigned char - */ -unsigned char curlx_ultouc(unsigned long ulnum) -{ #ifdef __INTEL_COMPILER #pragma warning(push) #pragma warning(disable:810) /* conversion may lose significant bits */ #endif +/* + * unsigned long to unsigned char + */ +unsigned char curlx_ultouc(unsigned long ulnum) +{ DEBUGASSERT(ulnum <= (unsigned long)CURL_MASK_UCHAR); return (unsigned char)(ulnum & (unsigned long)CURL_MASK_UCHAR); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -71,17 +67,8 @@ unsigned char curlx_ultouc(unsigned long ulnum) */ int curlx_uztosi(size_t uznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(uznum <= (size_t)CURL_MASK_SINT); return (int)(uznum & (size_t)CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -89,19 +76,10 @@ int curlx_uztosi(size_t uznum) */ unsigned long curlx_uztoul(size_t uznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - #if ULONG_MAX < SIZE_MAX DEBUGASSERT(uznum <= (size_t)CURL_MASK_ULONG); #endif return (unsigned long)(uznum & (size_t)CURL_MASK_ULONG); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -109,19 +87,10 @@ unsigned long curlx_uztoul(size_t uznum) */ unsigned int curlx_uztoui(size_t uznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - #if UINT_MAX < SIZE_MAX DEBUGASSERT(uznum <= (size_t)CURL_MASK_UINT); #endif return (unsigned int)(uznum & (size_t)CURL_MASK_UINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -129,20 +98,11 @@ unsigned int curlx_uztoui(size_t uznum) */ int curlx_sltosi(long slnum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(slnum >= 0); #if INT_MAX < LONG_MAX DEBUGASSERT((unsigned long)slnum <= (unsigned long)CURL_MASK_SINT); #endif return (int)(slnum & (long)CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -150,20 +110,11 @@ int curlx_sltosi(long slnum) */ unsigned int curlx_sltoui(long slnum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(slnum >= 0); #if UINT_MAX < LONG_MAX DEBUGASSERT((unsigned long)slnum <= (unsigned long)CURL_MASK_UINT); #endif return (unsigned int)(slnum & (long)CURL_MASK_UINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -171,18 +122,9 @@ unsigned int curlx_sltoui(long slnum) */ unsigned short curlx_sltous(long slnum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(slnum >= 0); DEBUGASSERT((unsigned long)slnum <= (unsigned long)CURL_MASK_USHORT); return (unsigned short)(slnum & (long)CURL_MASK_USHORT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -190,17 +132,8 @@ unsigned short curlx_sltous(long slnum) */ ssize_t curlx_uztosz(size_t uznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(uznum <= (size_t)CURL_MASK_SSIZE_T); return (ssize_t)(uznum & (size_t)CURL_MASK_SSIZE_T); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -208,17 +141,8 @@ ssize_t curlx_uztosz(size_t uznum) */ size_t curlx_sotouz(curl_off_t sonum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(sonum >= 0); return (size_t)(sonum & (curl_off_t)CURL_MASK_USIZE_T); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -226,20 +150,11 @@ size_t curlx_sotouz(curl_off_t sonum) */ int curlx_sztosi(ssize_t sznum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(sznum >= 0); #if INT_MAX < SSIZE_MAX DEBUGASSERT((size_t)sznum <= (size_t)CURL_MASK_SINT); #endif return (int)(sznum & (ssize_t)CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -247,17 +162,8 @@ int curlx_sztosi(ssize_t sznum) */ unsigned short curlx_uitous(unsigned int uinum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(uinum <= (unsigned int)CURL_MASK_USHORT); return (unsigned short)(uinum & (unsigned int)CURL_MASK_USHORT); - -#ifdef __INTEL_COMPILER -#pragma warning(pop) -#endif } /* @@ -265,18 +171,13 @@ unsigned short curlx_uitous(unsigned int uinum) */ size_t curlx_sitouz(int sinum) { -#ifdef __INTEL_COMPILER -#pragma warning(push) -#pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - DEBUGASSERT(sinum >= 0); return (size_t)sinum; +} #ifdef __INTEL_COMPILER #pragma warning(pop) #endif -} size_t curlx_uitouz(unsigned int uinum) { diff --git a/lib/curlx/fopen.c b/lib/curlx/win32-fopen.c similarity index 95% rename from lib/curlx/fopen.c rename to lib/curlx/win32-fopen.c index 09264aeb101a..9c683fc64888 100644 --- a/lib/curlx/fopen.c +++ b/lib/curlx/win32-fopen.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "curl_setup.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" int curlx_fseek(void *stream, curl_off_t offset, int whence) { @@ -40,6 +40,7 @@ int curlx_fseek(void *stream, curl_off_t offset, int whence) #ifdef _WIN32 +#include /* for _mkdir(), _wmkdir() */ #include /* for _SH_DENYNO */ #include "curlx/multibyte.h" @@ -467,6 +468,37 @@ int curlx_win32_stat(const char *path, curlx_struct_stat *buffer) return res; } +int curlx_win32_mkdir(const char *path) +{ + int res = -1; + TCHAR *fixed = NULL; + const TCHAR *target = NULL; + +#ifdef _UNICODE + wchar_t *path_w = curlx_convert_UTF8_to_wchar(path); + if(path_w) { + if(fix_excessive_path(path_w, &fixed)) + target = fixed; + else + target = path_w; + res = _wmkdir(target); + curlx_free(path_w); + } + else + /* !checksrc! disable ERRNOVAR 1 */ + errno = EINVAL; +#else + if(fix_excessive_path(path, &fixed)) + target = fixed; + else + target = path; + res = _mkdir(target); +#endif + + CURLX_FREE(fixed); + 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. diff --git a/lib/curlx/fopen.h b/lib/curlx/win32-fopen.h similarity index 94% rename from lib/curlx/fopen.h rename to lib/curlx/win32-fopen.h index 469d3c881303..1548f7a18675 100644 --- a/lib/curlx/fopen.h +++ b/lib/curlx/win32-fopen.h @@ -53,6 +53,7 @@ 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); +int curlx_win32_mkdir(const char *path); #define CURLX_FOPEN_LOW curlx_win32_fopen #define CURLX_FREOPEN_LOW curlx_win32_freopen #define CURLX_FDOPEN_LOW _fdopen @@ -60,6 +61,7 @@ int curlx_win32_rename(const char *oldpath, const char *newpath); #define curlx_open curlx_win32_open #define curlx_close _close #define curlx_rename curlx_win32_rename +#define curlx_mkdir(x, y) curlx_win32_mkdir(x) #else #define curlx_fstat fstat #define curlx_struct_stat struct stat @@ -70,6 +72,11 @@ int curlx_win32_rename(const char *oldpath, const char *newpath); #define curlx_open open #define curlx_close close #define curlx_rename rename +#if defined(MSDOS) && !defined(__DJGPP__) +#define curlx_mkdir(x, y) mkdir(x) +#else +#define curlx_mkdir mkdir +#endif #endif #ifdef CURL_MEMDEBUG diff --git a/lib/cw-out.c b/lib/cw-out.c index 0137975b1eba..9e85faccece4 100644 --- a/lib/cw-out.c +++ b/lib/cw-out.c @@ -97,6 +97,8 @@ static void cw_out_buf_free(struct cw_out_buf *cwbuf) struct cw_out_ctx { struct Curl_cwriter super; struct cw_out_buf *buf; + struct cw_out_buf *tail; + size_t buffered_len; BIT(errored); }; @@ -106,6 +108,8 @@ static CURLcode cw_out_init(struct Curl_easy *data, struct cw_out_ctx *ctx = writer->ctx; (void)data; ctx->buf = NULL; + ctx->tail = NULL; + ctx->buffered_len = 0; return CURLE_OK; } @@ -116,17 +120,8 @@ static void cw_out_bufs_free(struct cw_out_ctx *ctx) cw_out_buf_free(ctx->buf); ctx->buf = next; } -} - -static size_t cw_out_bufs_len(struct cw_out_ctx *ctx) -{ - struct cw_out_buf *cwbuf = ctx->buf; - size_t len = 0; - while(cwbuf) { - len += curlx_dyn_len(&cwbuf->b); - cwbuf = cwbuf->next; - } - return len; + ctx->tail = NULL; + ctx->buffered_len = 0; } static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer) @@ -147,8 +142,14 @@ static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype, switch(otype) { case CW_OUT_BODY: case CW_OUT_BODY_0LEN: - *pwcb = data->set.fwrite_func; - *pwcb_data = data->set.out; + if(Curl_ftp_listmode(data)) { + *pwcb = Curl_ftp_parselist; + *pwcb_data = data; + } + else { + *pwcb = data->set.fwrite_func; + *pwcb_data = data->set.out; + } *pmax_write = CURL_MAX_WRITE_SIZE; /* if we ever want buffering of BODY output, we can set `min_write` * the preferred size. The default should always be to pass data @@ -266,6 +267,12 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, *pconsumed += nwritten; blen -= nwritten; buf += nwritten; + /* write functions/data may have been re-setopt'ed. sigh. */ + cw_get_writefunc(data, otype, &wcb, &wcb_data, &max_write, &min_write); + if(!wcb) { + *pconsumed += blen; + break; + } } } return CURLE_OK; @@ -307,38 +314,34 @@ static CURLcode cw_out_buf_flush(struct cw_out_ctx *ctx, static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, struct Curl_easy *data, - struct cw_out_buf **pcwbuf, bool flush_all) { - struct cw_out_buf *cwbuf = *pcwbuf; CURLcode result; - if(!cwbuf) - return CURLE_OK; - if(data->req.writer.paused) - return CURLE_OK; + /* write the chain oldest first until it blocks or gets empty */ + while(ctx->buf) { + struct cw_out_buf *cwbuf = ctx->buf; + size_t blen_before; - /* write the end of the chain until it blocks or gets empty */ - while(cwbuf->next) { - struct cw_out_buf **plast = &cwbuf->next; - while((*plast)->next) - plast = &(*plast)->next; - result = cw_out_flush_chain(ctx, data, plast, flush_all); + if(data->req.writer.paused) + return CURLE_OK; + + blen_before = curlx_dyn_len(&cwbuf->b); + result = cw_out_buf_flush(ctx, data, cwbuf, flush_all); if(result) return result; - if(*plast) { - /* could not write last, paused again? */ + ctx->buffered_len -= blen_before - curlx_dyn_len(&cwbuf->b); + + if(curlx_dyn_len(&cwbuf->b)) { + /* could not write it all, paused again? */ DEBUGASSERT(data->req.writer.paused); return CURLE_OK; } - } - result = cw_out_buf_flush(ctx, data, cwbuf, flush_all); - if(result) - return result; - if(!curlx_dyn_len(&cwbuf->b)) { + ctx->buf = cwbuf->next; + if(!ctx->buf) + ctx->tail = NULL; cw_out_buf_free(cwbuf); - *pcwbuf = NULL; } return CURLE_OK; } @@ -348,25 +351,33 @@ static CURLcode cw_out_append(struct cw_out_ctx *ctx, cw_out_type otype, const char *buf, size_t blen) { + CURLcode result; + CURL_TRC_WRITE(data, "[OUT] paused, buffering %zu more bytes (%zu/%d)", - blen, cw_out_bufs_len(ctx), DYN_PAUSE_BUFFER); - if(cw_out_bufs_len(ctx) + blen > DYN_PAUSE_BUFFER) { + blen, ctx->buffered_len, DYN_PAUSE_BUFFER); + if(ctx->buffered_len + blen > DYN_PAUSE_BUFFER) { failf(data, "pause buffer not large enough -> CURLE_TOO_LARGE"); return CURLE_TOO_LARGE; } - /* if we do not have a buffer, or it is of another type, make a new one. - * 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)) { + /* if we do not have a buffer, or it is of another type, make a new one + * and append it at the tail. For CW_OUT_HDS always make a new one, so + * we "replay" headers exactly as they came in */ + if(!ctx->tail || (ctx->tail->type != otype) || (otype == CW_OUT_HDS)) { struct cw_out_buf *cwbuf = cw_out_buf_create(otype); if(!cwbuf) return CURLE_OUT_OF_MEMORY; - cwbuf->next = ctx->buf; - ctx->buf = cwbuf; + if(ctx->tail) + ctx->tail->next = cwbuf; + else + ctx->buf = cwbuf; + ctx->tail = cwbuf; } - DEBUGASSERT(ctx->buf && (ctx->buf->type == otype)); - return curlx_dyn_addn(&ctx->buf->b, buf, blen); + DEBUGASSERT(ctx->tail && (ctx->tail->type == otype)); + result = curlx_dyn_addn(&ctx->tail->b, buf, blen); + if(!result) + ctx->buffered_len += blen; + return result; } static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, @@ -377,10 +388,10 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, { CURLcode result = CURLE_OK; - /* if we have buffered data and it is a different type than what - * we are writing now, try to flush all */ - if(ctx->buf && ctx->buf->type != otype) { - result = cw_out_flush_chain(ctx, data, &ctx->buf, TRUE); + /* if we have buffered data and the last chunk buffered is of a + * different type than what we are writing now, try to flush all */ + if(ctx->tail && ctx->tail->type != otype) { + result = cw_out_flush_chain(ctx, data, TRUE); if(result) goto out; } @@ -390,7 +401,7 @@ static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, result = cw_out_append(ctx, data, otype, buf, blen); if(result) goto out; - result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + result = cw_out_flush_chain(ctx, data, flush_all); if(result) goto out; } @@ -460,7 +471,7 @@ static CURLcode cw_out_do_flush(struct Curl_easy *data, CURLcode result; CURL_TRC_WRITE(data, "[OUT] flush"); - result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + result = cw_out_flush_chain(ctx, data, flush_all); if(result) { ctx->errored = TRUE; cw_out_bufs_free(ctx); diff --git a/lib/cw-pause.c b/lib/cw-pause.c index a5b9ba148e08..22c0f6e577fc 100644 --- a/lib/cw-pause.c +++ b/lib/cw-pause.c @@ -67,6 +67,7 @@ static void cw_pause_buf_free(struct cw_pause_buf *cwbuf) struct cw_pause_ctx { struct Curl_cwriter super; struct cw_pause_buf *buf; + struct cw_pause_buf *tail; size_t buf_total; }; @@ -76,6 +77,7 @@ static CURLcode cw_pause_init(struct Curl_easy *data, struct cw_pause_ctx *ctx = writer->ctx; (void)data; ctx->buf = NULL; + ctx->tail = NULL; return CURLE_OK; } @@ -86,6 +88,7 @@ static void cw_pause_bufs_free(struct cw_pause_ctx *ctx) cw_pause_buf_free(ctx->buf); ctx->buf = next; } + ctx->tail = NULL; } static void cw_pause_close(struct Curl_easy *data, struct Curl_cwriter *writer) @@ -102,39 +105,39 @@ static CURLcode cw_pause_flush(struct Curl_easy *data, struct cw_pause_ctx *ctx = (struct cw_pause_ctx *)cw_pause; CURLcode result = CURLE_OK; - /* write the end of the chain until it blocks or gets empty */ + /* write the front of the chain until it blocks or gets empty */ while(ctx->buf && !Curl_cwriter_is_paused(data)) { - struct cw_pause_buf **plast = &ctx->buf; + struct cw_pause_buf *first = ctx->buf; size_t blen, wlen = 0; const unsigned char *buf = NULL; - while((*plast)->next) /* got to last in list */ - plast = &(*plast)->next; - if(Curl_bufq_peek(&(*plast)->b, &buf, &blen)) { - wlen = ((*plast)->type & CLIENTWRITE_BODY) ? + if(Curl_bufq_peek(&first->b, &buf, &blen)) { + wlen = (first->type & CLIENTWRITE_BODY) ? CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen; - result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type, + result = Curl_cwriter_write(data, cw_pause->next, first->type, (const char *)buf, wlen); CURL_TRC_WRITE(data, "[PAUSE] flushed %zu/%zu bytes, type=%x -> %d", - wlen, ctx->buf_total, (unsigned int)(*plast)->type, + wlen, ctx->buf_total, (unsigned int)first->type, (int)result); - Curl_bufq_skip(&(*plast)->b, wlen); + Curl_bufq_skip(&first->b, wlen); DEBUGASSERT(ctx->buf_total >= wlen); ctx->buf_total -= wlen; if(result) return result; } - else if((*plast)->type & CLIENTWRITE_EOS) { - result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type, + else if(first->type & CLIENTWRITE_EOS) { + result = Curl_cwriter_write(data, cw_pause->next, first->type, (const char *)buf, 0); CURL_TRC_WRITE(data, "[PAUSE] flushed 0/%zu bytes, type=%x -> %d", - ctx->buf_total, (unsigned int)(*plast)->type, + ctx->buf_total, (unsigned int)first->type, (int)result); } - if(Curl_bufq_is_empty(&(*plast)->b)) { - cw_pause_buf_free(*plast); - *plast = NULL; + if(Curl_bufq_is_empty(&first->b)) { + ctx->buf = first->next; + if(!ctx->buf) + ctx->tail = NULL; + cw_pause_buf_free(first); } } @@ -177,19 +180,22 @@ static CURLcode cw_pause_write(struct Curl_easy *data, do { size_t nwritten = 0; - if(ctx->buf && (ctx->buf->type == type) && (type & CLIENTWRITE_BODY)) { - /* same type and body, append to current buffer which has a soft + if(ctx->tail && (ctx->tail->type == type) && (type & CLIENTWRITE_BODY)) { + /* same type and body, append to tail buffer which has a soft * limit and should take everything up to OOM. */ - result = Curl_bufq_cwrite(&ctx->buf->b, buf, blen, &nwritten); + result = Curl_bufq_cwrite(&ctx->tail->b, buf, blen, &nwritten); } else { /* Need a new buf, type changed */ struct cw_pause_buf *cwbuf = cw_pause_buf_create(type, blen); if(!cwbuf) return CURLE_OUT_OF_MEMORY; - cwbuf->next = ctx->buf; - ctx->buf = cwbuf; - result = Curl_bufq_cwrite(&ctx->buf->b, buf, blen, &nwritten); + if(ctx->tail) + ctx->tail->next = cwbuf; + else + ctx->buf = cwbuf; + ctx->tail = cwbuf; + result = Curl_bufq_cwrite(&ctx->tail->b, buf, blen, &nwritten); } CURL_TRC_WRITE(data, "[PAUSE] buffer %zu more bytes of type %x, " "total=%zu -> %d", nwritten, (unsigned int)type, diff --git a/lib/dllmain.c b/lib/dllmain.c index 5aa6565010d8..cee1801b4f9a 100644 --- a/lib/dllmain.c +++ b/lib/dllmain.c @@ -33,7 +33,7 @@ #if defined(USE_OPENSSL) && \ !defined(OPENSSL_IS_AWSLC) && !defined(OPENSSL_IS_BORINGSSL) && \ !defined(LIBRESSL_VERSION_NUMBER) -#define PREVENT_OPENSSL_MEMLEAK +#define PREVENT_OPENSSL_MEMLEAK /* non-fork OpenSSL */ #endif #ifdef PREVENT_OPENSSL_MEMLEAK @@ -59,6 +59,6 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) } return TRUE; } -#endif /* USE_OPENSSL (non-fork) */ +#endif /* PREVENT_OPENSSL_MEMLEAK */ #endif /* _WIN32 && !CURL_STATICLIB */ diff --git a/lib/dynhds.c b/lib/dynhds.c index 097306cec98f..c3aad4b503f8 100644 --- a/lib/dynhds.c +++ b/lib/dynhds.c @@ -33,8 +33,9 @@ static struct dynhds_entry *entry_new(const char *name, size_t namelen, struct dynhds_entry *e; char *p; - DEBUGASSERT(name); - DEBUGASSERT(value); + /* namelen + valuelen <= SIZE_MAX has already been checked by caller */ + if((SIZE_MAX - namelen - valuelen) < (sizeof(*e) + 2)) + return NULL; e = curlx_calloc(1, sizeof(*e) + namelen + valuelen + 2); if(!e) return NULL; @@ -42,8 +43,10 @@ static struct dynhds_entry *entry_new(const char *name, size_t namelen, memcpy(p, name, namelen); e->namelen = namelen; e->value = p += namelen + 1; /* leave a \0 at the end of name */ - memcpy(p, value, valuelen); - e->valuelen = valuelen; + if(valuelen) { + memcpy(p, value, valuelen); + e->valuelen = valuelen; + } if(opts & DYNHDS_OPT_LOWERCASE) Curl_strntolower(e->name, e->name, e->namelen); return e; @@ -135,10 +138,15 @@ CURLcode Curl_dynhds_add(struct dynhds *dynhds, struct dynhds_entry *entry = NULL; CURLcode result = CURLE_OUT_OF_MEMORY; - DEBUGASSERT(dynhds); - if(dynhds->max_entries && dynhds->hds_len >= dynhds->max_entries) - return CURLE_OUT_OF_MEMORY; - if(dynhds->strs_len + namelen + valuelen > dynhds->max_strs_size) + /* Accept NULL value with length 0, name must have length */ + if(!dynhds || !name || !namelen || (!value && valuelen)) { + DEBUGASSERT(0); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + /* Does the new header fit into the limits? */ + if((dynhds->max_entries && (dynhds->hds_len >= dynhds->max_entries)) || + (dynhds->max_strs_size - dynhds->strs_len < namelen) || + (dynhds->max_strs_size - dynhds->strs_len - namelen < valuelen)) return CURLE_OUT_OF_MEMORY; entry = entry_new(name, namelen, value, valuelen, dynhds->opts); @@ -146,7 +154,7 @@ CURLcode Curl_dynhds_add(struct dynhds *dynhds, goto out; if(dynhds->hds_len + 1 >= dynhds->hds_allc) { - size_t nallc = dynhds->hds_len + 16; + size_t nallc = dynhds->hds_allc ? dynhds->hds_allc * 2 : 16; struct dynhds_entry **nhds; if(dynhds->max_entries && nallc > dynhds->max_entries) @@ -177,7 +185,10 @@ CURLcode Curl_dynhds_add(struct dynhds *dynhds, CURLcode Curl_dynhds_cadd(struct dynhds *dynhds, const char *name, const char *value) { - return Curl_dynhds_add(dynhds, name, strlen(name), value, strlen(value)); + if(!name) + return CURLE_BAD_FUNCTION_ARGUMENT; + return Curl_dynhds_add(dynhds, name, strlen(name), + value, value ? strlen(value) : 0); } CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, @@ -193,7 +204,7 @@ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds, return CURLE_OK; p = memchr(line, ':', line_len); - if(!p) + if(!p || p == line) return CURLE_BAD_FUNCTION_ARGUMENT; name = line; namelen = p - line; @@ -347,10 +358,15 @@ CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf) return result; for(i = 0; i < dynhds->hds_len; ++i) { - result = curlx_dyn_addf(dbuf, "%.*s: %.*s\r\n", - (int)dynhds->hds[i]->namelen, dynhds->hds[i]->name, - (int)dynhds->hds[i]->valuelen, - dynhds->hds[i]->value); + result = curlx_dyn_addn(dbuf, dynhds->hds[i]->name, + dynhds->hds[i]->namelen); + if(!result) + result = curlx_dyn_addn(dbuf, STRCONST(": ")); + if(!result) + result = curlx_dyn_addn(dbuf, dynhds->hds[i]->value, + dynhds->hds[i]->valuelen); + if(!result) + result = curlx_dyn_addn(dbuf, STRCONST("\r\n")); if(result) break; } diff --git a/lib/easy.c b/lib/easy.c index 04c5a003a048..9d467f4c4be3 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -70,18 +70,20 @@ #include "vssh/ssh.h" #include "setopt.h" #include "http_digest.h" -#include "system_win32.h" #include "curlx/dynbuf.h" #include "bufref.h" #include "altsvc.h" #include "hsts.h" +#include "curl_sspi.h" /* Curl_sspi_global_init()/Curl_sspi_global_cleanup() */ +#include "curlx/timeval.h" /* for curlx_now_init() */ +#include "curlx/version_win32.h" /* for curlx_verify_windows_init() */ #include "easy_lock.h" /* true globals -- for curl_global_init() and curl_global_cleanup() */ static unsigned int initialized; -#ifdef _WIN32 -static long easy_init_flags; +#ifdef USE_WINSOCK +static bool winsock_initialized; #endif #ifdef GLOBAL_INIT_IS_THREADSAFE @@ -126,6 +128,8 @@ static char *leakpointer; */ static CURLcode global_init(long flags, bool memoryfuncs) { + (void)flags; + if(initialized++) return CURLE_OK; @@ -138,21 +142,46 @@ static CURLcode global_init(long flags, bool memoryfuncs) Curl_ccalloc = (curl_calloc_callback)calloc; } - if(Curl_win32_init(flags)) { - DEBUGF(curl_mfprintf(stderr, "Error: win32_init failed\n")); - goto fail; - } +#ifdef _WIN32 + curlx_verify_windows_init(); + curlx_now_init(); +#endif if(Curl_trc_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_trc_init failed\n")); goto fail; } +#ifdef USE_WINDOWS_SSPI + if(Curl_sspi_global_init()) { + DEBUGF(curl_mfprintf(stderr, "Error: Curl_sspi_global_init() failed\n")); + goto fail; + } +#endif + +#ifdef USE_WINSOCK + /* CURL_GLOBAL_WINSOCK controls the *optional* part of the Windows + initialization which is for the Windows sockets library (Winsock). */ + if(flags & CURL_GLOBAL_WINSOCK) { + WSADATA wsa; + if(WSAStartup(MAKEWORD(2, 2), &wsa)) { + DEBUGF(curl_mfprintf(stderr, "Error: WSAStartup() failed\n")); + goto fail; + } + winsock_initialized = TRUE; + } +#endif + if(!Curl_ssl_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_ssl_init failed\n")); goto fail; } + if(Curl_hash_global_init()) { + DEBUGF(curl_mfprintf(stderr, "Error: Curl_hash_global_init failed\n")); + goto fail; + } + if(!Curl_vquic_init()) { DEBUGF(curl_mfprintf(stderr, "Error: Curl_vquic_init failed\n")); goto fail; @@ -178,12 +207,6 @@ static CURLcode global_init(long flags, bool memoryfuncs) goto fail; } -#ifdef _WIN32 - easy_init_flags = flags; -#else - (void)flags; -#endif - #ifdef DEBUGBUILD if(getenv("CURL_GLOBAL_INIT")) /* alloc data that will leak if *cleanup() is not called! */ @@ -193,6 +216,12 @@ static CURLcode global_init(long flags, bool memoryfuncs) return CURLE_OK; fail: +#ifdef USE_WINSOCK + if(winsock_initialized) { + WSACleanup(); + winsock_initialized = FALSE; + } +#endif initialized--; /* undo the increase */ return CURLE_FAILED_INIT; } @@ -255,9 +284,7 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, } /** - * curl_global_cleanup() globally cleanups curl, uses the value of - * "easy_init_flags" to determine what needs to be cleaned up and what does - * not. + * curl_global_cleanup() globally cleanups curl. */ void curl_global_cleanup(void) { @@ -273,18 +300,22 @@ void curl_global_cleanup(void) return; } + Curl_ssh_cleanup(); Curl_ssl_cleanup(); Curl_vquic_cleanup(); Curl_async_global_cleanup(); + Curl_amiga_cleanup(); -#ifdef _WIN32 - Curl_win32_cleanup(easy_init_flags); - easy_init_flags = 0; +#ifdef USE_WINSOCK + if(winsock_initialized) { + WSACleanup(); + winsock_initialized = FALSE; + } #endif - Curl_amiga_cleanup(); - - Curl_ssh_cleanup(); +#ifdef USE_WINDOWS_SSPI + Curl_sspi_global_cleanup(); +#endif #ifdef DEBUGBUILD curlx_free(leakpointer); @@ -524,48 +555,37 @@ static void events_setup(struct Curl_multi *multi, struct events *ev) curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, ev); } -/* populate_fds() - * - * populate the fds[] array - */ -static unsigned int populate_fds(struct pollfd *fds, struct events *ev) +static CURLcode populate_cpfds(struct curl_pollfds *cpfds, struct events *ev) { - unsigned int numfds = 0; - struct pollfd *f; struct socketmonitor *m; + CURLcode result = CURLE_OK; - f = &fds[0]; - for(m = ev->list; m; m = m->next) { - f->fd = m->socket.fd; - f->events = m->socket.events; - f->revents = 0; + Curl_pollfds_reset(cpfds); + for(m = ev->list; m && !result; m = m->next) { + result = Curl_pollfds_add_sock(cpfds, m->socket.fd, m->socket.events); #if DEBUG_EV_POLL - curl_mfprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); + if(!result) + curl_mfprintf(stderr, "poll() %d check socket %d\n", + cpfds->n - 1, cpfds->pfds[cpfds->n - 1].fd); #endif - f++; - numfds++; } - return numfds; + return result; } -/* poll_fds() - * - * poll the fds[] array - */ -static CURLcode poll_fds(struct events *ev, - struct pollfd *fds, - const unsigned int numfds, - int *pollrc) +static CURLcode poll_cpfds(struct events *ev, + struct curl_pollfds *cpfds, + int *pollrc) { - if(numfds) { + if(cpfds->n) { /* wait for activity or timeout */ #if DEBUG_EV_POLL - curl_mfprintf(stderr, "poll(numfds=%u, timeout=%ldms)\n", numfds, ev->ms); + curl_mfprintf(stderr, "poll(numfds=%u, timeout=%ldms)\n", + cpfds->n, ev->ms); #endif - *pollrc = Curl_poll(fds, numfds, ev->ms); + *pollrc = Curl_poll(cpfds->pfds, cpfds->n, ev->ms); #if DEBUG_EV_POLL curl_mfprintf(stderr, "poll(numfds=%u, timeout=%ldms) -> %d\n", - numfds, ev->ms, *pollrc); + cpfds->n, ev->ms, *pollrc); #endif if(*pollrc < 0) return CURLE_UNRECOVERABLE_POLL; @@ -587,30 +607,35 @@ static CURLcode poll_fds(struct events *ev, */ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) { + struct pollfd fds_on_stack[8]; + struct curl_pollfds cpfds; bool done = FALSE; CURLMcode mresult = CURLM_OK; CURLcode result = CURLE_OK; + Curl_pollfds_init(&cpfds, fds_on_stack, CURL_ARRAYSIZE(fds_on_stack)); while(!done) { CURLMsg *msg; - struct pollfd fds[4]; int pollrc; struct curltime start; - const unsigned int numfds = populate_fds(fds, ev); + + result = populate_cpfds(&cpfds, ev); + if(result) + goto out; /* get the time stamp to use to figure out how long poll takes */ curlx_pnow(&start); - result = poll_fds(ev, fds, numfds, &pollrc); + result = poll_cpfds(ev, &cpfds, &pollrc); if(result) - return result; + goto out; ev->msbump = FALSE; /* reset here */ if(!pollrc) { /* timeout! */ ev->ms = 0; -#if 0 +#if DEBUG_EV_POLL curl_mfprintf(stderr, "call curl_multi_socket_action(TIMEOUT)\n"); #endif mresult = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, @@ -620,15 +645,15 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) /* here pollrc is > 0 */ /* loop over the monitored sockets to see which ones had activity */ unsigned int i; - for(i = 0; i < numfds; i++) { - if(fds[i].revents) { + for(i = 0; i < cpfds.n; i++) { + if(cpfds.pfds[i].revents) { /* socket activity, tell libcurl */ - int act = poll2cselect(fds[i].revents); /* convert */ + int act = poll2cselect(cpfds.pfds[i].revents); /* convert */ /* sending infof "randomly" to the first easy handle */ infof(multi->admin, "call curl_multi_socket_action(socket " - "%" FMT_SOCKET_T ")", (curl_socket_t)fds[i].fd); - mresult = curl_multi_socket_action(multi, fds[i].fd, act, + "%" FMT_SOCKET_T ")", (curl_socket_t)cpfds.pfds[i].fd); + mresult = curl_multi_socket_action(multi, cpfds.pfds[i].fd, act, &ev->running_handles); } } @@ -651,8 +676,10 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) } } - if(mresult) - return CURLE_URL_MALFORMAT; + if(mresult) { + result = CURLE_URL_MALFORMAT; + goto out; + } /* we do not really care about the "msgs_in_queue" value returned in the second argument */ @@ -662,7 +689,8 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) done = TRUE; } } - +out: + Curl_pollfds_cleanup(&cpfds); return result; } @@ -927,10 +955,10 @@ static CURLcode dupset(struct Curl_easy *dst, struct Curl_easy *src) if(src->set.postfieldsize == -1) dst->set.str_copypostfields = curlx_strdup(src->set.str_copypostfields); else - /* postfieldsize is curl_off_t, curlx_memdup() takes a size_t ... */ + /* postfieldsize is curl_off_t, curlx_memdup0() takes a size_t ... */ dst->set.str_copypostfields = - curlx_memdup(src->set.str_copypostfields, - curlx_sotouz(src->set.postfieldsize)); + curlx_memdup0(src->set.str_copypostfields, + curlx_sotouz(src->set.postfieldsize)); if(!dst->set.str_copypostfields) return CURLE_OUT_OF_MEMORY; /* point to the new copy */ @@ -991,8 +1019,7 @@ CURL *curl_easy_duphandle(CURL *curl) */ outcurl->set.buffer_size = data->set.buffer_size; - Curl_hash_init(&outcurl->meta_hash, 23, - Curl_hash_str, curlx_str_key_compare, + Curl_hash_init(&outcurl->meta_hash, 23, CURL_HASH_TYPE_BYTES, dupeasy_meta_freeentry); curlx_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); Curl_bufref_init(&outcurl->state.url); @@ -1000,7 +1027,8 @@ CURL *curl_easy_duphandle(CURL *curl) Curl_netrc_init(&outcurl->state.netrc); /* the connection pool is setup on demand */ - outcurl->state.lastconnect_id = -1; + outcurl->state.last_conn_id = -1; + outcurl->state.last_cpid = UINT32_MAX; outcurl->id = -1; outcurl->mid = UINT32_MAX; outcurl->master_mid = UINT32_MAX; @@ -1118,7 +1146,8 @@ void curl_easy_reset(CURL *curl) if(CURL_EAPI_ENTER(&guard, curl, easy_reset, NULL)) { struct Curl_easy *data = curl; - data->state.lastconnect_id = -1; /* clear remembered connection id */ + data->state.last_conn_id = -1; /* clear remembered connection id */ + data->state.last_cpid = UINT32_MAX; Curl_req_hard_reset(&data->req, data); Curl_hash_clean(&data->meta_hash); @@ -1204,17 +1233,15 @@ CURLcode curl_easy_pause(CURL *curl, int action) if(data->multi) { Curl_multi_mark_dirty(data); /* make it run */ /* On changes, tell application to update its timers. */ - if(changed) { - if(Curl_update_timer(data->multi) && !result) - result = CURLE_ABORTED_BY_CALLBACK; - } + if(changed && Curl_update_timer(data->multi) && !result) + result = CURLE_ABORTED_BY_CALLBACK; } } - if(!result && changed && !data->state.done && data->multi) - /* pause/unpausing may result in multi event changes */ - if(Curl_multi_ev_assess_xfer(data->multi, data) && !result) - result = CURLE_ABORTED_BY_CALLBACK; + if(!result && changed && !data->state.done && data->multi && + /* pause/unpausing may result in multi event changes */ + Curl_multi_ev_assess_xfer(data->multi, data)) + result = CURLE_ABORTED_BY_CALLBACK; } out: CURL_EAPI_LEAVE(&guard); @@ -1239,7 +1266,7 @@ static CURLcode easy_connection(struct Curl_easy *data, if(sfd == CURL_SOCKET_BAD) { failf(data, "Failed to get last socket used for connection #%" FMT_OFF_T, - data->state.lastconnect_id); + data->state.last_conn_id); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -1276,8 +1303,13 @@ CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) CURLcode result; if(CURL_EAPI_ENTER(&guard, curl, easy_recv, &result)) { + if(!n || (buflen && !buffer)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } result = Curl_easy_recv(curl, buffer, buflen, n); } +out: CURL_EAPI_LEAVE(&guard); return result; } @@ -1346,9 +1378,14 @@ CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, struct Curl_easy *data = curl; size_t written = 0; + if(!n || (buflen && !buffer)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } result = Curl_senddata(data, buffer, buflen, &written); *n = written; } +out: CURL_EAPI_LEAVE(&guard); return result; } @@ -1378,8 +1415,11 @@ CURLcode curl_easy_ssls_import(CURL *curl, const char *session_key, CURLcode result; if(CURL_EAPI_ENTER(&guard, curl, easy_ssls_import, &result)) { - result = Curl_ssl_session_import((struct Curl_easy *)curl, session_key, - shmac, shmac_len, sdata, sdata_len); + if(!sdata || !sdata_len) + result = CURLE_BAD_FUNCTION_ARGUMENT; + else + result = Curl_ssl_session_import((struct Curl_easy *)curl, session_key, + shmac, shmac_len, sdata, sdata_len); } CURL_EAPI_LEAVE(&guard); return result; @@ -1403,8 +1443,11 @@ CURLcode curl_easy_ssls_export(CURL *curl, CURLcode result; if(CURL_EAPI_ENTER(&guard, curl, easy_ssls_export, &result)) { - result = Curl_ssl_session_export((struct Curl_easy *)curl, - export_fn, userptr); + if(!export_fn) + result = CURLE_BAD_FUNCTION_ARGUMENT; + else + result = Curl_ssl_session_export((struct Curl_easy *)curl, + export_fn, userptr); } CURL_EAPI_LEAVE(&guard); return result; @@ -1420,9 +1463,9 @@ CURLcode Curl_meta_set(struct Curl_easy *data, const char *key, void *meta_data, Curl_meta_dtor *meta_dtor) { DEBUGASSERT(meta_data); /* never set to NULL */ - if(!Curl_hash_add2(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1, + if(!Curl_hash_add2(&data->meta_hash, key, strlen(key) + 1, meta_data, meta_dtor)) { - meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data); + meta_dtor(key, strlen(key) + 1, meta_data); return CURLE_OUT_OF_MEMORY; } return CURLE_OK; @@ -1430,12 +1473,12 @@ CURLcode Curl_meta_set(struct Curl_easy *data, const char *key, void Curl_meta_remove(struct Curl_easy *data, const char *key) { - Curl_hash_delete(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1); + Curl_hash_delete(&data->meta_hash, key, strlen(key) + 1); } void *Curl_meta_get(struct Curl_easy *data, const char *key) { - return Curl_hash_pick(&data->meta_hash, CURL_UNCONST(key), strlen(key) + 1); + return Curl_hash_pick(&data->meta_hash, key, strlen(key) + 1); } void Curl_meta_reset(struct Curl_easy *data) diff --git a/lib/escape.c b/lib/escape.c index a1a714f8f592..32f94cbb8e2f 100644 --- a/lib/escape.c +++ b/lib/escape.c @@ -44,13 +44,122 @@ char *curl_unescape(const char *string, int length) return curl_easy_unescape(NULL, string, length, NULL); } +#define NOPE 0xff +#define _OK_ 0x1f /* octet not needing encoding but not a hexadecimal + character */ + +static const unsigned char hextable[256] = { + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 00 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 08 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 10 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 18 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 20 */ + NOPE, NOPE, NOPE, NOPE, NOPE, _OK_, _OK_, NOPE, /* 28 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30 */ + 8, 9, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 38 */ + NOPE, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, _OK_, /* 40 */ + _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 48 */ + _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 50 */ + _OK_, _OK_, _OK_, NOPE, NOPE, NOPE, NOPE, _OK_, /* 58 */ + NOPE, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, _OK_, /* 60 */ + _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 68 */ + _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 70 */ + _OK_, _OK_, _OK_, NOPE, NOPE, NOPE, _OK_, NOPE, /* 78 */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 80 - not ASCII */ + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, + NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE +}; + +static const char hexpairs[][2] = { + {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, + {'0', '4'}, {'0', '5'}, {'0', '6'}, {'0', '7'}, + {'0', '8'}, {'0', '9'}, {'0', 'A'}, {'0', 'B'}, + {'0', 'C'}, {'0', 'D'}, {'0', 'E'}, {'0', 'F'}, + {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, + {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, + {'1', '8'}, {'1', '9'}, {'1', 'A'}, {'1', 'B'}, + {'1', 'C'}, {'1', 'D'}, {'1', 'E'}, {'1', 'F'}, + {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, + {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, + {'2', '8'}, {'2', '9'}, {'2', 'A'}, {'2', 'B'}, + {'2', 'C'}, {'2', 'D'}, {'2', 'E'}, {'2', 'F'}, + {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, + {'3', '4'}, {'3', '5'}, {'3', '6'}, {'3', '7'}, + {'3', '8'}, {'3', '9'}, {'3', 'A'}, {'3', 'B'}, + {'3', 'C'}, {'3', 'D'}, {'3', 'E'}, {'3', 'F'}, + {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, + {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, + {'4', '8'}, {'4', '9'}, {'4', 'A'}, {'4', 'B'}, + {'4', 'C'}, {'4', 'D'}, {'4', 'E'}, {'4', 'F'}, + {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, + {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, + {'5', '8'}, {'5', '9'}, {'5', 'A'}, {'5', 'B'}, + {'5', 'C'}, {'5', 'D'}, {'5', 'E'}, {'5', 'F'}, + {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, + {'6', '4'}, {'6', '5'}, {'6', '6'}, {'6', '7'}, + {'6', '8'}, {'6', '9'}, {'6', 'A'}, {'6', 'B'}, + {'6', 'C'}, {'6', 'D'}, {'6', 'E'}, {'6', 'F'}, + {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, + {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, + {'7', '8'}, {'7', '9'}, {'7', 'A'}, {'7', 'B'}, + {'7', 'C'}, {'7', 'D'}, {'7', 'E'}, {'7', 'F'}, + {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, + {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, + {'8', '8'}, {'8', '9'}, {'8', 'A'}, {'8', 'B'}, + {'8', 'C'}, {'8', 'D'}, {'8', 'E'}, {'8', 'F'}, + {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, + {'9', '4'}, {'9', '5'}, {'9', '6'}, {'9', '7'}, + {'9', '8'}, {'9', '9'}, {'9', 'A'}, {'9', 'B'}, + {'9', 'C'}, {'9', 'D'}, {'9', 'E'}, {'9', 'F'}, + {'A', '0'}, {'A', '1'}, {'A', '2'}, {'A', '3'}, + {'A', '4'}, {'A', '5'}, {'A', '6'}, {'A', '7'}, + {'A', '8'}, {'A', '9'}, {'A', 'A'}, {'A', 'B'}, + {'A', 'C'}, {'A', 'D'}, {'A', 'E'}, {'A', 'F'}, + {'B', '0'}, {'B', '1'}, {'B', '2'}, {'B', '3'}, + {'B', '4'}, {'B', '5'}, {'B', '6'}, {'B', '7'}, + {'B', '8'}, {'B', '9'}, {'B', 'A'}, {'B', 'B'}, + {'B', 'C'}, {'B', 'D'}, {'B', 'E'}, {'B', 'F'}, + {'C', '0'}, {'C', '1'}, {'C', '2'}, {'C', '3'}, + {'C', '4'}, {'C', '5'}, {'C', '6'}, {'C', '7'}, + {'C', '8'}, {'C', '9'}, {'C', 'A'}, {'C', 'B'}, + {'C', 'C'}, {'C', 'D'}, {'C', 'E'}, {'C', 'F'}, + {'D', '0'}, {'D', '1'}, {'D', '2'}, {'D', '3'}, + {'D', '4'}, {'D', '5'}, {'D', '6'}, {'D', '7'}, + {'D', '8'}, {'D', '9'}, {'D', 'A'}, {'D', 'B'}, + {'D', 'C'}, {'D', 'D'}, {'D', 'E'}, {'D', 'F'}, + {'E', '0'}, {'E', '1'}, {'E', '2'}, {'E', '3'}, + {'E', '4'}, {'E', '5'}, {'E', '6'}, {'E', '7'}, + {'E', '8'}, {'E', '9'}, {'E', 'A'}, {'E', 'B'}, + {'E', 'C'}, {'E', 'D'}, {'E', 'E'}, {'E', 'F'}, + {'F', '0'}, {'F', '1'}, {'F', '2'}, {'F', '3'}, + {'F', '4'}, {'F', '5'}, {'F', '6'}, {'F', '7'}, + {'F', '8'}, {'F', '9'}, {'F', 'A'}, {'F', 'B'}, + {'F', 'C'}, {'F', 'D'}, {'F', 'E'}, {'F', 'F'} +}; + /* Escapes for URL the given unescaped string of given length. * 'data' is ignored since 7.82.0. */ char *curl_easy_escape(CURL *curl, const char *string, int length) { size_t len; - struct dynbuf d; + const char *f = string; + size_t nappend = 0; + char *target; + char *encoded; (void)curl; if(!string || (length < 0)) @@ -63,27 +172,36 @@ char *curl_easy_escape(CURL *curl, const char *string, int length) if(len > SIZE_MAX / 16) return NULL; - curlx_dyn_init(&d, (len * 3) + 1); + encoded = target = curlx_malloc((len * 3) + 1); + if(!target) + return NULL; while(len--) { - /* treat the characters unsigned */ - unsigned char in = (unsigned char)*string++; + uint8_t in = (uint8_t)*string++; - if(ISUNRESERVED(in)) { + if(!(hextable[in] & 0x80)) /* append this */ - if(curlx_dyn_addn(&d, &in, 1)) - return NULL; - } + nappend++; else { + if(nappend) { + memcpy(target, f, nappend); + target += nappend; + nappend = 0; + } + f = string; /* encode it */ - unsigned char out[3] = { '%' }; - Curl_hexbyte(&out[1], in); - if(curlx_dyn_addn(&d, out, 3)) - return NULL; + target[0] = '%'; + memcpy(target + 1, hexpairs[in], 2); + target += 3; } } + if(nappend) { + memcpy(target, f, nappend); + target += nappend; + } + *target = '\0'; /* null-terminate */ - return curlx_dyn_ptr(&d); + return encoded; } /* @@ -108,6 +226,7 @@ CURLcode Curl_urldecode(const char *string, size_t length, { size_t alloc; char *ns; + uint8_t reject_limit; DEBUGASSERT(string); DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */ @@ -121,36 +240,66 @@ CURLcode Curl_urldecode(const char *string, size_t length, /* store output string */ *ostring = ns; + reject_limit = (ctrl == REJECT_CTRL) ? 0x20 : + (ctrl == REJECT_ZERO) ? 1 : 0; + while(alloc) { - unsigned char in = (unsigned char)*string; - if(('%' == in) && (alloc > 2) && - ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { - /* this is two hexadecimal digits following a '%' */ - in = (unsigned char)((curlx_hexval(string[1]) << 4) | - curlx_hexval(string[2])); - string += 3; - alloc -= 3; - } - else { + uint8_t in; + + if(*string == '%') { + if(alloc > 2) { + uint8_t h1 = hextable[(uint8_t)string[1]]; + uint8_t h2 = hextable[(uint8_t)string[2]]; + if(!((h1 | h2) & 0xf0)) { + in = (uint8_t)((h1 << 4) | h2); + string += 3; + alloc -= 3; + if(in < reject_limit) + goto error; + *ns++ = (char)in; + continue; + } + } + in = '%'; string++; alloc--; + *ns++ = (char)in; + continue; } + else { + const char *p = memchr(string + 1, '%', alloc - 1); + size_t n = p ? (size_t)(p - string) : alloc; - if(((ctrl == REJECT_CTRL) && (in < 0x20)) || - ((ctrl == REJECT_ZERO) && (in == 0))) { - curlx_safefree(*ostring); - return CURLE_URL_MALFORMAT; - } + if(reject_limit) { + if(reject_limit == 1) { + if(memchr(string, 0, n)) + goto error; + } + else { + size_t i; + for(i = 0; i < n; i++) { + if((uint8_t)string[i] < 0x20) + goto error; + } + } + } - *ns++ = (char)in; + memcpy(ns, string, n); + ns += n; + string += n; + alloc -= n; + } } *ns = 0; /* terminate it */ if(olen) /* store output size */ - *olen = ns - *ostring; + *olen = (size_t)(ns - *ostring); return CURLE_OK; +error: + curlx_safefree(*ostring); + return CURLE_URL_MALFORMAT; } /* diff --git a/lib/file.c b/lib/file.c index d3553b7df2d1..e4ff57378225 100644 --- a/lib/file.c +++ b/lib/file.c @@ -59,7 +59,7 @@ #include "transfer.h" #include "url.h" #include "parsedate.h" /* for the week day and month names */ -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curl_range.h" #if defined(_WIN32) || defined(MSDOS) @@ -90,7 +90,7 @@ static void file_cleanup(struct FILEPROTO *file) } } -static void file_easy_dtor(void *key, size_t klen, void *entry) +static void file_easy_dtor(const void *key, size_t klen, void *entry) { struct FILEPROTO *file = entry; (void)key; @@ -331,15 +331,13 @@ static CURLcode file_upload(struct Curl_easy *data, else mode |= O_TRUNC; -#ifdef _WIN32 fd = curlx_open(file->path, mode, - data->set.new_file_perms & (_S_IREAD | _S_IWRITE)); -#elif (defined(ANDROID) || defined(__ANDROID__)) && \ - (defined(__i386__) || defined(__arm__)) - fd = curlx_open(file->path, mode, (mode_t)data->set.new_file_perms); +#ifdef _WIN32 + data->set.new_file_perms & (_S_IREAD | _S_IWRITE) #else - fd = curlx_open(file->path, mode, data->set.new_file_perms); + (mode_t)data->set.new_file_perms #endif + ); if(fd < 0) { failf(data, "cannot open %s for writing", file->path); return CURLE_WRITE_ERROR; diff --git a/lib/formdata.c b/lib/formdata.c index 0c81d8c781c9..825333e60af3 100644 --- a/lib/formdata.c +++ b/lib/formdata.c @@ -33,7 +33,7 @@ struct Curl_easy; #include "mime.h" #include "curlx/strdup.h" #include "bufref.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #define HTTPPOST_PTRNAME CURL_HTTPPOST_PTRNAME @@ -256,22 +256,18 @@ static CURLFORMcode formadd_check(struct FormInfo *first_form, if(Curl_bufref_memdup0(&form->contenttype, type, strlen(type))) return CURL_FORMADD_MEMORY; } - if(name && form->namelength) { - if(memchr(name, 0, form->namelength)) - return CURL_FORMADD_NULL; - } - if(!(form->flags & HTTPPOST_PTRNAME)) { - /* Note that there is small risk that form->name is NULL here if the app - passed in a bad combo, so we check for that. */ - if(forminfo_copyfield(&form->name, form->namelength)) - return CURL_FORMADD_MEMORY; - } + if(name && form->namelength && memchr(name, 0, form->namelength)) + return CURL_FORMADD_NULL; + if(!(form->flags & HTTPPOST_PTRNAME) && + /* Note that there is small risk that form->name is NULL here if the app + passed in a bad combo, so we check for that. */ + forminfo_copyfield(&form->name, form->namelength)) + return CURL_FORMADD_MEMORY; if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | - HTTPPOST_CALLBACK))) { - if(forminfo_copyfield(&form->value, (size_t)form->contentslength)) - return CURL_FORMADD_MEMORY; - } + HTTPPOST_CALLBACK)) && + forminfo_copyfield(&form->value, (size_t)form->contentslength)) + return CURL_FORMADD_MEMORY; post = httppost_add(form, post, httppost, last_post); if(!post) @@ -404,14 +400,25 @@ static CURLFORMcode formadd(struct curl_httppost **httppost, retval = CURL_FORMADD_NULL; } break; - case CURLFORM_CONTENTSLENGTH: - curr->contentslength = (curl_off_t)(size_t)form_int_arg(long); + case CURLFORM_CONTENTSLENGTH: { + long clen = form_int_arg(long); + if(clen < 0) + retval = CURL_FORMADD_INCOMPLETE; + else + curr->contentslength = (curl_off_t)clen; break; + } - case CURLFORM_CONTENTLEN: - curr->flags |= CURL_HTTPPOST_LARGE; - curr->contentslength = form_int_arg(curl_off_t); + case CURLFORM_CONTENTLEN: { + curl_off_t clen = form_int_arg(curl_off_t); + if(clen < 0) + retval = CURL_FORMADD_INCOMPLETE; + else { + curr->flags |= CURL_HTTPPOST_LARGE; + curr->contentslength = clen; + } break; + } /* Get contents from a given filename */ case CURLFORM_FILECONTENT: @@ -826,10 +833,10 @@ CURLcode Curl_getformdata(CURL *data, } /* Set fake filename. */ - if(!result && post->showfilename) - if(post->more || (post->flags & (HTTPPOST_FILENAME | HTTPPOST_BUFFER | - HTTPPOST_CALLBACK))) - result = curl_mime_filename(part, post->showfilename); + if(!result && post->showfilename && + (post->more || (post->flags & (HTTPPOST_FILENAME | HTTPPOST_BUFFER | + HTTPPOST_CALLBACK)))) + result = curl_mime_filename(part, post->showfilename); } } diff --git a/lib/ftp-int.h b/lib/ftp-int.h index 8d7e14127488..0bf6b9a24577 100644 --- a/lib/ftp-int.h +++ b/lib/ftp-int.h @@ -79,11 +79,6 @@ 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 diff --git a/lib/ftp.c b/lib/ftp.c index 70bf654bf914..f66d6bd1d351 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -304,7 +304,7 @@ static CURLcode ftp_parse_url_path(struct Curl_easy *data, if(fileName && *fileName) ftpc->file = fileName; else - ftpc->file = NULL; /* instead of point to a zero byte, + ftpc->file = NULL; /* instead of point to a NUL byte, we make it a NULL pointer */ if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) { @@ -2673,13 +2673,12 @@ static CURLcode ftp_state_size_resp(struct Curl_easy *data, if(curlx_str_number(&fdigit, &filesize, CURL_OFF_T_MAX)) filesize = -1; /* size remain unknown */ } - else if(ftpcode == 550) { /* "No such file or directory" */ - /* allow a SIZE failure for (resumed) uploads, when probing what command - to use */ - if(instate != FTP_STOR_SIZE) { - failf(data, "The file does not exist"); - return CURLE_REMOTE_FILE_NOT_FOUND; - } + else if(ftpcode == 550 && /* "No such file or directory" */ + /* allow a SIZE failure for (resumed) uploads, when probing what + command to use */ + instate != FTP_STOR_SIZE) { + failf(data, "The file does not exist"); + return CURLE_REMOTE_FILE_NOT_FOUND; } if(instate == FTP_SIZE) { @@ -3169,6 +3168,15 @@ static CURLcode ftp_wait_resp(struct Curl_easy *data, return result; } +/* an implicit TLS scheme implies TLS on both connections */ +static unsigned char ftp_use_ssl(struct connectdata *conn, + struct ftp_conn *ftpc) +{ + if(!ftpc->use_ssl && (conn->scheme->flags & PROTOPT_SSL)) + return CURLUSESSL_ALL; + return ftpc->use_ssl; +} + static CURLcode ftp_pp_statemachine(struct Curl_easy *data, struct connectdata *conn) { @@ -3255,7 +3263,7 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, case FTP_PBSZ: result = Curl_pp_sendf(data, &ftpc->pp, "PROT %c", - ftpc->use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); + ftp_use_ssl(conn, ftpc) == CURLUSESSL_CONTROL ? 'C' : 'P'); if(!result) ftp_state(data, ftpc, FTP_PROT); break; @@ -3263,10 +3271,11 @@ static CURLcode ftp_pp_statemachine(struct Curl_easy *data, case FTP_PROT: if(ftpcode / 100 == 2) /* We have enabled SSL for the data connection! */ - conn->bits.ftp_use_data_ssl = (ftpc->use_ssl != CURLUSESSL_CONTROL); + conn->bits.ftp_use_data_ssl = + (ftp_use_ssl(conn, ftpc) != CURLUSESSL_CONTROL); /* FTP servers typically responds with 500 if they decide to reject our 'P' request */ - else if(ftpc->use_ssl > CURLUSESSL_CONTROL) + else if(ftp_use_ssl(conn, ftpc) > CURLUSESSL_CONTROL) /* we failed and bails out */ return CURLE_USE_SSL_FAILED; @@ -4017,15 +4026,6 @@ static CURLcode init_wc_data(struct Curl_easy *data, goto fail; } - /* backup old write_function */ - ftpwc->backup.write_function = data->set.fwrite_func; - /* parsing write function */ - data->set.fwrite_func = Curl_ftp_parselist; - /* backup old file descriptor */ - ftpwc->backup.file_descriptor = data->set.out; - /* let the writefunc callback know the transfer */ - data->set.out = data; - infof(data, "Wildcard - Parsing started"); return CURLE_OK; @@ -4061,10 +4061,6 @@ static CURLcode wc_statemach(struct Curl_easy *data, /* In this state is LIST response successfully parsed, so lets restore previous WRITEFUNCTION callback and WRITEDATA pointer */ struct ftp_wc *ftpwc = wildcard->ftpwc; - data->set.fwrite_func = ftpwc->backup.write_function; - data->set.out = ftpwc->backup.file_descriptor; - ftpwc->backup.write_function = ZERO_NULL; - ftpwc->backup.file_descriptor = NULL; wildcard->state = CURLWC_DOWNLOADING; if(Curl_ftp_parselist_geterror(ftpwc->parser)) { @@ -4084,8 +4080,13 @@ static CURLcode wc_statemach(struct Curl_easy *data, /* filelist has at least one file, lets get first one */ struct Curl_llist_node *head = Curl_llist_head(&wildcard->filelist); struct curl_fileinfo *finfo = Curl_node_elem(head); + char *tmp_path; + char *enc = curl_easy_escape(NULL, finfo->filename, 0); + if(!enc) + return CURLE_OUT_OF_MEMORY; - char *tmp_path = curl_maprintf("%s%s", wildcard->path, finfo->filename); + tmp_path = curl_maprintf("%s%s", wildcard->path, enc); + curl_free(enc); if(!tmp_path) return CURLE_OUT_OF_MEMORY; @@ -4365,7 +4366,7 @@ static CURLcode ftp_doing(struct Curl_easy *data, return result; } -static void ftp_easy_dtor(void *key, size_t klen, void *entry) +static void ftp_easy_dtor(const void *key, size_t klen, void *entry) { struct FTP *ftp = entry; (void)key; @@ -4374,7 +4375,7 @@ static void ftp_easy_dtor(void *key, size_t klen, void *entry) curlx_free(ftp); } -static void ftp_conn_dtor(void *key, size_t klen, void *entry) +static void ftp_conn_dtor(const void *key, size_t klen, void *entry) { struct ftp_conn *ftpc = entry; (void)key; @@ -4480,16 +4481,15 @@ bool Curl_ftp_conns_match(struct connectdata *needle, struct connectdata *conn) cftpc->alternative_to_user) || (nftpc->ccc != cftpc->ccc)) return FALSE; - /* A mismatch on `use_ssl` MUST have been found in connection matching - * before we come here. This is a check on MAYBE/MUST use of STARTTLS and - * it only works on FTP. But IMAP/SMTP etc have the same `use_ssl` and - * no extra match like FTP. We lack tests in this area, so let FTP fail - * loudly here to help other cases. */ - if(nftpc->use_ssl > cftpc->use_ssl) { - DEBUGASSERT(0); - return FALSE; + + switch(nftpc->use_ssl) { + case CURLUSESSL_TRY: + /* A transfer that only "tries" SSL, is compatible with a connection + * of similar or stricter SSL use setting. */ + return (cftpc->use_ssl != CURLUSESSL_NONE); + default: + return (nftpc->use_ssl == cftpc->use_ssl); } - return TRUE; } /* diff --git a/lib/ftplistparser.c b/lib/ftplistparser.c index 47e3d75d940a..fa721cc78d38 100644 --- a/lib/ftplistparser.c +++ b/lib/ftplistparser.c @@ -323,7 +323,10 @@ static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, compare = Curl_fnmatch; /* filter pattern-corresponding filenames */ - { + if(strchr(finfo->filename, '/')) + add = FALSE; + else { + /* a real file name cannot contain a slash */ struct Curl_mapi_guard guard; CURL_CBAPI_START(&guard, data, easy_fnmatch_data); if(compare(data->set.fnmatch_data, wc->pattern, finfo->filename) == 0) { @@ -1012,6 +1015,16 @@ static CURLcode parse_winnt(struct Curl_easy *data, return CURLE_OK; } +/* returns TRUE if in wildcard listing mode */ +bool Curl_ftp_listmode(struct Curl_easy *data) +{ + if(data->state.wildcardmatch) { + struct WildcardData * const wildcard = data->wildcard; + return wildcard && (wildcard->state == CURLWC_MATCHING); + } + return FALSE; +} + size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, void *connptr) { diff --git a/lib/ftplistparser.h b/lib/ftplistparser.h index 5d7aa492ba4e..942e1028945d 100644 --- a/lib/ftplistparser.h +++ b/lib/ftplistparser.h @@ -27,6 +27,9 @@ #ifndef CURL_DISABLE_FTP +/* returns TRUE if in wildcard listing mode */ +bool Curl_ftp_listmode(struct Curl_easy *data); + /* WRITEFUNCTION callback for parsing LIST responses */ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, void *connptr); @@ -71,6 +74,8 @@ void Curl_wildcard_dtor(struct WildcardData **wcp); struct Curl_easy; #else /* CURL_DISABLE_FTP */ +#define Curl_ftp_listmode(x) FALSE +#define Curl_ftp_parselist ZERO_NULL #define Curl_wildcard_dtor(x) #endif /* !CURL_DISABLE_FTP */ diff --git a/lib/getenv.c b/lib/getenv.c index a957e6e80199..acf058e49704 100644 --- a/lib/getenv.c +++ b/lib/getenv.c @@ -56,7 +56,8 @@ char *curl_getenv(const char *variable) return NULL; } - /* if rc < bufsize then rc is bytes written not including null */ + /* if rc < bufsize then rc is bytes written not including + the null-terminator */ if(rc < bufsize) return buf; diff --git a/lib/getinfo.c b/lib/getinfo.c index 23f1de270c49..f841ebeaea7a 100644 --- a/lib/getinfo.c +++ b/lib/getinfo.c @@ -63,6 +63,7 @@ void Curl_initinfo(struct Curl_easy *data) curlx_safefree(info->contenttype); curlx_safefree(info->wouldredirect); + curlx_safefree(info->effective_url); memset(&info->primary, 0, sizeof(info->primary)); info->retry_after = 0; @@ -76,15 +77,51 @@ void Curl_initinfo(struct Curl_easy *data) #endif } +static CURLcode effective_url(struct Curl_easy *data, + const char **param_charp) +{ + const char *s = Curl_bufref_ptr(&data->state.url); + *param_charp = s ? s : ""; + if(s) { + char *url = NULL; + /* Make a copy of the URL without credentials */ + CURLU *u = curl_url(); + CURLUcode uc; + if(!u) + return CURLE_OUT_OF_MEMORY; + + uc = curl_url_set(u, CURLUPART_URL, s, + CURLU_NON_SUPPORT_SCHEME | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + if(!uc) + uc = curl_url_set(u, CURLUPART_USER, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); + if(!uc) + uc = curl_url_get(u, CURLUPART_URL, &url, CURLU_GET_EMPTY); + + curl_url_cleanup(u); + + if(uc || !url) + return CURLE_OUT_OF_MEMORY; + if(strcmp(url, s)) { + /* they are different */ + curlx_free(data->info.effective_url); + data->info.effective_url = url; + *param_charp = url; + } + else + curl_free(url); + } + return CURLE_OK; +} + static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, const char **param_charp) { switch(info) { - case CURLINFO_EFFECTIVE_URL: { - const char *s = Curl_bufref_ptr(&data->state.url); - *param_charp = s ? s : ""; - } - break; + case CURLINFO_EFFECTIVE_URL: + return effective_url(data, param_charp); case CURLINFO_EFFECTIVE_METHOD: { const char *m = CURL_EASY_STR(data, STRING_CUSTOMREQUEST); if(!m) { @@ -463,7 +500,7 @@ static CURLcode getinfo_offt(struct Curl_easy *data, CURLINFO info, *param_offt = data->id; break; case CURLINFO_CONN_ID: - *param_offt = data->state.lastconnect_id; + *param_offt = data->state.last_conn_id; break; case CURLINFO_EARLYDATA_SENT_T: *param_offt = data->progress.earlydata_sent; diff --git a/lib/hash.c b/lib/hash.c index 3eae1d26a791..0b9dc2f5f0c8 100644 --- a/lib/hash.c +++ b/lib/hash.c @@ -23,7 +23,10 @@ ***************************************************************************/ #include "curl_setup.h" +#include /* for offsetof() */ + #include "hash.h" +#include "rand.h" /* random patterns for API verification */ #ifdef DEBUGBUILD @@ -31,6 +34,140 @@ #define ITERINIT 0x5FEDCBA9 #endif + +typedef size_t (*hash_function)(const void *key, + size_t key_length, + size_t slots_num); + +typedef size_t (*comp_function)(const void *key1, + size_t key1_len, + const void *key2, + size_t key2_len); + +/* Curl_hash stores its type in a uint8_t. */ +typedef char hash_types_fit_uint8[ + ((CURL_HASH_TYPE_LAST - 1) <= UINT8_MAX) ? 1 : -1]; + +struct hash_functions { + hash_function hash; + comp_function compare; +}; + +/* set at global init, read-only afterwards */ +static size_t hash_seed; + +CURLcode Curl_hash_global_init(void) +{ + static bool seeded; + unsigned char buf[sizeof(size_t)]; + size_t seed = 0; + size_t i; + CURLcode result; + + if(seeded) + return CURLE_OK; + + result = Curl_rand_bytes(NULL, +#ifdef DEBUGBUILD + FALSE, +#endif + buf, sizeof(buf)); + if(result) + return result; + + for(i = 0; i < sizeof(buf); i++) + seed = (seed << 8) | buf[i]; + + hash_seed = seed ? seed : 1; + seeded = TRUE; + return CURLE_OK; +} + +/* @unittest 1603 + */ +UNITTEST size_t hash_str_seeded(const void *key, size_t key_length, + size_t slots_num, size_t seed); +UNITTEST size_t hash_str_seeded(const void *key, size_t key_length, + size_t slots_num, size_t seed) +{ + const char *key_str = (const char *)key; + const char *end = key_str + key_length; + size_t h = 5381 ^ seed; + + while(key_str < end) { + size_t j = (size_t)*key_str++; + h += h << 5; + h ^= j; + } + + return (h % slots_num); +} + +/* @unittest 1603 + */ +UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num); +UNITTEST size_t hash_str(const void *key, size_t key_length, size_t slots_num) +{ + return hash_str_seeded(key, key_length, slots_num, hash_seed); +} + +/* Avoid treating the variable-length key[1] as a one-byte object. */ +static char *hash_elem_key(struct Curl_hash_element *he) +{ + return (char *)he + offsetof(struct Curl_hash_element, key); +} + +static size_t hash_socket(const void *key, size_t key_length, + size_t slots_num) +{ + curl_socket_t socket; + + DEBUGASSERT(key_length == sizeof(socket)); + if(key_length != sizeof(socket)) + return 0; + memcpy(&socket, key, sizeof(socket)); + return (size_t)(socket % (curl_socket_t)slots_num); +} + +static size_t compare_socket(const void *key1, size_t key1_len, + const void *key2, size_t key2_len) +{ + curl_socket_t socket1; + curl_socket_t socket2; + + if((key1_len != sizeof(socket1)) || (key2_len != sizeof(socket2))) + return 0; + memcpy(&socket1, key1, sizeof(socket1)); + memcpy(&socket2, key2, sizeof(socket2)); + return socket1 == socket2; +} + +static size_t compare_bytes(const void *key1, size_t key1_len, + const void *key2, size_t key2_len) +{ + return (key1_len == key2_len) && !memcmp(key1, key2, key1_len); +} + +static const struct hash_functions hash_functions[] = { + { hash_str, compare_bytes }, + { hash_socket, compare_socket } +}; + +static const struct hash_functions *hash_get_functions(uint8_t type) +{ + DEBUGASSERT(CURL_ARRAYSIZE(hash_functions) == CURL_HASH_TYPE_LAST); + if((unsigned int)type >= CURL_HASH_TYPE_LAST) + type = CURL_HASH_TYPE_BYTES; + return &hash_functions[type]; +} + +static bool hash_key_is_valid(uint8_t type, size_t key_len) +{ + return (type == CURL_HASH_TYPE_BYTES) || + ((type == CURL_HASH_TYPE_SOCKET) && + (key_len == sizeof(curl_socket_t))); +} + #if 0 /* useful function for debugging hashes and their contents */ void Curl_hash_print(struct Curl_hash *h, void (*func)(void *)) { @@ -59,7 +196,7 @@ void Curl_hash_print(struct Curl_hash *h, void (*func)(void *)) func(he->ptr); else curl_mfprintf(stderr, " [key=%.*s, he=%p, ptr=%p]", - (int)he->key_len, (char *)he->key, + (int)he->key_len, hash_elem_key(he), (void *)he, (void *)he->ptr); he = Curl_hash_next_element(&iter); @@ -69,29 +206,25 @@ void Curl_hash_print(struct Curl_hash *h, void (*func)(void *)) #endif /* Initializes a hash structure. - * Return 1 on error, 0 is fine. * * @unittest: 1602 * @unittest: 1603 */ void Curl_hash_init(struct Curl_hash *h, size_t slots, - hash_function hfunc, - comp_function comparator, + Curl_hash_type type, Curl_hash_dtor dtor) { DEBUGASSERT(h); DEBUGASSERT(slots); - DEBUGASSERT(hfunc); - DEBUGASSERT(comparator); + DEBUGASSERT((unsigned int)type < CURL_HASH_TYPE_LAST); DEBUGASSERT(dtor); h->table = NULL; - h->hash_func = hfunc; - h->comp_func = comparator; h->dtor = dtor; h->size = 0; h->slots = slots; + h->type = (uint8_t)type; #ifdef DEBUGBUILD h->init = HASHINIT; #endif @@ -99,7 +232,7 @@ void Curl_hash_init(struct Curl_hash *h, static struct Curl_hash_element *hash_elem_create(const void *key, size_t key_len, - const void *p, + void *p, Curl_hash_elem_dtor dtor) { struct Curl_hash_element *he; @@ -109,9 +242,9 @@ static struct Curl_hash_element *hash_elem_create(const void *key, if(he) { he->next = NULL; /* copy the key */ - memcpy(he->key, key, key_len); + memcpy(hash_elem_key(he), key, key_len); he->key_len = key_len; - he->ptr = CURL_UNCONST(p); + he->ptr = p; he->dtor = dtor; } return he; @@ -124,7 +257,7 @@ static void hash_elem_clear_ptr(struct Curl_hash *h, DEBUGASSERT(he); if(he->ptr) { if(he->dtor) - he->dtor(he->key, he->key_len, he->ptr); + he->dtor(hash_elem_key(he), he->key_len, he->ptr); else h->dtor(he->ptr); he->ptr = NULL; @@ -155,26 +288,28 @@ static void hash_elem_link(struct Curl_hash *h, ++h->size; } -#define CURL_HASH_SLOT(x, y, z) x->table[(x)->hash_func(y, z, (x)->slots)] -#define CURL_HASH_SLOT_ADDR(x, y, z) &CURL_HASH_SLOT(x, y, z) - -void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, +void *Curl_hash_add2(struct Curl_hash *h, + const void *key, size_t key_len, void *p, Curl_hash_elem_dtor dtor) { + const struct hash_functions *functions; struct Curl_hash_element *he, **slot; DEBUGASSERT(h); DEBUGASSERT(h->slots); DEBUGASSERT(h->init == HASHINIT); + if(!hash_key_is_valid(h->type, key_len)) + return NULL; if(!h->table) { h->table = curlx_calloc(h->slots, sizeof(struct Curl_hash_element *)); if(!h->table) return NULL; /* OOM */ } - slot = CURL_HASH_SLOT_ADDR(h, key, key_len); + functions = hash_get_functions(h->type); + slot = &h->table[functions->hash(key, key_len, h->slots)]; for(he = *slot; he; he = he->next) { - if(h->comp_func(he->key, he->key_len, key, key_len)) { + if(functions->compare(hash_elem_key(he), he->key_len, key, key_len)) { /* existing key entry, overwrite by clearing old pointer */ hash_elem_clear_ptr(h, he); he->ptr = p; @@ -199,7 +334,8 @@ void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, * @unittest: 1602 * @unittest: 1603 */ -void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) +void *Curl_hash_add(struct Curl_hash *h, + const void *key, size_t key_len, void *p) { return Curl_hash_add2(h, key, key_len, p, NULL); } @@ -209,18 +345,23 @@ void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p) * * @unittest: 1603 */ -int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) +int Curl_hash_delete(struct Curl_hash *h, const void *key, size_t key_len) { + const struct hash_functions *functions; + DEBUGASSERT(h); DEBUGASSERT(h->slots); DEBUGASSERT(h->init == HASHINIT); + if(!hash_key_is_valid(h->type, key_len)) + return 1; if(h->table) { struct Curl_hash_element *he, **he_anchor; - he_anchor = CURL_HASH_SLOT_ADDR(h, key, key_len); + functions = hash_get_functions(h->type); + he_anchor = &h->table[functions->hash(key, key_len, h->slots)]; while(*he_anchor) { he = *he_anchor; - if(h->comp_func(he->key, he->key_len, key, key_len)) { + if(functions->compare(hash_elem_key(he), he->key_len, key, key_len)) { hash_elem_unlink(h, he_anchor, he); hash_elem_destroy(h, he); return 0; @@ -235,16 +376,21 @@ int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) * * @unittest: 1603 */ -void *Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len) +void *Curl_hash_pick(struct Curl_hash *h, const void *key, size_t key_len) { + const struct hash_functions *functions; + DEBUGASSERT(h); DEBUGASSERT(h->init == HASHINIT); + if(!hash_key_is_valid(h->type, key_len)) + return NULL; if(h->table) { struct Curl_hash_element *he; DEBUGASSERT(h->slots); - he = CURL_HASH_SLOT(h, key, key_len); + functions = hash_get_functions(h->type); + he = h->table[functions->hash(key, key_len, h->slots)]; while(he) { - if(h->comp_func(he->key, he->key_len, key, key_len)) { + if(functions->compare(hash_elem_key(he), he->key_len, key, key_len)) { return he->ptr; } he = he->next; @@ -323,30 +469,6 @@ void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, } } -size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num) -{ - const char *key_str = (const char *)key; - const char *end = key_str + key_length; - size_t h = 5381; - - while(key_str < end) { - size_t j = (size_t)*key_str++; - h += h << 5; - h ^= j; - } - - return (h % slots_num); -} - -size_t curlx_str_key_compare(void *k1, size_t key1_len, - void *k2, size_t key2_len) -{ - if((key1_len == key2_len) && !memcmp(k1, k2, key1_len)) - return 1; - - return 0; -} - void Curl_hash_start_iterate(struct Curl_hash *hash, struct Curl_hash_iterator *iter) { diff --git a/lib/hash.h b/lib/hash.h index d4c73b778647..2becbd919c0f 100644 --- a/lib/hash.h +++ b/lib/hash.h @@ -25,22 +25,16 @@ ***************************************************************************/ #include "curl_setup.h" -/* Hash function prototype */ -typedef size_t (*hash_function)(void *key, - size_t key_length, - size_t slots_num); - -/* - * Comparator function prototype. Compares two keys. - */ -typedef size_t (*comp_function)(void *key1, - size_t key1_len, - void *key2, - size_t key2_len); - typedef void (*Curl_hash_dtor)(void *); -typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p); +typedef void (*Curl_hash_elem_dtor)(const void *key, size_t key_len, void *p); + +/* How keys in a hash are interpreted. */ +typedef enum { + CURL_HASH_TYPE_BYTES, + CURL_HASH_TYPE_SOCKET, + CURL_HASH_TYPE_LAST +} Curl_hash_type; struct Curl_hash_element { struct Curl_hash_element *next; @@ -52,15 +46,11 @@ struct Curl_hash_element { struct Curl_hash { struct Curl_hash_element **table; - - /* Hash function to be used for this hash table */ - hash_function hash_func; - /* Comparator function to compare keys */ - comp_function comp_func; - /* General element construct, unless element itself carries one */ + /* General element destructor, unless element itself carries one */ Curl_hash_dtor dtor; size_t slots; size_t size; + uint8_t type; /* Curl_hash_type */ #ifdef DEBUGBUILD int init; #endif @@ -75,26 +65,26 @@ struct Curl_hash_iterator { #endif }; +CURLcode Curl_hash_global_init(void); + void Curl_hash_init(struct Curl_hash *h, size_t slots, - hash_function hfunc, - comp_function comparator, + Curl_hash_type type, Curl_hash_dtor dtor); -void *Curl_hash_add(struct Curl_hash *h, void *key, size_t key_len, void *p); -void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, +void *Curl_hash_add(struct Curl_hash *h, + const void *key, size_t key_len, void *p); +void *Curl_hash_add2(struct Curl_hash *h, + const void *key, size_t key_len, void *p, Curl_hash_elem_dtor dtor); -int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len); -void *Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len); +int Curl_hash_delete(struct Curl_hash *h, const void *key, size_t key_len); +void *Curl_hash_pick(struct Curl_hash *h, const void *key, size_t key_len); void Curl_hash_destroy(struct Curl_hash *h); size_t Curl_hash_count(struct Curl_hash *h); void Curl_hash_clean(struct Curl_hash *h); void Curl_hash_clean_with_criterium(struct Curl_hash *h, void *user, int (*comp)(void *, void *)); -size_t Curl_hash_str(void *key, size_t key_length, size_t slots_num); -size_t curlx_str_key_compare(void *k1, size_t key1_len, void *k2, - 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( diff --git a/lib/headers.c b/lib/headers.c index a6416b0f07b2..438eb5464ce7 100644 --- a/lib/headers.c +++ b/lib/headers.c @@ -27,6 +27,7 @@ #include "sendf.h" #include "curl_trc.h" #include "headers.h" +#include "strcase.h" #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API) @@ -139,6 +140,80 @@ CURLHcode curl_easy_header(CURL *curl, return hresult; } +struct nextheader_cache_entry { + struct Curl_header_store *header; + size_t order; +}; + +static int nextheader_compare(const void *p1, const void *p2) +{ + const struct nextheader_cache_entry *e1 = p1; + const struct nextheader_cache_entry *e2 = p2; + const struct Curl_header_store *h1 = e1->header; + const struct Curl_header_store *h2 = e2->header; + const char *n1 = h1->name; + const char *n2 = h2->name; + + while(*n1 && *n2) { + unsigned char c1 = (unsigned char)Curl_raw_toupper(*n1++); + unsigned char c2 = (unsigned char)Curl_raw_toupper(*n2++); + if(c1 != c2) + return c1 > c2 ? 1 : -1; + } + if(*n1) + return 1; + if(*n2) + return -1; + if(e1->order != e2->order) + return e1->order > e2->order ? 1 : -1; + return 0; +} + +static bool nextheader_cache_build(struct Curl_easy *data, + unsigned int origin, + int request, size_t count) +{ + struct nextheader_cache_entry *headers; + struct Curl_llist_node *e; + size_t i = 0; + size_t first; + + headers = curlx_malloc(sizeof(*headers) * count); + if(!headers) + return FALSE; + + for(e = Curl_llist_head(&data->state.httphdrs); e; + e = Curl_node_next(e)) { + struct Curl_header_store *hs = Curl_node_elem(e); + if((hs->type & origin) && (hs->request == request)) { + headers[i].header = hs; + headers[i].order = i; + i++; + } + } + + qsort(headers, i, sizeof(*headers), nextheader_compare); + for(first = 0; first < i;) { + size_t last = first + 1; + size_t index; + while((last < i) && + curl_strequal(headers[first].header->name, + headers[last].header->name)) + last++; + for(index = first; index < last; index++) { + headers[index].header->nh_amount = last - first; + headers[index].header->nh_index = index - first; + } + first = last; + } + curlx_free(headers); + + data->state.nh_origin = origin; + data->state.nh_request = request; + data->state.nh_count = count; + return TRUE; +} + /* public API */ struct curl_header *curl_easy_nextheader(CURL *curl, unsigned int origin, @@ -156,6 +231,7 @@ struct curl_header *curl_easy_nextheader(CURL *curl, struct Curl_header_store *hs; size_t amount = 0; size_t index = 0; + size_t count; if(request > data->state.requests) goto out; @@ -187,19 +263,29 @@ struct curl_header *curl_easy_nextheader(CURL *curl, goto out; hs = Curl_node_elem(pick); - - /* count number of occurrences of this name within the mask and figure out - the index for the currently selected entry */ - for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { - struct Curl_header_store *check = Curl_node_elem(e); - if(curl_strequal(hs->name, check->name) && - (check->request == request) && - (check->type & origin)) - amount++; - if(e == pick) - index = amount - 1; + count = Curl_llist_count(&data->state.httphdrs); + + if(((data->state.nh_count == count) && + (data->state.nh_origin == origin) && + (data->state.nh_request == request)) || + nextheader_cache_build(data, origin, request, count)) { + amount = hs->nh_amount; + index = hs->nh_index; + } + else { + /* count number of occurrences of this name within the mask and figure + out the index for the currently selected entry */ + for(e = Curl_llist_head(&data->state.httphdrs); e; + e = Curl_node_next(e)) { + struct Curl_header_store *check = Curl_node_elem(e); + if(curl_strequal(hs->name, check->name) && + (check->request == request) && + (check->type & origin)) + amount++; + if(e == pick) + index = amount - 1; + } } - copy_header_external(hs, index, amount, pick, &data->state.headerout[1]); hd = &data->state.headerout[1]; @@ -316,6 +402,7 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header, static void headers_reset(struct Curl_easy *data) { Curl_llist_init(&data->state.httphdrs, NULL); + data->state.nh_count = 0; } struct hds_cw_collect_ctx { diff --git a/lib/headers.h b/lib/headers.h index 283179acdcc8..6b6594d528a6 100644 --- a/lib/headers.h +++ b/lib/headers.h @@ -31,6 +31,8 @@ struct Curl_header_store { struct Curl_llist_node node; char *name; /* points into 'buffer' */ char *value; /* points into 'buffer' */ + size_t nh_amount; /* cached amount for curl_easy_nextheader() */ + size_t nh_index; /* cached index for curl_easy_nextheader() */ int request; /* 0 is the first request, then 1.. 2.. */ unsigned char type; /* CURLH_* defines */ char buffer[1]; /* this is the raw header blob */ diff --git a/lib/hsts.c b/lib/hsts.c index 73c01db01892..1cce1e5fb950 100644 --- a/lib/hsts.c +++ b/lib/hsts.c @@ -43,6 +43,8 @@ #define MAX_HSTS_DATELEN 17 #define UNLIMITED "unlimited" +#define CAP_HSTS_MAX_AGE (2*365*24*3600) /* two years cap */ + #if defined(DEBUGBUILD) || defined(UNITTESTS) /* to play well with debug builds, we can *set* a fixed time this will return */ @@ -257,9 +259,9 @@ CURLcode Curl_hsts_parse(struct hsts *h, const char *hostname, const char *vp = curlx_str(&val); if(gotma) return CURLE_BAD_FUNCTION_ARGUMENT; - rc = curlx_str_number(&vp, &expires, TIME_T_MAX); + rc = curlx_str_number(&vp, &expires, CAP_HSTS_MAX_AGE); if(rc == STRE_OVERFLOW) - expires = CURL_OFF_T_MAX; + expires = CAP_HSTS_MAX_AGE; else if(rc) /* invalid max-age */ return CURLE_BAD_FUNCTION_ARGUMENT; @@ -344,22 +346,21 @@ static CURLcode hsts_push(struct Curl_easy *data, /* * Write this single hsts entry to a single output line */ -static CURLcode hsts_out(struct stsentry *sts, FILE *fp) +static void hsts_out(struct stsentry *sts, FILE *fp) { struct tm stamp; if(sts->expires != TIME_T_MAX) { CURLcode result = curlx_gmtime((time_t)sts->expires, &stamp); - if(result) - return result; - curl_mfprintf(fp, "%s%s \"%d%02d%02d %02d:%02d:%02d\"\n", - sts->includeSubDomains ? "." : "", sts->host, - stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, - stamp.tm_hour, stamp.tm_min, stamp.tm_sec); + if(!result) + /* skip the entry if the date function fails */ + curl_mfprintf(fp, "%s%s \"%d%02d%02d %02d:%02d:%02d\"\n", + sts->includeSubDomains ? "." : "", sts->host, + stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday, + stamp.tm_hour, stamp.tm_min, stamp.tm_sec); } else curl_mfprintf(fp, "%s%s \"%s\"\n", sts->includeSubDomains ? "." : "", sts->host, UNLIMITED); - return CURLE_OK; } /* @@ -394,9 +395,7 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h, for(e = Curl_llist_head(&h->list); e; e = n) { struct stsentry *sts = Curl_node_elem(e); n = Curl_node_next(e); - result = hsts_out(sts, out); - if(result) - break; + hsts_out(sts, out); } curlx_fclose(out); if(!result && tempstore && curlx_rename(tempstore, file)) @@ -463,7 +462,7 @@ static CURLcode hsts_add_host_expire(struct hsts *h, if(hostlen) { /* only add it if not already present */ - e = hsts_check(h, host, hostlen, subdomain); + e = hsts_check(h, host, hostlen, FALSE); if(!e) result = hsts_create(h, host, hostlen, subdomain, expires); /* 'host' is not necessarily null-terminated */ diff --git a/lib/http.c b/lib/http.c index 4b79b2ab9399..016e42c3bddf 100644 --- a/lib/http.c +++ b/lib/http.c @@ -130,7 +130,7 @@ CURLcode Curl_http_setup_conn(struct Curl_easy *data, during this request */ if(data->state.http_neg.wanted == CURL_HTTP_V3x) { /* only HTTP/3, needs to work */ - CURLcode result = Curl_conn_may_http3(data, conn, conn->transport_wanted); + CURLcode result = Curl_conn_may_http3(data, conn); if(result) return result; } @@ -446,8 +446,8 @@ static CURLcode http_perhapsrewind(struct Curl_easy *data, if((data->state.authproxy.picked == CURLAUTH_NEGOTIATE) || (data->state.authhost.picked == CURLAUTH_NEGOTIATE)) { VERBOSE(ongoing_auth = "NEGOTIATE"); - if((conn->http_negotiate_state != GSS_AUTHNONE) || - (conn->proxy_negotiate_state != GSS_AUTHNONE)) { + if((conn->http_negotiate_state != CURL_NEGO_AUTH_NONE) || + (conn->proxy_negotiate_state != CURL_NEGO_AUTH_NONE)) { /* The NEGOTIATE-negotiation has started, keep on sending. * Need to do further work on same connection */ abort_upload = FALSE; @@ -616,19 +616,18 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data) } else if((data->req.httpcode < 300) && !data->state.authhost.done && - data->req.authneg) { - /* no (known) authentication available, - authentication is not "done" yet and - no authentication seems to be required and - we did not try HEAD or GET */ - if((data->state.httpreq != HTTPREQ_GET) && - (data->state.httpreq != HTTPREQ_HEAD)) { - /* clone URL */ - data->req.newurl = Curl_bufref_dup(&data->state.url); - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - data->state.authhost.done = TRUE; - } + data->req.authneg && + /* no (known) authentication available, + authentication is not "done" yet and + no authentication seems to be required and + we did not try HEAD or GET */ + (data->state.httpreq != HTTPREQ_GET) && + (data->state.httpreq != HTTPREQ_HEAD)) { + /* clone URL */ + data->req.newurl = Curl_bufref_dup(&data->state.url); + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + data->state.authhost.done = TRUE; } if(http_should_fail(data, data->req.httpcode)) { failf(data, "The requested URL returned error: %d", @@ -692,7 +691,8 @@ static CURLcode output_auth_headers(struct Curl_easy *data, (proxy && !Curl_checkProxyheaders(data, conn, STRCONST("Proxy-authorization"))) || #endif - (!proxy && !Curl_checkheaders(data, STRCONST("Authorization")))) { + (!proxy && !Curl_checkheaders(data, STRCONST("Authorization")) && + Curl_auth_allowed_to_host(data))) { auth = "Negotiate"; result = Curl_output_negotiate(data, conn, proxy); if(result) @@ -705,10 +705,14 @@ static CURLcode output_auth_headers(struct Curl_easy *data, #endif #ifdef USE_NTLM if(authstatus->picked == CURLAUTH_NTLM) { - auth = "NTLM"; - result = Curl_output_ntlm(data, proxy); - if(result) - return result; + if(proxy || Curl_auth_allowed_to_host(data)) { + auth = "NTLM"; + result = Curl_output_ntlm(data, proxy); + if(result) + return result; + } + else + authstatus->done = TRUE; } else #endif @@ -941,7 +945,7 @@ static CURLcode auth_spnego(struct Curl_easy *data, if(authp->picked == CURLAUTH_NEGOTIATE) { struct connectdata *conn = data->conn; CURLcode result = Curl_input_negotiate(data, conn, proxy, auth); - curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : + uint8_t *negstate = proxy ? &conn->proxy_negotiate_state : &conn->http_negotiate_state; if(!result) { curlx_free(data->req.newurl); @@ -950,7 +954,7 @@ static CURLcode auth_spnego(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; data->state.authproblem = FALSE; /* we received a GSS auth token and we dealt with it fine */ - *negstate = GSS_AUTHRECV; + *negstate = CURL_NEGO_AUTH_RECV; } else data->state.authproblem = TRUE; @@ -1063,7 +1067,7 @@ static CURLcode auth_bearer(struct Curl_easy *data, * headers. They are dealt with both in the transfer.c main loop and in the * proxy CONNECT loop. * - * The 'auth' line ends with a null byte without CR or LF present. + * The 'auth' line ends with a NUL byte without CR or LF present. */ CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth) /* the first non-space */ @@ -1173,73 +1177,47 @@ static void http_switch_to_get(struct Curl_easy *data, int code) (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) +static CURLcode set_auto_referer(struct Curl_easy *data) { - bool disallowport = FALSE; - bool reachedmax = FALSE; - char *follow_url = NULL; + CURLU *u; + char *referer = NULL; CURLUcode uc; - CURLcode rewind_result; - bool switch_to_get = FALSE; - DEBUGASSERT(type != FOLLOW_NONE); + /* We are asked to automatically set the previous URL as the referer + when we get the next URL. We pick the ->url field, which may or may + not be 100% correct */ + Curl_bufref_free(&data->state.referer); - if(type != FOLLOW_FAKE) - data->state.requests++; /* count all real follows */ - if(type == FOLLOW_REDIR) { - if((data->set.maxredirs != -1) && - (data->state.followlocation >= data->set.maxredirs)) { - reachedmax = TRUE; - type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected - to URL */ - } - else { - data->state.followlocation++; /* count redirect-followings, including - auth reloads */ - - if(data->set.http_auto_referer) { - CURLU *u; - char *referer = NULL; - - /* We are asked to automatically set the previous URL as the referer - when we get the next URL. We pick the ->url field, which may or may - not be 100% correct */ - Curl_bufref_free(&data->state.referer); - - /* Make a copy of the URL without credentials and fragment */ - u = curl_url(); - if(!u) - return CURLE_OUT_OF_MEMORY; + /* Make a copy of the URL without credentials and fragment */ + u = curl_url(); + if(!u) + return CURLE_OUT_OF_MEMORY; - uc = curl_url_set(u, CURLUPART_URL, - Curl_bufref_ptr(&data->state.url), 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_USER, NULL, 0); - if(!uc) - uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); - if(!uc) - uc = curl_url_get(u, CURLUPART_URL, &referer, 0); + uc = curl_url_set(u, CURLUPART_URL, + Curl_bufref_ptr(&data->state.url), 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_USER, NULL, 0); + if(!uc) + uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0); + if(!uc) + uc = curl_url_get(u, CURLUPART_URL, &referer, 0); - curl_url_cleanup(u); + curl_url_cleanup(u); - if(uc || !referer) - return CURLE_OUT_OF_MEMORY; + if(uc || !referer) + return CURLE_OUT_OF_MEMORY; - Curl_bufref_set(&data->state.referer, referer, 0, curl_free); - } - } - } + Curl_bufref_set(&data->state.referer, referer, 0, curl_free); + return CURLE_OK; +} - if((type != FOLLOW_RETRY) && - (data->req.httpcode != 401) && (data->req.httpcode != 407) && - Curl_is_absolute_url(newurl, NULL, 0, FALSE)) { - /* If this is not redirect due to a 401 or 407 response and an absolute - URL: do not allow a custom port number */ - disallowport = TRUE; - } +static CURLcode follow_parse_url(struct Curl_easy *data, const char *newurl, + followtype type, char **follow_urlp, + CURLUcode *ucp) +{ + CURLUcode uc; DEBUGASSERT(data->state.uh); uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int) @@ -1247,6 +1225,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, ((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) | CURLU_ALLOW_SPACE | (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); + *ucp = uc; if(uc) { if((uc == CURLUE_OUT_OF_MEMORY) || (type != FOLLOW_FAKE)) { failf(data, "The redirect target URL could not be parsed: %s", @@ -1256,8 +1235,8 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, /* the URL could not be parsed for some reason, but since this is FAKE mode, duplicate the field as-is */ - follow_url = curlx_strdup(newurl); - if(!follow_url) + *follow_urlp = curlx_strdup(newurl); + if(!*follow_urlp) return CURLE_OUT_OF_MEMORY; } else { @@ -1268,7 +1247,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, 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); + uc = curl_url_get(data->state.uh, CURLUPART_URL, follow_urlp, 0); if(uc) { curl_url_cleanup(u); return Curl_uc_to_curlcode(uc); @@ -1285,53 +1264,50 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, curl_url_cleanup(u); #endif } - DEBUGASSERT(follow_url); + return CURLE_OK; +} - if(type == FOLLOW_FAKE) { - /* we are only figuring out the new URL if we would have followed locations - but now we are done so we can get out! */ - data->info.wouldredirect = follow_url; +static CURLcode follow_fake(struct Curl_easy *data, char *follow_url, + CURLUcode uc, bool reachedmax) +{ + /* we are only figuring out the new URL if we would have followed locations + but now we are done so we can get out! */ + if(!uc) { + CURLU *u = curl_url(); + char *nocred = NULL; - if(reachedmax) { - failf(data, "Maximum (%d) redirects followed", data->set.maxredirs); - return CURLE_TOO_MANY_REDIRECTS; + if(!u) { + curlx_free(follow_url); + return CURLE_OUT_OF_MEMORY; + } + if(!curl_url_set(u, CURLUPART_URL, follow_url, + CURLU_NON_SUPPORT_SCHEME | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)) && + !curl_url_set(u, CURLUPART_USER, NULL, 0) && + !curl_url_set(u, CURLUPART_PASSWORD, NULL, 0)) + (void)curl_url_get(u, CURLUPART_URL, &nocred, CURLU_GET_EMPTY); + curl_url_cleanup(u); + if(nocred) { + curlx_free(follow_url); + follow_url = nocred; } - return CURLE_OK; } + data->info.wouldredirect = follow_url; - if(disallowport) - data->state.allow_port = FALSE; - - Curl_bufref_set(&data->state.url, follow_url, 0, curl_free); - rewind_result = Curl_req_soft_reset(&data->req, data); - infof(data, "Issue another request to this URL: '%s'", follow_url); - if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) && - !data->state.http_ignorecustom && - CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) { - data->state.http_ignorecustom = TRUE; - infof(data, "Drop custom request method for next request"); + if(reachedmax) { + failf(data, "Maximum (%d) redirects followed", data->set.maxredirs); + return CURLE_TOO_MANY_REDIRECTS; } + return CURLE_OK; +} +static bool follow_switch_get(struct Curl_easy *data) +{ /* - * We get here when the HTTP code is 300-399 (and 401). We need to perform - * differently based on exactly what return code there was. - * - * News from 7.10.6: we can also get here on a 401 or 407, in case we act on - * an HTTP (proxy-) authentication scheme other than Basic. + * We get here when the HTTP code is 300-399 and 401 or 407. We need to + * perform differently based on exactly what return code there was. */ switch(data->info.httpcode) { - /* 401 - Act on a WWW-Authenticate, we keep on moving and do the - Authorization: XXXX header in the HTTP request code snippet */ - /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the - Proxy-Authorization: XXXX header in the HTTP request code snippet */ - /* 300 - Multiple Choices */ - /* 306 - Not used */ - /* 307 - Temporary Redirect */ - default: /* for all above (and the unknown ones) */ - /* Some codes are explicitly mentioned since I have checked RFC2616 and - * they seem to be OK to POST to. - */ - break; case 301: /* Moved Permanently */ /* (quote from RFC7231, section 6.4.2) * @@ -1351,7 +1327,7 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, */ if(HTTPREQ_IS_POST(data) && !data->set.post301) { http_switch_to_get(data, 301); - switch_to_get = TRUE; + return TRUE; } break; case 302: /* Found */ @@ -1373,10 +1349,9 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, */ if(HTTPREQ_IS_POST(data) && !data->set.post302) { http_switch_to_get(data, 302); - switch_to_get = TRUE; + return TRUE; } break; - case 303: /* See Other */ /* '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 @@ -1384,24 +1359,92 @@ CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, */ if(!HTTPREQ_IS_POST(data) || !data->set.post303) { http_switch_to_get(data, 303); - switch_to_get = TRUE; + return TRUE; } break; - case 304: /* Not Modified */ + /* 304 means we did a conditional request and it was "Not modified". * We should not get any Location: header in this response! */ - break; - case 305: /* Use Proxy */ - /* (quote from RFC2616, section 10.3.6): + + /* 305 (quote from RFC2616, section 10.3.6): * "The requested resource MUST be accessed through the proxy given * by the Location field. The Location field gives the URI of the * proxy. The recipient is expected to repeat this single request * via the proxy. 305 responses MUST only be generated by origin * servers." */ + default: break; } + return FALSE; +} + +CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl, + followtype type) +{ + bool disallowport = FALSE; + bool reachedmax = FALSE; + char *follow_url = NULL; + CURLUcode uc; + CURLcode result; + CURLcode rewind_result; + bool switch_to_get; + + DEBUGASSERT(type != FOLLOW_NONE); + + if(type != FOLLOW_FAKE) + data->state.requests++; /* count all real follows */ + if(type == FOLLOW_REDIR) { + if((data->set.maxredirs != -1) && + (data->state.followlocation >= data->set.maxredirs)) { + reachedmax = TRUE; + type = FOLLOW_FAKE; /* switch to fake to store the would-be-redirected + to URL */ + } + else { + data->state.followlocation++; /* count redirect-followings, including + auth reloads */ + + if(data->set.http_auto_referer) { + result = set_auto_referer(data); + if(result) + return result; + } + } + } + + if((type != FOLLOW_RETRY) && + (data->req.httpcode != 401) && (data->req.httpcode != 407) && + Curl_is_absolute_url(newurl, NULL, 0, FALSE)) { + /* If this is not redirect due to a 401 or 407 response and an absolute + URL: do not allow a custom port number */ + disallowport = TRUE; + } + + result = follow_parse_url(data, newurl, type, &follow_url, &uc); + if(result) + return result; + + DEBUGASSERT(follow_url); + + if(type == FOLLOW_FAKE) + return follow_fake(data, follow_url, uc, reachedmax); + + if(disallowport) + data->state.allow_port = FALSE; + + Curl_bufref_set(&data->state.url, follow_url, 0, curl_free); + rewind_result = Curl_req_soft_reset(&data->req, data); + infof(data, "Issue another request to this URL: '%s'", follow_url); + if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) && + !data->state.http_ignorecustom && + CURL_EASY_STR(data, STRING_CUSTOMREQUEST)) { + data->state.http_ignorecustom = TRUE; + infof(data, "Drop custom request method for next request"); + } + + switch_to_get = follow_switch_get(data); /* When rewind of upload data failed and we are not switching to GET, * we need to fail the follow, as we cannot send the data again. */ @@ -1457,7 +1500,7 @@ bool Curl_compareheader(const char *headerline, /* line to check */ 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 */ + /* after a match there must be a comma, space, newline or NUL byte */ if(curl_strnequal(p, content, clen) && ((p[clen] == ',') || ISBLANK(p[clen]) || ISNEWLINE(p[clen]) || !p[clen])) @@ -2379,35 +2422,11 @@ static CURLcode set_reader(struct Curl_easy *data, Curl_HttpReq httpreq) static CURLcode http_resume(struct Curl_easy *data, Curl_HttpReq httpreq) { - if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) && + if((HTTPREQ_POST == httpreq || HTTPREQ_POST_FORM == httpreq || + HTTPREQ_POST_MIME == httpreq || HTTPREQ_PUT == httpreq) && data->state.resume_from) { - /********************************************************************** - * Resuming upload in HTTP means that we PUT or POST and that we have - * got a resume_from value set. The resume value has already created - * a Range: header that will be passed along. We need to "fast forward" - * the file the given number of bytes and decrease the assume upload - * file size before we continue this venture in the dark lands of HTTP. - * Resuming mime/form posting at an offset > 0 has no sense and is ignored. - *********************************************************************/ - - if(data->state.resume_from < 0) { - /* - * This is meant to get the size of the present remote-file by itself. - * We do not support this now. Bail out! - */ - data->state.resume_from = 0; - } - - if(data->state.resume_from && !data->req.authneg) { - /* only act on the first request */ - CURLcode result; - result = Curl_creader_resume_from(data, data->state.resume_from); - if(result) { - failf(data, "Unable to resume from offset %" FMT_OFF_T, - data->state.resume_from); - return result; - } - } + failf(data, "HTTP upload cannot be resumed"); + return CURLE_BAD_FUNCTION_ARGUMENT; } return CURLE_OK; } @@ -2547,13 +2566,12 @@ static CURLcode http_add_content_hds(struct Curl_easy *data, } } #endif - if(httpreq == HTTPREQ_POST) { - if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { - result = curlx_dyn_addn(r, STRCONST("Content-Type: application/" - "x-www-form-urlencoded\r\n")); - if(result) - goto out; - } + if(httpreq == HTTPREQ_POST && + !Curl_checkheaders(data, STRCONST("Content-Type"))) { + result = curlx_dyn_addn(r, STRCONST("Content-Type: application/" + "x-www-form-urlencoded\r\n")); + if(result) + goto out; } result = addexpect(data, r, httpversion, &announced_exp100); if(result) @@ -2747,22 +2765,20 @@ static CURLcode http_firstwrite(struct Curl_easy *data) return CURLE_RANGE_ERROR; } - if(data->set.timecondition && !data->state.range) { - /* A time condition has been set AND no ranges have been requested. This - seems to be what chapter 13.3.4 of RFC 2616 defines to be the correct - action for an HTTP/1.1 client */ - - if(!Curl_meets_timecondition(data, k->timeofdoc)) { - k->done = TRUE; - /* We are simulating an HTTP 304 from server so we return - what should have been returned from the server */ - data->info.httpcode = 304; - infof(data, "Simulate an HTTP 304 response"); - /* we abort the transfer before it is completed == we ruin the - reuse ability. Close the connection */ - streamclose(conn); - return CURLE_OK; - } + if(data->set.timecondition && !data->state.range && + /* A time condition has been set AND no ranges have been requested. This + seems to be what chapter 13.3.4 of RFC 2616 defines to be the correct + action for an HTTP/1.1 client */ + !Curl_meets_timecondition(data, k->timeofdoc)) { + k->done = TRUE; + /* We are simulating an HTTP 304 from server so we return + what should have been returned from the server */ + data->info.httpcode = 304; + infof(data, "Simulate an HTTP 304 response"); + /* we abort the transfer before it is completed == we ruin the + reuse ability. Close the connection */ + streamclose(conn); + return CURLE_OK; } /* we have a time condition */ return CURLE_OK; @@ -3276,7 +3292,7 @@ static CURLcode http_header_c(struct Curl_easy *data, list also is fine and then we should accept them all as long as they are the same value. Different values trigger error. */ - v = (!k->http_bodyless && !data->set.ignorecl) ? + v = (!k->http_bodyless && !data->set.ignorecl && !k->ignore_cl) ? HD_VAL(hd, hdlen, "Content-Length:") : NULL; if(v) { do { @@ -3493,8 +3509,15 @@ static CURLcode http_header_p(struct Curl_easy *data, } #endif if((407 == k->httpcode) && HD_IS(hd, hdlen, "Proxy-authenticate:")) { - char *auth = Curl_copy_header_value(hd); - CURLcode result = auth ? CURLE_OK : CURLE_OUT_OF_MEMORY; + char *auth; + CURLcode result; +#ifndef CURL_DISABLE_PROXY + if(!data->conn->bits.origin_is_proxy) + /* this response is not from a forward proxy, not for us */ + return CURLE_OK; +#endif + auth = Curl_copy_header_value(hd); + result = auth ? CURLE_OK : CURLE_OUT_OF_MEMORY; if(!result) { result = Curl_http_input_auth(data, TRUE, auth); curlx_free(auth); @@ -3649,7 +3672,7 @@ static CURLcode http_header_t(struct Curl_easy *data, CURLcode result = Curl_build_unencoding_stack(data, v, TRUE); if(result) return result; - if(!k->chunk && data->set.http_transfer_encoding) { + if(!k->chunk) { /* if this is not chunked, only close can signal the end of this * transfer as Content-Length is said not to be trusted for * transfer-encoding! */ @@ -3859,18 +3882,24 @@ CURLcode Curl_verify_header(struct Curl_easy *data, const char *hd, size_t hdlen) { struct SingleRequest *k = &data->req; - const char *ptr = memchr(hd, 0x00, hdlen); - if(ptr) { - /* this is bad, bail out */ - failf(data, "Nul byte in header"); - return CURLE_WEIRD_SERVER_REPLY; - } - if(hdlen > 2) { - ptr = memchr(hd, '\r', hdlen - 2); - if(ptr) { - /* CR may only precede the LF, nothing else */ - failf(data, "Carriage return found in header"); + size_t i; + bool has_colon = FALSE; + for(i = 0; i < hdlen; i++) { + switch(hd[i]) { + case '\0': + /* this is bad, bail out */ + failf(data, "NUL byte in header"); return CURLE_WEIRD_SERVER_REPLY; + case '\r': + if(i < hdlen - 2) { + /* CR may only precede the LF, nothing else */ + failf(data, "Carriage return found in header"); + return CURLE_WEIRD_SERVER_REPLY; + } + break; + case ':': + has_colon = TRUE; + break; } } if(k->headerline < 2) @@ -3879,13 +3908,10 @@ CURLcode Curl_verify_header(struct Curl_easy *data, if(((hd[0] == ' ') || (hd[0] == '\t')) && k->headerline > 2) /* line folding, cannot happen on line 2 */ ; - else { - ptr = memchr(hd, ':', hdlen); - if(!ptr) { - /* this is bad, bail out */ - failf(data, "Header without colon"); - return CURLE_WEIRD_SERVER_REPLY; - } + else if(!has_colon) { + /* this is bad, bail out */ + failf(data, "Header without colon"); + return CURLE_WEIRD_SERVER_REPLY; } return CURLE_OK; } @@ -4036,19 +4062,19 @@ static void http_check_auth_closure(struct Curl_easy *data, #ifdef USE_SPNEGO if(conn->bits.close && (((data->req.httpcode == 401) && - (conn->http_negotiate_state == GSS_AUTHRECV)) || + (conn->http_negotiate_state == CURL_NEGO_AUTH_RECV)) || ((data->req.httpcode == 407) && - (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { + (conn->proxy_negotiate_state == CURL_NEGO_AUTH_RECV)))) { infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); data->state.authproblem = TRUE; } - if((conn->http_negotiate_state == GSS_AUTHDONE) && + if((conn->http_negotiate_state == CURL_NEGO_AUTH_DONE) && (data->req.httpcode != 401)) { - conn->http_negotiate_state = GSS_AUTHSUCC; + conn->http_negotiate_state = CURL_NEGO_AUTH_SUCC; } - if((conn->proxy_negotiate_state == GSS_AUTHDONE) && + if((conn->proxy_negotiate_state == CURL_NEGO_AUTH_DONE) && (data->req.httpcode != 407)) { - conn->proxy_negotiate_state = GSS_AUTHSUCC; + conn->proxy_negotiate_state = CURL_NEGO_AUTH_SUCC; } #endif } @@ -4238,6 +4264,16 @@ static CURLcode http_on_response(struct Curl_easy *data, if(result) goto out; +#ifndef CURL_DISABLE_RTSP + /* An RTSP response to any method may carry a body, which is announced + with Content-Length (RFC 2326 section 12.14). Read it instead of + treating it as excess data, unless the application asked for no body + with CURLOPT_NOBODY. */ + if((conn->scheme->protocol & CURLPROTO_RTSP) && data->req.no_body && + !data->set.opt_no_body && (k->size > 0)) + data->req.no_body = FALSE; +#endif + /* If we requested a "no body", this is a good time to get * out and return home. */ @@ -4701,10 +4737,10 @@ CURLcode Curl_http_write_resp(struct Curl_easy *data, } /* Decode HTTP status code string. */ -CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len) +CURLcode Curl_http_decode_status(int32_t *pstatus, const char *s, size_t len) { CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; - int status = 0; + int32_t status = 0; int i; if(len != 3) diff --git a/lib/http.h b/lib/http.h index 83b4cc6c6414..2756378092df 100644 --- a/lib/http.h +++ b/lib/http.h @@ -197,7 +197,7 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data, bool is_connect); /* Decode HTTP status code string. */ -CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len); +CURLcode Curl_http_decode_status(int32_t *pstatus, const char *s, size_t len); /** * All about a core HTTP request, excluding body and trailers diff --git a/lib/http2.c b/lib/http2.c index 0cfdf20571be..9010179e57be 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -28,7 +28,7 @@ #include "urldata.h" #include "bufq.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "http1.h" #include "http2.h" #include "http.h" @@ -97,7 +97,7 @@ struct cf_h2_ctx { struct bufc_pool stream_bufcp; /* spares for stream buffers */ struct dynbuf scratch; /* scratch buffer for temp use */ - struct uint_hash streams; /* hash of `data->mid` to `h2_stream_ctx` */ + struct u32_ptrset streams; /* hash of `data->mid` to `h2_stream_ctx` */ size_t drain_total; /* sum of all stream's UrlState drain */ uint32_t initial_win_size; /* current initial window size (settings) */ uint32_t max_concurrent_streams; @@ -125,17 +125,19 @@ struct h2_stream_ctx { struct h1_req_parser h1; /* parsing the request */ struct dynhds resp_trailers; /* response trailer fields */ size_t resp_hds_len; /* amount of response header bytes in recvbuf */ + curl_off_t xfer_id; /* transfer `id` owning this stream */ curl_off_t nrcvd_data; /* number of DATA bytes received */ char **push_headers; /* allocated array */ size_t push_headers_used; /* number of entries filled in */ size_t push_headers_alloc; /* number of entries allocated */ - int status_code; /* HTTP response status code */ + int32_t id; /* HTTP/2 protocol identifier for stream */ + uint32_t mid; /* transfer `mid` owning this stream */ uint32_t error; /* stream error code */ CURLcode xfer_result; /* Result of writing out response */ int32_t local_window_size; /* the local recv window size */ - int32_t id; /* HTTP/2 protocol identifier for stream */ + int32_t status_code; /* HTTP response status code */ BIT(resp_hds_complete); /* we have a complete, final response */ BIT(closed); /* TRUE on stream close */ BIT(reset); /* TRUE on stream reset */ @@ -165,7 +167,7 @@ static void h2_stream_ctx_free(struct h2_stream_ctx *stream) curlx_free(stream); } -static void h2_stream_hash_free(unsigned int id, void *stream) +static void h2_stream_hash_free(uint32_t id, void *stream) { (void)id; DEBUGASSERT(stream); @@ -178,7 +180,7 @@ static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); - Curl_uint32_hash_init(&ctx->streams, 63, h2_stream_hash_free); + Curl_u32_ptrset_init(&ctx->streams, h2_stream_hash_free); ctx->remote_max_sid = INT32_MAX; ctx->via_h1_upgrade = via_h1_upgrade; ctx->initialized = TRUE; @@ -193,7 +195,7 @@ static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) Curl_bufq_free(&ctx->outbufq); Curl_bufcp_free(&ctx->stream_bufcp); curlx_dyn_free(&ctx->scratch); - Curl_uint32_hash_destroy(&ctx->streams); + Curl_u32_ptrset_clear(&ctx->streams); memset(ctx, 0, sizeof(*ctx)); } curlx_free(ctx); @@ -255,22 +257,25 @@ static CURLcode cf_h2_update_settings(struct cf_h2_ctx *ctx, #define H2_STREAM_CTX(ctx, data) \ ((struct h2_stream_ctx *)( \ - (data) ? Curl_uint32_hash_get(&(ctx)->streams, (data)->mid) : NULL)) + (data) ? Curl_u32_ptrset_get(&(ctx)->streams, (data)->mid) : NULL)) -static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) +static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx, + struct Curl_easy *data) { struct h2_stream_ctx *stream; - (void)ctx; stream = curlx_calloc(1, sizeof(*stream)); if(!stream) return NULL; stream->id = -1; + stream->xfer_id = data->id; + stream->mid = data->mid; Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&stream->resp_trailers, + MAX_HTTP_RESP_HEADER_COUNT, DYN_HTTP_REQUEST); stream->bodystarted = FALSE; stream->status_code = -1; stream->closed = FALSE; @@ -281,6 +286,28 @@ static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) return stream; } +static struct Curl_easy *cf_h2_get_stream_xfer(struct Curl_cfilter *cf, + int32_t stream_id, + struct h2_stream_ctx **pstream) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream = + nghttp2_session_get_stream_user_data(ctx->h2, stream_id); + if(pstream) + *pstream = stream; + if(stream && (stream->mid != UINT32_MAX)) { + struct Curl_easy *data, *call_data = CF_DATA_CURRENT(cf); + if(!call_data || !call_data->multi) { + DEBUGASSERT(0); + return NULL; + } + data = Curl_multi_get_easy(call_data->multi, stream->mid); + if(data && (data->id == stream->xfer_id)) + return data; + } + return NULL; +} + #ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE static int32_t cf_h2_get_desired_local_win(struct Curl_cfilter *cf, struct Curl_easy *data) @@ -377,11 +404,11 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf, return CURLE_OK; } - stream = h2_stream_ctx_create(ctx); + stream = h2_stream_ctx_create(ctx, data); if(!stream) return CURLE_OUT_OF_MEMORY; - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { + if(Curl_u32_ptrset_set(&ctx->streams, data->mid, stream)) { h2_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } @@ -444,7 +471,7 @@ static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) } } - Curl_uint32_hash_remove(&ctx->streams, data->mid); + Curl_u32_ptrset_unset(&ctx->streams, data->mid); } static int h2_client_new(struct Curl_cfilter *cf, @@ -496,7 +523,7 @@ static CURLcode h2_process_pending_input(struct Curl_cfilter *cf, rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen); if(!curlx_sztouz(rv, &nread)) { failf(data, "nghttp2 recv error %zd: %s", rv, nghttp2_strerror((int)rv)); - return CURLE_HTTP2; + return CURLE_RECV_ERROR; } Curl_bufq_skip(&ctx->inbufq, nread); if(Curl_bufq_is_empty(&ctx->inbufq)) { @@ -707,6 +734,18 @@ static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, second->state.weight = data->state.weight; if(data->share) (void)Curl_share_easy_link(second, data->share); + /* copy a range of options from the parent handle */ + second->set.fwrite_func = data->set.fwrite_func; + second->set.out = data->set.out; + second->set.fwrite_header = data->set.fwrite_header; + second->set.writeheader = data->set.writeheader; + second->set.fdebug = data->set.fdebug; + second->set.debugdata = data->set.debugdata; + second->set.fxferinfo = data->set.fxferinfo; + second->set.fprogress = data->set.fprogress; + second->set.progress_client = data->set.progress_client; + second->progress.hide = data->progress.hide; + second->progress.callback = data->progress.callback; } return second; } @@ -762,10 +801,13 @@ static int set_transfer_url(struct Curl_easy *newhandle, if(uc) rc = 4; fail: - curl_url_cleanup(u); - if(rc) + if(rc) { + curl_url_cleanup(u); return rc; + } + DEBUGASSERT(!newhandle->state.uh); + newhandle->state.uh = u; Curl_bufref_set(&newhandle->state.url, url, 0, curl_free); return 0; } @@ -870,7 +912,7 @@ static int push_promise(struct Curl_cfilter *cf, newhandle->mid); rv = nghttp2_session_set_stream_user_data(ctx->h2, newstream->id, - newhandle); + newstream); if(rv) { infof(data, "failed to set user_data for stream %d", newstream->id); @@ -940,10 +982,10 @@ static void h2_xfer_write_resp(struct Curl_cfilter *cf, static CURLcode on_stream_frame(struct Curl_cfilter *cf, struct Curl_easy *data, + struct h2_stream_ctx *stream, const nghttp2_frame *frame) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int32_t stream_id = frame->hd.stream_id; int rv; @@ -984,17 +1026,16 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, return CURLE_RECV_ERROR; /* Only final status code signals the end of header */ - if(stream->status_code / 100 != 1) + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; stream->bodystarted = TRUE; + } else stream->status_code = -1; h2_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), (bool)stream->closed); - if(stream->status_code / 100 != 1) { - stream->resp_hds_complete = TRUE; - } Curl_multi_mark_dirty(data); break; case NGHTTP2_PUSH_PROMISE: @@ -1157,6 +1198,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, struct Curl_cfilter *cf = userp; struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data = CF_DATA_CURRENT(cf), *data_s; + struct h2_stream_ctx *stream; int32_t stream_id = frame->hd.stream_id; DEBUGASSERT(data); @@ -1217,13 +1259,14 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - data_s = nghttp2_session_get_stream_user_data(session, stream_id); + data_s = cf_h2_get_stream_xfer(cf, stream_id, &stream); if(!data_s) { CURL_TRC_CF(data, cf, "[%d] No Curl_easy associated", stream_id); return 0; } - return on_stream_frame(cf, data_s, frame) ? NGHTTP2_ERR_CALLBACK_FAILURE : 0; + return on_stream_frame(cf, data_s, stream, frame) ? + NGHTTP2_ERR_CALLBACK_FAILURE : 0; } static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, @@ -1233,11 +1276,12 @@ static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, struct Curl_cfilter *cf = userp; struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data; + struct h2_stream_ctx *stream; int32_t stream_id = frame->hd.stream_id; - data = nghttp2_session_get_stream_user_data(session, stream_id); + (void)session; + data = cf_h2_get_stream_xfer(cf, stream_id, &stream); if(data) { - struct h2_stream_ctx *stream; #ifdef CURLVERBOSE char buffer[256]; int len; @@ -1246,17 +1290,16 @@ static int cf_h2_on_invalid_frame_recv(nghttp2_session *session, failf(data, "[HTTP2] [%d] received invalid frame: %s, error %d: %s", stream_id, buffer, ngerr, nghttp2_strerror(ngerr)); #endif /* CURLVERBOSE */ - stream = H2_STREAM_CTX(ctx, data); if(stream) { nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, stream->id, NGHTTP2_STREAM_CLOSED); stream->error = ngerr; stream->closed = TRUE; stream->reset = TRUE; - return 0; /* keep the connection alive */ } } - return NGHTTP2_ERR_CALLBACK_FAILURE; + /* Return no error, nghttp2 will RST/GOAWAY by itself when needed */ + return 0; } static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, @@ -1273,7 +1316,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, DEBUGASSERT(CF_DATA_CURRENT(cf)); /* get the stream from the hash based on Stream ID */ - data_s = nghttp2_session_get_stream_user_data(session, stream_id); + data_s = cf_h2_get_stream_xfer(cf, stream_id, &stream); if(!data_s) { /* Receiving a Stream ID not in the hash should not happen - unless we have aborted a transfer artificially and there were more data @@ -1284,7 +1327,6 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, return 0; } - stream = H2_STREAM_CTX(ctx, data_s); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1301,14 +1343,13 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, struct Curl_cfilter *cf = userp; struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf); - struct h2_stream_ctx *stream; + struct h2_stream_ctx *stream = NULL; int rv; (void)session; DEBUGASSERT(call_data); /* stream id 0 is the connection, do not look there for streams. */ - data_s = stream_id ? - nghttp2_session_get_stream_user_data(session, stream_id) : NULL; + data_s = stream_id ? cf_h2_get_stream_xfer(cf, stream_id, &stream) : NULL; if(!data_s) { CURL_TRC_CF(call_data, cf, "[%d] on_stream_close, no easy set on stream", stream_id); @@ -1320,10 +1361,9 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, * trigger DONE or DETACH events as it must. */ CURL_TRC_CF(call_data, cf, "[%d] on_stream_close, not a GOOD easy on stream", stream_id); - (void)nghttp2_session_set_stream_user_data(session, stream_id, 0); + (void)nghttp2_session_set_stream_user_data(session, stream_id, NULL); return NGHTTP2_ERR_CALLBACK_FAILURE; } - stream = H2_STREAM_CTX(ctx, data_s); if(!stream) { CURL_TRC_CF(data_s, cf, "[%d] on_stream_close, GOOD easy but no stream", stream_id); @@ -1332,8 +1372,16 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, stream->closed = TRUE; stream->error = error_code; - if(stream->error) + if(stream->error) { stream->reset = TRUE; + if((stream->error == NGHTTP2_REFUSED_STREAM) && + ctx->rcvd_goaway && ctx->goaway_error) + /* REFUSED_STREAM is used by nghttp2 on a GOAWAY from the + * server where it indicated it will no longer process this stream. + * If the GOAWAY carried an error code, take that as the error for + * the stream. */ + stream->error = ctx->goaway_error; + } if(stream->error) CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %u)", @@ -1342,8 +1390,8 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, CURL_TRC_CF(data_s, cf, "[%d] CLOSED", stream_id); Curl_multi_mark_dirty(data_s); - /* remove `data_s` from the nghttp2 stream */ - rv = nghttp2_session_set_stream_user_data(session, stream_id, 0); + /* remove `stream_ctx` from the nghttp2 stream */ + rv = nghttp2_session_set_stream_user_data(session, stream_id, NULL); if(rv) { infof(data_s, "http/2: failed to clear user_data for stream %d", stream_id); @@ -1352,32 +1400,6 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, return 0; } -static int on_begin_headers(nghttp2_session *session, - const nghttp2_frame *frame, void *userp) -{ - struct Curl_cfilter *cf = userp; - struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream; - struct Curl_easy *data_s = NULL; - - (void)cf; - data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - if(!data_s) { - return 0; - } - - if(frame->hd.type != NGHTTP2_HEADERS) { - return 0; - } - - stream = H2_STREAM_CTX(ctx, data_s); - if(!stream || !stream->bodystarted) { - return 0; - } - - return 0; -} - static void cf_h2_header_error(struct Curl_cfilter *cf, struct Curl_easy *data, struct h2_stream_ctx *stream, @@ -1413,13 +1435,12 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ /* get the stream from the hash based on Stream ID */ - data = nghttp2_session_get_stream_user_data(session, stream_id); + data = cf_h2_get_stream_xfer(cf, stream_id, &stream); if(!GOOD_EASY_HANDLE(data)) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = H2_STREAM_CTX(ctx, data); if(!stream) { failf(data, "Internal NULL stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1440,7 +1461,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, /* no memory */ return NGHTTP2_ERR_CALLBACK_FAILURE; if(!curl_strequal(check, (const char *)value) && - ((data->state.origin->port != cf->conn->given->defport) || + ((data->state.origin->port != data->state.origin->scheme->defport) || !curl_strequal(data->state.origin->hostname, (const char *)value))) { /* This is push is not for the same authority that was asked for in * the URL. RFC 7540 section 8.2 says: "A client MUST treat a @@ -1582,7 +1603,6 @@ static ssize_t req_body_read_callback(nghttp2_session *session, void *userp) { struct Curl_cfilter *cf = userp; - struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s; struct h2_stream_ctx *stream = NULL; CURLcode result; @@ -1590,22 +1610,18 @@ static ssize_t req_body_read_callback(nghttp2_session *session, size_t n; (void)source; - (void)cf; + (void)session; if(!stream_id) return NGHTTP2_ERR_INVALID_ARGUMENT; /* get the stream from the hash based on Stream ID, stream ID zero is for connection-oriented stuff */ - data_s = nghttp2_session_get_stream_user_data(session, stream_id); - if(!data_s) + data_s = cf_h2_get_stream_xfer(cf, stream_id, &stream); + if(!data_s || !stream) /* Receiving a Stream ID not in the hash should not happen, this is an internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = H2_STREAM_CTX(ctx, data_s); - if(!stream) - return NGHTTP2_ERR_CALLBACK_FAILURE; - result = Curl_bufq_read(&stream->sendbuf, buf, length, &n); if(result) { if(result != CURLE_AGAIN) @@ -1740,6 +1756,9 @@ static CURLcode http2_handle_stream_close(struct Curl_cfilter *cf, curlx_dyn_ptr(&dbuf), curlx_dyn_len(&dbuf)); if(result) break; + result = Curl_bump_headersize(data, curlx_dyn_len(&dbuf), FALSE); + if(result) + return result; } curlx_dyn_free(&dbuf); if(result) @@ -1792,21 +1811,23 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); int rv = 0; - if(stream && stream->id > 0 && - (sweight_wanted(data) != sweight_in_effect(data))) { - /* send new weight and/or dependency */ - nghttp2_priority_spec pri_spec; - - h2_pri_spec(data, &pri_spec); - CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); - DEBUGASSERT(stream->id != -1); - rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, - stream->id, &pri_spec); - if(rv) - goto out; + if(sweight_wanted(data) != sweight_in_effect(data)) { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + if(stream && stream->id > 0 && + (sweight_wanted(data) != sweight_in_effect(data))) { + /* send new weight and/or dependency */ + nghttp2_priority_spec pri_spec; + + h2_pri_spec(data, &pri_spec); + CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); + DEBUGASSERT(stream->id != -1); + rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, &pri_spec); + if(rv) + goto out; + } } ctx->nw_out_blocked = 0; @@ -1867,7 +1888,6 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, size_t data_max_bytes) { struct cf_h2_ctx *ctx = cf->ctx; - struct h2_stream_ctx *stream; CURLcode result = CURLE_OK; size_t nread; @@ -1892,7 +1912,7 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, * it is time to stop due to connection close or us not processing * all network input */ while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { - stream = H2_STREAM_CTX(ctx, data); + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); if(stream && (stream->closed || !data_max_bytes)) { /* We would like to abort here and stop processing, so that the transfer * loop can handle the data/close here. This may leave data in @@ -2078,7 +2098,7 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream, uint32_t initial_win_size; *pnwritten = 0; - Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + Curl_dynhds_init(&h2_headers, MAX_HTTP_RESP_HEADER_COUNT, DYN_HTTP_REQUEST); result = http2_data_setup(cf, data, &stream); if(result) @@ -2131,11 +2151,11 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream, data_prd.read_callback = req_body_read_callback; data_prd.source.ptr = NULL; stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, - &data_prd, data); + &data_prd, stream); break; default: stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, - NULL, data); + NULL, stream); } if(stream_id < 0) { @@ -2238,7 +2258,7 @@ static CURLcode cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* 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) { + if(stream && stream->closed && !stream->resp_hds_complete) { infof(data, "stream %d closed", stream->id); result = CURLE_SEND_ERROR; goto out; @@ -2398,8 +2418,6 @@ static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, nghttp2_session_callbacks_set_on_data_chunk_recv_callback( cbs, on_data_chunk_recv); nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close); - nghttp2_session_callbacks_set_on_begin_headers_callback( - cbs, on_begin_headers); nghttp2_session_callbacks_set_on_header_callback(cbs, on_header); #ifdef CURLVERBOSE nghttp2_session_callbacks_set_error_callback(cbs, error_callback); @@ -2447,8 +2465,7 @@ static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, goto out; } - rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->id, - data); + rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->id, stream); if(rc) { infof(data, "http/2: failed to set user_data for stream %d", stream->id); diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index b16c2cd2aaa9..135d7a4d042c 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -186,10 +186,8 @@ static CURLcode split_to_dyn_array(const char *source, if(segment_length) { curlx_dyn_init(&db[index], segment_length + 1); result = curlx_dyn_addn(&db[index], &source[start], segment_length); - if(!result) { - if(++num_splits == MAX_QUERY_COMPONENTS) - result = CURLE_TOO_LARGE; - } + if(!result && ++num_splits == MAX_QUERY_COMPONENTS) + result = CURLE_TOO_LARGE; } fail: *num_splits_out = num_splits; @@ -266,13 +264,14 @@ static bool should_urlencode(struct Curl_str *service_name) { /* * These services require unmodified (not additionally URL-encoded) URL - * paths. - * should_urlencode == true is equivalent to should_urlencode_uri_path - * from the AWS SDK. Urls are already normalized by the curl URL parser + * paths. They are the S3 signing names, which have disableDoubleEncoding + * set in the AWS S3 endpoint rules. Urls are already normalized by the curl + * URL parser */ if(curlx_str_cmp(service_name, "s3") || - curlx_str_cmp(service_name, "s3-express") || - curlx_str_cmp(service_name, "s3-outposts")) { + curlx_str_cmp(service_name, "s3express") || + curlx_str_cmp(service_name, "s3-outposts") || + curlx_str_cmp(service_name, "s3-object-lambda")) { return FALSE; } return TRUE; @@ -523,10 +522,8 @@ static CURLcode make_headers(struct Curl_easy *data, if(tmp) *tmp = 0; - if(l != head) { - if(curlx_dyn_add(signed_headers, ";")) - goto fail; - } + if(l != head && curlx_dyn_add(signed_headers, ";")) + goto fail; if(curlx_dyn_add(signed_headers, l->data)) goto fail; } @@ -692,10 +689,8 @@ UNITTEST CURLcode canon_path(const char *q, size_t len, else result = curlx_dyn_addn(new_path, q, len); - if(!result) { - if(curlx_dyn_len(new_path) == 0) - result = curlx_dyn_add(new_path, "/"); - } + if(!result && curlx_dyn_len(new_path) == 0) + result = curlx_dyn_add(new_path, "/"); return result; } diff --git a/lib/http_chunks.c b/lib/http_chunks.c index 95929d27ba05..2a84ce57c358 100644 --- a/lib/http_chunks.c +++ b/lib/http_chunks.c @@ -282,6 +282,12 @@ static CURLcode httpchunk_readwrite(struct Curl_easy *data, ch->last_code = CHUNKE_PASSTHRU_ERROR; return result; } + result = Curl_bump_headersize(data, trlen, FALSE); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_TRAILER_LIMIT; + return result; + } } curlx_dyn_reset(&ch->trailer); ch->state = CHUNK_TRAILER_CR; @@ -385,6 +391,8 @@ static const char *Curl_chunked_strerror(CHUNKcode code) return "Bad content-encoding found"; case CHUNKE_OUT_OF_MEMORY: return "Out of memory"; + case CHUNKE_TRAILER_LIMIT: + return "trailers exceeded header limit"; } } diff --git a/lib/http_chunks.h b/lib/http_chunks.h index 0cb1f8834093..00a27cc807e9 100644 --- a/lib/http_chunks.h +++ b/lib/http_chunks.h @@ -33,10 +33,11 @@ struct connectdata; /* * The longest possible hexadecimal number we support in a chunked transfer. - * Neither RFC2616 nor the later HTTP specs define a maximum chunk size. - * For 64-bit curl_off_t we support 16 digits. For 32-bit, 8 digits. + * Neither RFC2616 nor the later HTTP specs define a maximum chunk size. With + * the 64-bit curl_off_t we support 16 digits, excluding leading zeroes. But + * why would a sane server prefix with zeroes? Let's accept one. */ -#define CHUNK_MAXNUM_LEN (SIZEOF_CURL_OFF_T * 2) +#define CHUNK_MAXNUM_LEN 17 typedef enum { /* await and buffer all hexadecimal digits until we get one that is not a @@ -88,7 +89,8 @@ typedef enum { CHUNKE_BAD_CHUNK, CHUNKE_BAD_ENCODING, CHUNKE_OUT_OF_MEMORY, - CHUNKE_PASSTHRU_ERROR /* Curl_httpchunk_read() returns a CURLcode to use */ + CHUNKE_PASSTHRU_ERROR, /* Curl_httpchunk_read() returns a CURLcode to use */ + CHUNKE_TRAILER_LIMIT /* Max header limit exceeded by trailers */ } CHUNKcode; struct Curl_chunker { diff --git a/lib/http_digest.c b/lib/http_digest.c index 1ec29bd391a1..e739b2be801c 100644 --- a/lib/http_digest.c +++ b/lib/http_digest.c @@ -41,9 +41,10 @@ static void digest_flush_stale(struct Curl_easy *data, struct Curl_creds *creds) { bool flush = FALSE; - if(digest->origin && !Curl_peer_same_destination(peer, digest->origin)) { + if(!digest->origin || !Curl_peer_equal(peer, digest->origin)) { CURL_TRC_M(data, "http_digest, reset on peer change to %s:%u", - peer->hostname, peer->port); + peer ? peer->hostname : "-", + (unsigned int)(peer ? peer->port : 0)); flush = TRUE; } else if(digest->creds && !Curl_creds_same(creds, digest->creds)) { diff --git a/lib/http_httpsig.c b/lib/http_httpsig.c index 37f23a09cbe5..804018081e32 100644 --- a/lib/http_httpsig.c +++ b/lib/http_httpsig.c @@ -157,7 +157,7 @@ static CURLcode httpsig_authority(struct Curl_easy *data, const char *hostname = conn->origin->hostname; uint16_t port = conn->origin->port; - if((conn->given->defport != port) && port) + if((conn->origin->scheme->defport != port) && port) return curlx_dyn_addf(authority_buf, "%s:%u", hostname, port); return curlx_dyn_add(authority_buf, hostname); } diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c index 5e8798134f55..66c7af47f810 100644 --- a/lib/http_negotiate.c +++ b/lib/http_negotiate.c @@ -39,9 +39,9 @@ static void http_auth_nego_reset(struct connectdata *conn, bool proxy) { if(proxy) - conn->proxy_negotiate_state = GSS_AUTHNONE; + conn->proxy_negotiate_state = CURL_NEGO_AUTH_NONE; else { - conn->http_negotiate_state = GSS_AUTHNONE; + conn->http_negotiate_state = CURL_NEGO_AUTH_NONE; Curl_peer_unlink(&conn->creds_origin); Curl_creds_unlink(&conn->creds); } @@ -61,7 +61,7 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, /* Point to the correct struct with this */ struct negotiatedata *neg_ctx; - curlnegotiate state; + uint8_t state; if(proxy) { #ifndef CURL_DISABLE_PROXY @@ -89,11 +89,11 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, len = strlen(header); neg_ctx->havenegdata = len != 0; if(!len) { - if(state == GSS_AUTHSUCC) { + if(state == CURL_NEGO_AUTH_SUCC) { infof(data, "Negotiate auth restarted"); http_auth_nego_reset(conn, neg_ctx, proxy); } - else if(state != GSS_AUTHNONE) { + else if(state != CURL_NEGO_AUTH_NONE) { /* The server rejected our authentication and has not supplied any more negotiation mechanisms */ http_auth_nego_reset(conn, neg_ctx, proxy); @@ -157,7 +157,7 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, { struct negotiatedata *neg_ctx; struct auth *authp; - curlnegotiate *state; + uint8_t *state; char *base64 = NULL; size_t len = 0; char *userp; @@ -181,21 +181,21 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, authp->done = FALSE; - if(*state == GSS_AUTHRECV) { + if(*state == CURL_NEGO_AUTH_RECV) { if(neg_ctx->havenegdata) { neg_ctx->havemultiplerequests = TRUE; } } - else if(*state == GSS_AUTHSUCC) { + else if(*state == CURL_NEGO_AUTH_SUCC) { if(!neg_ctx->havenoauthpersist) { neg_ctx->noauthpersist = !neg_ctx->havemultiplerequests; } } if(neg_ctx->noauthpersist || - (*state != GSS_AUTHDONE && *state != GSS_AUTHSUCC)) { + (*state != CURL_NEGO_AUTH_DONE && *state != CURL_NEGO_AUTH_SUCC)) { - if(neg_ctx->noauthpersist && *state == GSS_AUTHSUCC) { + if(neg_ctx->noauthpersist && *state == CURL_NEGO_AUTH_SUCC) { infof(data, "Curl_output_negotiate, " "no persistent authentication: cleanup existing context"); http_auth_nego_reset(conn, neg_ctx, proxy); @@ -236,23 +236,23 @@ CURLcode Curl_output_negotiate(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } - *state = GSS_AUTHSENT; + *state = CURL_NEGO_AUTH_SENT; #ifdef HAVE_GSSAPI if(neg_ctx->status == GSS_S_COMPLETE || neg_ctx->status == GSS_S_CONTINUE_NEEDED) { - *state = GSS_AUTHDONE; + *state = CURL_NEGO_AUTH_DONE; } #else #ifdef USE_WINDOWS_SSPI if(neg_ctx->status == SEC_E_OK || neg_ctx->status == SEC_I_CONTINUE_NEEDED) { - *state = GSS_AUTHDONE; + *state = CURL_NEGO_AUTH_DONE; } #endif #endif } - if(*state == GSS_AUTHDONE || *state == GSS_AUTHSUCC) { + if(*state == CURL_NEGO_AUTH_DONE || *state == CURL_NEGO_AUTH_SUCC) { /* connection is already authenticated, * do not send a header in future requests */ authp->done = TRUE; diff --git a/lib/http_proxy.c b/lib/http_proxy.c index 7774589ecd64..ad79a477b74b 100644 --- a/lib/http_proxy.c +++ b/lib/http_proxy.c @@ -132,6 +132,8 @@ static CURLcode dynhds_add_custom(struct Curl_easy *data, /* trim surrounding whitespace so a padded field name (e.g. `Authorization :`) cannot slip past the Authorization/Cookie check */ curlx_str_trimblanks(&name); + if(!curlx_strlen(&name)) + continue; if(data->state.http_host && /* a Host: header was sent already, do not pass on any custom Host: header as that will produce *two* in the same request! */ @@ -197,6 +199,7 @@ static int proxy_http_ver_major(proxy_http_ver ver) static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, proxy_http_ver ver) { @@ -206,6 +209,7 @@ static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, CURLcode result; struct httpreq *req = NULL; + (void)peer; authority = curl_maprintf("%s%s%s:%u", dest->ipv6 ? "[" : "", dest->hostname, @@ -216,7 +220,7 @@ static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, goto out; } - result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"), + result = Curl_http_req_make(&req, STRCONST("CONNECT"), NULL, 0, authority, strlen(authority), NULL, 0); if(result) @@ -274,17 +278,16 @@ static CURLcode http_proxy_create_CONNECT(struct httpreq **preq, static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, struct Curl_cfilter *cf, struct Curl_easy *data, + struct Curl_peer *peer, struct Curl_peer *dest, proxy_http_ver ver) { const char *proxy_scheme = "http", *ua; - const char *proxy_host = cf->conn->http_proxy.peer->hostname; int httpversion = proxy_http_ver_major(ver); char *authority = NULL; char *path = NULL; char *encoded_host = NULL; struct httpreq *req = NULL; - bool proxy_ipv6_ip; CURLcode result; if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS || @@ -292,13 +295,11 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS3) proxy_scheme = "https"; - proxy_ipv6_ip = cf->conn->http_proxy.peer->ipv6 != 0; - authority = curl_maprintf("%s%s%s:%d", - proxy_ipv6_ip ? "[" : "", - proxy_host, - proxy_ipv6_ip ? "]" : "", - cf->conn->http_proxy.peer->port); + peer->ipv6 ? "[" : "", + peer->hostname, + peer->ipv6 ? "]" : "", + peer->port); if(!authority) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -349,7 +350,7 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, goto out; } else if(ver == PROXY_HTTP_V2 || ver == PROXY_HTTP_V3) { - result = Curl_http_req_make(&req, "CONNECT", CURL_CSTRLEN("CONNECT"), + result = Curl_http_req_make(&req, STRCONST("CONNECT"), proxy_scheme, strlen(proxy_scheme), authority, strlen(authority), path, strlen(path)); @@ -440,28 +441,20 @@ static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq, CURLcode Curl_http_proxy_create_tunnel_request( struct httpreq **preq, struct Curl_cfilter *cf, - struct Curl_easy *data, struct Curl_peer *dest, + struct Curl_easy *data, struct Curl_peer *peer, struct Curl_peer *dest, proxy_http_ver ver, bool udp_tunnel) { - CURLcode result; - - if(udp_tunnel) - result = http_proxy_create_CONNECTUDP(preq, cf, data, dest, ver); - else - result = http_proxy_create_CONNECT(preq, cf, data, dest, ver); + CURLcode result = udp_tunnel ? + http_proxy_create_CONNECTUDP(preq, cf, data, peer, dest, ver) : + http_proxy_create_CONNECT(preq, cf, data, peer, dest, ver); if(result) return result; - if(udp_tunnel) - infof(data, "Establishing %s proxy UDP tunnel to %s:%u", - (ver == PROXY_HTTP_V2) ? "HTTP/2" : - (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP", - dest->user_hostname, dest->port); - else - infof(data, "Establishing %s proxy tunnel to %s", - (ver == PROXY_HTTP_V2) ? "HTTP/2" : - (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP", - (*preq)->authority); + infof(data, "Establishing %s proxy %stunnel to %s:%u", + (ver == PROXY_HTTP_V2) ? "HTTP/2" : + (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP", + udp_tunnel ? "UDP " : "", + dest->user_hostname, dest->port); return CURLE_OK; } @@ -478,10 +471,8 @@ CURLcode Curl_http_proxy_inspect_tunnel_response( DEBUGASSERT(resp); header_count = Curl_dynhds_count(&resp->headers); - if(udp_tunnel) - infof(data, "CONNECT-UDP Response Status %d", resp->status); - else - infof(data, "CONNECT Response Status %d", resp->status); + infof(data, "CONNECT%s Response Status %d", + udp_tunnel ? "-UDP" : "", resp->status); infof(data, "Response Headers (%zu total):", header_count); for(i = 0; i < header_count; i++) { struct dynhds_entry *entry = Curl_dynhds_getn(&resp->headers, i); @@ -489,10 +480,7 @@ CURLcode Curl_http_proxy_inspect_tunnel_response( infof(data, " %s: %s", entry->name, entry->value); } - if(resp->status == 401) { - auth_reply = Curl_dynhds_cget(&resp->headers, "WWW-Authenticate"); - } - else if(resp->status == 407) { + if(resp->status == 407) { auth_reply = Curl_dynhds_cget(&resp->headers, "Proxy-Authenticate"); } @@ -561,14 +549,13 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, struct cf_proxy_ctx *ctx = cf->ctx; CURLcode result; bool udp_tunnel = TRNSPRT_IS_DGRAM(ctx->tunnel_transport); - const char *tunnel_type = udp_tunnel ? "CONNECT-UDP" : "CONNECT"; if(cf->connected) { *done = TRUE; return CURLE_OK; } - CURL_TRC_CF(data, cf, "%s", tunnel_type); + CURL_TRC_CF(data, cf, "CONNECT%s", udp_tunnel ? "-UDP" : ""); connect_sub: /* in case of h3_proxy, cf->next will be NULL initially */ if(cf->next) { @@ -587,10 +574,11 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, } if(alpn) - infof(data, "%s: '%s' negotiated", tunnel_type, alpn); + infof(data, "CONNECT%s: '%s' negotiated", + udp_tunnel ? "-UDP" : "", alpn); else if(!alpn) { /* No ALPN, proxytype rules. Fake ALPN */ - infof(data, "%s: no ALPN negotiated", tunnel_type); + infof(data, "CONNECT%s: no ALPN negotiated", udp_tunnel ? "-UDP" : ""); switch(ctx->proxytype) { case CURLPROXY_HTTP_1_0: alpn = "http/1.0"; @@ -609,8 +597,8 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, if(!strcmp(alpn, "http/1.0")) { CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.0"); - result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer, 10, - udp_tunnel); + result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->peer, + ctx->tunnel_peer, 10, udp_tunnel); if(result) goto out; } @@ -618,7 +606,8 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, int httpversion = (ctx->proxytype == CURLPROXY_HTTP_1_0) ? 10 : 11; CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.%d", httpversion % 10); - result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->tunnel_peer, + result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->peer, + ctx->tunnel_peer, httpversion, udp_tunnel); if(result) goto out; @@ -626,8 +615,8 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, #ifdef USE_NGHTTP2 else if(!strcmp(alpn, "h2")) { CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2"); - result = Curl_cf_h2_proxy_insert_after(cf, data, ctx->tunnel_peer, - udp_tunnel); + result = Curl_cf_h2_proxy_insert_after(cf, data, ctx->peer, + ctx->tunnel_peer, udp_tunnel); if(result) goto out; } @@ -644,7 +633,8 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf, } #endif /* USE_PROXY_HTTP3 && USE_NGHTTP3 && USE_NGTCP2 && USE_OPENSSL */ else { - failf(data, "%s: negotiated ALPN '%s' not supported", tunnel_type, alpn); + failf(data, "CONNECT%s: negotiated ALPN '%s' not supported", + udp_tunnel ? "-UDP" : "", alpn); result = CURLE_COULDNT_CONNECT; goto out; } diff --git a/lib/http_proxy.h b/lib/http_proxy.h index 86c908809303..7f5fc9444050 100644 --- a/lib/http_proxy.h +++ b/lib/http_proxy.h @@ -53,7 +53,7 @@ typedef enum { /* Create CONNECT or CONNECT-UDP request */ CURLcode Curl_http_proxy_create_tunnel_request( struct httpreq **preq, struct Curl_cfilter *cf, - struct Curl_easy *data, struct Curl_peer *dest, + struct Curl_easy *data, struct Curl_peer *peer, struct Curl_peer *dest, proxy_http_ver ver, bool udp_tunnel); /* Inspect tunnel response for H2/H3 proxy (capsule-protocol, auth) */ diff --git a/lib/if2ip.c b/lib/if2ip.c index 9a9fbe7691d3..fee49ca3648b 100644 --- a/lib/if2ip.c +++ b/lib/if2ip.c @@ -110,7 +110,7 @@ if2ip_result_t Curl_if2ip(int af, for(iface = head; iface; iface = iface->ifa_next) { if(iface->ifa_addr) { if(iface->ifa_addr->sa_family == af) { - if(curl_strequal(iface->ifa_name, interf)) { + if(!strcmp(iface->ifa_name, interf)) { void *addr; char scope[12] = ""; char ipstr[64]; @@ -161,7 +161,7 @@ if2ip_result_t Curl_if2ip(int af, } } else if((res == IF2IP_NOT_FOUND) && - curl_strequal(iface->ifa_name, interf)) { + !strcmp(iface->ifa_name, interf)) { res = IF2IP_AF_NOT_SUPPORTED; } } diff --git a/lib/imap.c b/lib/imap.c index 62ab0a283bd1..036bf91951d8 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -877,11 +877,8 @@ static CURLcode imap_perform_append(struct Curl_easy *data, result = Curl_mime_prepare_headers(data, postp, NULL, NULL, MIMESTRATEGY_MAIL); - if(!result) - if(!Curl_checkheaders(data, STRCONST("Mime-Version"))) - result = Curl_mime_add_header(&postp->curlheaders, - "Mime-Version: 1.0"); - + if(!result && !Curl_checkheaders(data, STRCONST("Mime-Version"))) + result = Curl_mime_add_header(&postp->curlheaders, "Mime-Version: 1.0"); if(!result) result = Curl_creader_set_mime(data, postp); if(result) @@ -931,12 +928,11 @@ static CURLcode imap_perform_append(struct Curl_easy *data, } for(i = 0; ulflag[i].bit; i++) { - if(data->set.upload_flags & ulflag[i].bit) { - if((curlx_dyn_len(&flags) > 2 && curlx_dyn_add(&flags, " ")) || - curlx_dyn_add(&flags, "\\") || - curlx_dyn_add(&flags, ulflag[i].flag)) - goto cleanup; - } + if(data->set.upload_flags & ulflag[i].bit && + ((curlx_dyn_len(&flags) > 2 && curlx_dyn_add(&flags, " ")) || + curlx_dyn_add(&flags, "\\") || + curlx_dyn_add(&flags, ulflag[i].flag))) + goto cleanup; } if(curlx_dyn_add(&flags, ")")) @@ -1210,11 +1206,10 @@ static bool is_custom_fetch_listing(struct IMAP *imap) const char *p = imap->custom_params; return is_custom_fetch_listing_match(p); } - else if(curl_strequal(imap->custom, "UID") && imap->custom_params) { - if(curl_strnequal(imap->custom_params, " FETCH ", 7)) { - const char *p = imap->custom_params + 6; - return is_custom_fetch_listing_match(p); - } + else if(curl_strequal(imap->custom, "UID") && imap->custom_params && + curl_strnequal(imap->custom_params, " FETCH ", 7)) { + const char *p = imap->custom_params + 6; + return is_custom_fetch_listing_match(p); } return FALSE; } @@ -1276,7 +1271,7 @@ static CURLcode imap_state_listsearch_resp(struct Curl_easy *data, pp->nfinal = 0; /* done */ /* Limit chunk to the literal size */ - if(chunk > (size_t)size) + if((curl_off_t)chunk > size) chunk = (size_t)size; if(chunk) { @@ -1444,7 +1439,7 @@ static CURLcode imap_state_fetch_resp(struct Curl_easy *data, curlx_dyn_tail(&pp->recvbuf, chunk); pp->nfinal = 0; /* done */ - if(chunk > (size_t)size) + if((curl_off_t)chunk > size) /* The conversion from curl_off_t to size_t is always fine here */ chunk = (size_t)size; @@ -2007,15 +2002,13 @@ static CURLcode imap_done(struct Curl_easy *data, CURLcode status, struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); - (void)premature; - if(!imapc) return CURLE_FAILED_INIT; if(!imap) return CURLE_OK; - if(status) { - CURL_TRC_M(data, "IMAP done with bad status"); + if(status || premature) { + CURL_TRC_M(data, "IMAP done, not finalizing transfer"); connclose(conn); /* marked for closure */ result = status; /* use the already set error code */ } @@ -2076,7 +2069,9 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected, /* Determine if the requested mailbox (with the same UIDVALIDITY if set) has already been selected on this connection */ if(imap->mailbox && imapc->mailbox && - curl_strequal(imap->mailbox, imapc->mailbox) && + (!strcmp(imap->mailbox, imapc->mailbox) || + (curl_strequal(imap->mailbox, "INBOX") && + curl_strequal(imapc->mailbox, "INBOX"))) && (!imap->uidvalidity_set || !imapc->mb_uidvalidity_set || (imap->uidvalidity == imapc->mb_uidvalidity))) selected = TRUE; @@ -2206,17 +2201,16 @@ static CURLcode imap_disconnect(struct Curl_easy *data, { struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); - if(imapc) { - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way (pingpong has pending data to send), - sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to. */ - if(!dead_connection && conn->bits.protoconnstart && - !Curl_pp_needs_flush(data, &imapc->pp)) { - if(!imap_perform_logout(data, imapc)) - (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */ - } - } + if(imapc && + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way (pingpong has pending data to send), + sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. */ + !dead_connection && conn->bits.protoconnstart && + !Curl_pp_needs_flush(data, &imapc->pp) && + !imap_perform_logout(data, imapc)) + (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */ + return CURLE_OK; } @@ -2241,7 +2235,7 @@ static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) return result; } -static void imap_easy_dtor(void *key, size_t klen, void *entry) +static void imap_easy_dtor(const void *key, size_t klen, void *entry) { struct IMAP *imap = entry; (void)key; @@ -2250,7 +2244,7 @@ static void imap_easy_dtor(void *key, size_t klen, void *entry) curlx_free(imap); } -static void imap_conn_dtor(void *key, size_t klen, void *entry) +static void imap_conn_dtor(const void *key, size_t klen, void *entry) { struct imap_conn *imapc = entry; (void)key; diff --git a/lib/ldap.c b/lib/ldap.c index 2676f732bb1a..10f9d6ffb1ac 100644 --- a/lib/ldap.c +++ b/lib/ldap.c @@ -230,27 +230,30 @@ static ULONG ldap_win_bind(struct Curl_easy *data, LDAP *server, } #endif /* USE_WIN32_LDAP */ +/* RFC 2849: a string that is not a SAFE-STRING (leading ':' or '<', leading or + trailing space, or any non-printable byte such as CR/LF) cannot go into LDIF + as-is. DNs and values get base64-encoded instead; an attribute type has no + base64 form in LDIF and is rejected by the caller. */ +static bool ldap_str_needs_base64(const char *s, size_t len) +{ + size_t i; + if(!len) + return FALSE; + if((s[0] == ':') || (s[0] == '<') || ISBLANK(s[0]) || ISBLANK(s[len - 1])) + return TRUE; + for(i = 0; i < len; i++) + if(!ISPRINT(s[i])) + return TRUE; + return FALSE; +} + static bool ldap_value_needs_base64(const char *attr, size_t attr_len, const BerValue *val) { - ber_len_t j; - if((attr_len > 7) && curl_strequal(";binary", attr + attr_len - 7)) return TRUE; - /* check for a leading ':' or '<' (not a SAFE-INIT-CHAR per RFC 2849) or - leading or trailing whitespace */ - if(val->bv_len && ((val->bv_val[0] == ':') || (val->bv_val[0] == '<') || - ISBLANK(val->bv_val[0]) || - ISBLANK(val->bv_val[val->bv_len - 1]))) - return TRUE; - - /* check for unprintable characters */ - for(j = 0; j < val->bv_len; j++) - if(!ISPRINT(val->bv_val[j])) - return TRUE; - - return FALSE; + return ldap_str_needs_base64(val->bv_val, val->bv_len); } #ifdef USE_WIN32_LDAP @@ -271,6 +274,14 @@ static CURLcode show_vals(struct Curl_easy *data, BerValue **vals, int i; size_t attr_len = strlen(attr); + /* An attribute type has no base64 form in LDIF, so a type carrying control + bytes (CR/LF etc.) would let a hostile server inject lines. A conformant + type name never contains them, so reject it. */ + if(ldap_str_needs_base64(attr, attr_len)) { + failf(data, "LDAP local: attribute type contains illegal bytes"); + return CURLE_WEIRD_SERVER_REPLY; + } + for(i = 0; vals[i] && !result; i++) { result = Curl_client_write(data, CLIENTWRITE_BODY, "\t", 1); if(!result) @@ -408,14 +419,13 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) ldap_set_option(server, LDAP_OPT_SERVER_CERTIFICATE, (void *)(uintptr_t)bypass_cert_verify); } -#else /* !USE_WIN32_LDAP */ +#elif defined(LDAP_OPT_X_TLS) /* !USE_WIN32_LDAP */ int ldap_option; const char *ldap_ca = conn->ssl_config.CAfile; -#ifdef LDAP_OPT_X_TLS if(conn->ssl_config.verifypeer) { /* OpenLDAP SDK supports BASE64 files. */ - if(data->set.ssl.primary.cert_type && - !curl_strequal(data->set.ssl.primary.cert_type, "PEM")) { + if(conn->ssl_config.cert_type && + !curl_strequal(conn->ssl_config.cert_type, "PEM")) { failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type"); result = CURLE_SSL_CERTPROBLEM; goto quit; @@ -453,17 +463,14 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) result = CURLE_SSL_CERTPROBLEM; goto quit; } -#else /* !LDAP_OPT_X_TLS */ - (void)ldap_option; - (void)ldap_ca; +#else /* !USE_WIN32_LDAP && !LDAP_OPT_X_TLS */ /* we should probably never come up to here since configure should check in first place if we can support LDAP SSL/TLS */ failf(data, "LDAP local: SSL/TLS not supported with this version " "of the OpenLDAP toolkit"); result = CURLE_SSL_CERTPROBLEM; goto quit; -#endif /* LDAP_OPT_X_TLS */ -#endif /* USE_WIN32_LDAP */ +#endif #endif /* HAVE_LDAP_SSL */ } else if(data->set.use_ssl > CURLUSESSL_TRY) { @@ -534,12 +541,28 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done) result = dn ? CURLE_OUT_OF_MEMORY : CURLE_FAILED_INIT; else { name_len = strlen(name); - result = Curl_client_write(data, CLIENTWRITE_BODY, "DN: ", 4); + if(ldap_str_needs_base64(name, name_len)) { + char *dn_b64 = NULL; + size_t dn_b64_sz = 0; + + /* A DN with e.g. CR/LF would inject LDIF lines, so encode it */ + result = curlx_base64_encode((uint8_t *)name, name_len, + &dn_b64, &dn_b64_sz); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, "DN:: ", 5); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, dn_b64, + dn_b64_sz); + curlx_free(dn_b64); + } + else { + result = Curl_client_write(data, CLIENTWRITE_BODY, "DN: ", 4); + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, name, name_len); + } + if(!result) + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); } - if(!result) - result = Curl_client_write(data, CLIENTWRITE_BODY, name, name_len); - if(!result) - result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); FREE_ON_WINLDAP(name); ldap_memfree(dn); if(result) diff --git a/lib/llist.c b/lib/llist.c index 6528ebaef2c3..516b15673b9c 100644 --- a/lib/llist.c +++ b/lib/llist.c @@ -54,7 +54,7 @@ void Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor) } /* - * Curl_llist_insert_next() + * llist_insert_next() * * Inserts a new list element after the given one 'e'. If the given existing * entry is NULL and the list already has elements, the new one will be @@ -64,10 +64,13 @@ void Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor) * * @unittest 1300 */ -void Curl_llist_insert_next(struct Curl_llist *list, - struct Curl_llist_node *e, /* may be NULL */ - const void *p, - struct Curl_llist_node *ne) +UNITTEST void llist_insert_next(struct Curl_llist *list, + struct Curl_llist_node *e, const void *p, + struct Curl_llist_node *ne); +UNITTEST void llist_insert_next(struct Curl_llist *list, + struct Curl_llist_node *e, /* may be NULL */ + const void *p, + struct Curl_llist_node *ne) { DEBUGASSERT(list); DEBUGASSERT(list->_init == LLISTINIT); @@ -120,7 +123,7 @@ void Curl_llist_append(struct Curl_llist *list, const void *p, DEBUGASSERT(list); DEBUGASSERT(list->_init == LLISTINIT); DEBUGASSERT(ne); - Curl_llist_insert_next(list, list->_tail, p, ne); + llist_insert_next(list, list->_tail, p, ne); } void *Curl_node_take_elem(struct Curl_llist_node *e) diff --git a/lib/llist.h b/lib/llist.h index de4adc972f47..38fea5c208ef 100644 --- a/lib/llist.h +++ b/lib/llist.h @@ -51,8 +51,6 @@ struct Curl_llist_node { }; void Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor); -void Curl_llist_insert_next(struct Curl_llist *list, struct Curl_llist_node *e, - const void *p, struct Curl_llist_node *ne); void Curl_llist_append(struct Curl_llist *list, const void *p, struct Curl_llist_node *ne); void Curl_node_remove(struct Curl_llist_node *e); diff --git a/lib/md4.c b/lib/md4.c index e030ffac30c2..606c501c6f24 100644 --- a/lib/md4.c +++ b/lib/md4.c @@ -42,16 +42,35 @@ #include #endif -/* When OpenSSL or wolfSSL is available, we use their MD4 functions. */ - #if defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4) -#include +#include + +typedef EVP_MD_CTX *my_md4_ctx; + +static int my_md4_init(my_md4_ctx *ctx) +{ + *ctx = EVP_MD_CTX_new(); + if(!*ctx) + return 0; + + if(!EVP_DigestInit_ex(*ctx, EVP_md4(), NULL)) { + EVP_MD_CTX_free(*ctx); + return 0; + } + return 1; +} -/* 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 +static void my_md4_update(my_md4_ctx *ctx, + const unsigned char *input, unsigned int len) +{ + (void)EVP_DigestUpdate(*ctx, input, len); +} + +static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx) +{ + (void)EVP_DigestFinal_ex(*ctx, digest, NULL); + EVP_MD_CTX_free(*ctx); +} #elif defined(USE_WOLFSSL) && !defined(NO_MD4) #include @@ -81,20 +100,20 @@ static void my_md4_final(unsigned char *digest, my_md4_ctx *ctx) } #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ - (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 1040 && \ defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ - (__MAC_OS_X_VERSION_MIN_REQUIRED < 101500)) || \ + __MAC_OS_X_VERSION_MIN_REQUIRED < 101500) || \ (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - (__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 20000 && \ defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ - (__IPHONE_OS_VERSION_MIN_REQUIRED < 130000)) + __IPHONE_OS_VERSION_MIN_REQUIRED < 130000) #include typedef CC_MD4_CTX my_md4_ctx; static int my_md4_init(my_md4_ctx *ctx) { - return CC_MD4_Init(ctx); + return CC_MD4_Init(ctx); /* always returns 1 */ } static void my_md4_update(my_md4_ctx *ctx, diff --git a/lib/md5.c b/lib/md5.c index f842c7ae63b4..bf47b6c4a8a9 100644 --- a/lib/md5.c +++ b/lib/md5.c @@ -72,29 +72,39 @@ static void my_md5_final(unsigned char *digest, void *ctx) #endif } -#elif defined(USE_OPENSSL) && \ - !defined(OPENSSL_NO_MD5) && !defined(OPENSSL_NO_DEPRECATED_3_0) -#include +#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD5) +#include -typedef MD5_CTX my_md5_ctx; +typedef EVP_MD_CTX *my_md5_ctx; -static CURLcode my_md5_init(void *ctx) +static CURLcode my_md5_init(void *in) { - if(!MD5_Init(ctx)) + EVP_MD_CTX **ctx = (EVP_MD_CTX **)in; + *ctx = EVP_MD_CTX_new(); + if(!*ctx) return CURLE_OUT_OF_MEMORY; + if(!EVP_DigestInit_ex(*ctx, EVP_md5(), NULL)) { + EVP_MD_CTX_free(*ctx); + *ctx = NULL; + return CURLE_FAILED_INIT; + } return CURLE_OK; } -static void my_md5_update(void *ctx, +static void my_md5_update(void *in, const unsigned char *input, unsigned int len) { - (void)MD5_Update(ctx, input, len); + EVP_MD_CTX **ctx = (EVP_MD_CTX **)in; + (void)EVP_DigestUpdate(*ctx, input, len); } -static void my_md5_final(unsigned char *digest, void *ctx) +static void my_md5_final(unsigned char *digest, void *in) { - (void)MD5_Final(digest, ctx); + EVP_MD_CTX **ctx = (EVP_MD_CTX **)in; + (void)EVP_DigestFinal_ex(*ctx, digest, NULL); + EVP_MD_CTX_free(*ctx); + *ctx = NULL; } #elif defined(USE_WOLFSSL) && !defined(NO_MD5) @@ -148,13 +158,13 @@ static void my_md5_final(unsigned char *digest, void *ctx) } #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ - (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 1040 && \ defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ - (__MAC_OS_X_VERSION_MIN_REQUIRED < 101500)) || \ + __MAC_OS_X_VERSION_MIN_REQUIRED < 101500) || \ (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - (__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 20000 && \ defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ - (__IPHONE_OS_VERSION_MIN_REQUIRED < 130000)) + __IPHONE_OS_VERSION_MIN_REQUIRED < 130000) #include /* For Apple operating systems: CommonCrypto has the functions we need. @@ -167,9 +177,7 @@ static void my_md5_final(unsigned char *digest, void *ctx) static CURLcode my_md5_init(void *ctx) { - if(!CC_MD5_Init(ctx)) - return CURLE_OUT_OF_MEMORY; - + (void)CC_MD5_Init(ctx); /* always returns 1 */ return CURLE_OK; } diff --git a/lib/memdebug.c b/lib/memdebug.c index b6af2c6d3528..53c85291e09c 100644 --- a/lib/memdebug.c +++ b/lib/memdebug.c @@ -29,7 +29,7 @@ #include "urldata.h" #include "curl_threads.h" -#include "curlx/fopen.h" /* for CURLX_FOPEN_LOW(), CURLX_FREOPEN_LOW() */ +#include "curlx/win32-fopen.h" /* for CURLX_FOPEN_*() */ #ifdef USE_BACKTRACE #include diff --git a/lib/mime.c b/lib/mime.c index 600437576581..f491f22f95ac 100644 --- a/lib/mime.c +++ b/lib/mime.c @@ -33,7 +33,7 @@ struct Curl_easy; #include "curlx/strdup.h" #include "curlx/basename.h" #include "curlx/strcopy.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/base64.h" #if !defined(CURL_DISABLE_MIME) && (!defined(CURL_DISABLE_HTTP) || \ @@ -50,8 +50,7 @@ struct Curl_easy; #define STOP_FILLING ((size_t)-2) static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, - void *instream, bool *hasread, - size_t call_depth); + void *instream, bool *hasread); static curl_off_t mime_size(curl_mimepart *part); /* Quoted-printable character class table. @@ -701,14 +700,10 @@ static size_t readback_bytes(struct mime_state *state, /* Read a non-encoded part content. */ static size_t read_part_content(curl_mimepart *part, char *buffer, - size_t bufsize, bool *hasread, - size_t call_depth) + size_t bufsize, bool *hasread) { size_t sz = 0; - if(++call_depth > MAX_MIME_LEVELS) - return READ_ERROR; - switch(part->lastreadstatus) { case 0: case CURL_READFUNC_ABORT: @@ -731,8 +726,7 @@ static size_t read_part_content(curl_mimepart *part, char *buffer, * 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, - call_depth); + sz = mime_subparts_read(buffer, 1, bufsize, part->arg, hasread); break; case MIMEKIND_FILE: if(part->fp && feof(part->fp)) @@ -771,17 +765,13 @@ static size_t read_part_content(curl_mimepart *part, char *buffer, /* Read and encode part content. */ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, - size_t bufsize, bool *hasread, - size_t call_depth) + size_t bufsize, bool *hasread) { 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. */ @@ -814,7 +804,7 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, 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, call_depth); + sizeof(st->buf) - st->bufend, hasread); switch(sz) { case 0: ateof = TRUE; @@ -835,14 +825,10 @@ static size_t read_encoded_part_content(curl_mimepart *part, char *buffer, /* Readback a mime part. */ static size_t readback_part(curl_mimepart *part, - char *buffer, size_t bufsize, bool *hasread, - size_t call_depth) + char *buffer, size_t bufsize, bool *hasread) { size_t cursize = 0; - if(++call_depth > MAX_MIME_LEVELS) - return READ_ERROR; - /* Readback from part. */ while(bufsize) { size_t sz = 0; @@ -886,10 +872,9 @@ static size_t readback_part(curl_mimepart *part, break; case MIMESTATE_CONTENT: if(part->encoder) - sz = read_encoded_part_content(part, buffer, bufsize, hasread, - call_depth); + sz = read_encoded_part_content(part, buffer, bufsize, hasread); else - sz = read_part_content(part, buffer, bufsize, hasread, call_depth); + sz = read_part_content(part, buffer, bufsize, hasread); switch(sz) { case 0: mimesetstate(&part->state, MIMESTATE_END, NULL); @@ -923,16 +908,12 @@ static size_t readback_part(curl_mimepart *part, /* 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, - size_t call_depth) + void *instream, bool *hasread) { 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; @@ -967,7 +948,7 @@ static size_t mime_subparts_read(char *buffer, size_t size, size_t nitems, mimesetstate(&mime->state, MIMESTATE_END, NULL); break; } - sz = readback_part(part, buffer, nitems, hasread, call_depth); + sz = readback_part(part, buffer, nitems, hasread); switch(sz) { case CURL_READFUNC_ABORT: case CURL_READFUNC_PAUSE: @@ -1109,25 +1090,62 @@ void Curl_mime_cleanpart(curl_mimepart *part) } } -/* Recursively delete a mime handle and its parts. */ +/* Non-recursively delete a mime handle and its parts. */ void curl_mime_free(curl_mime *mime) { curl_mimepart *part; - if(mime) { - mime_subparts_unbind(mime); /* Be sure it is not referenced anymore. */ - while(mime->firstpart) { - part = mime->firstpart; + if(!mime) + return; + + mime_subparts_unbind(mime); /* Be sure it is not referenced anymore. */ + + while(mime) { + part = mime->firstpart; + if(part) { mime->firstpart = part->nextpart; + if(part->kind == MIMEKIND_MULTIPART && part->arg && + part->freefunc == mime_subparts_free) { + curl_mime *subparts = (curl_mime *)part->arg; + part->freefunc = NULL; + cleanup_part_content(part); + subparts->parent = mime->parent; + mime->parent = (curl_mimepart *)subparts; + } Curl_mime_cleanpart(part); curlx_free(part); } - curlx_free(mime); + else { + curl_mime *parent = (curl_mime *)mime->parent; + curlx_free(mime); + mime = parent; + } } } -CURLcode Curl_mime_duppart(struct Curl_easy *data, - curl_mimepart *dst, const curl_mimepart *src) +static bool mime_is_too_deep(const curl_mimepart *part, size_t call_depth) +{ + DEBUGASSERT(part); + + if(++call_depth > MAX_MIME_LEVELS) + return TRUE; + + if(part->kind == MIMEKIND_MULTIPART && part->arg) { + const curl_mime *mime = (const curl_mime *)part->arg; + const curl_mimepart *s; + + for(s = mime->firstpart; s; s = s->nextpart) { + if(mime_is_too_deep(s, call_depth)) + return TRUE; + } + } + + return FALSE; +} + +static CURLcode mime_duppart(struct Curl_easy *data, + curl_mimepart *dst, const curl_mimepart *src, + size_t call_depth) { curl_mime *mime; curl_mimepart *d; @@ -1135,6 +1153,10 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, CURLcode result = CURLE_OK; DEBUGASSERT(dst); + DEBUGASSERT(src); + + if(++call_depth > MAX_MIME_LEVELS) + return CURLE_TOO_LARGE; /* Duplicate content. */ switch(src->kind) { @@ -1163,7 +1185,7 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, for(s = ((curl_mime *)src->arg)->firstpart; !result && s; s = s->nextpart) { d = curl_mime_addpart(mime); - result = d ? Curl_mime_duppart(data, d, s) : CURLE_OUT_OF_MEMORY; + result = d ? mime_duppart(data, d, s, call_depth) : CURLE_OUT_OF_MEMORY; } break; default: /* Invalid kind: should not occur. */ @@ -1204,6 +1226,18 @@ CURLcode Curl_mime_duppart(struct Curl_easy *data, return result; } +CURLcode Curl_mime_duppart(struct Curl_easy *data, + curl_mimepart *dst, const curl_mimepart *src) +{ + if(!dst || !src) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(mime_is_too_deep(src, 0)) + return CURLE_TOO_LARGE; + + return mime_duppart(data, dst, src, 0); +} + /* * Mime build functions. */ @@ -1535,7 +1569,7 @@ size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, void *instream) * adding any data and this loops infinitely. */ do { hasread = FALSE; - ret = readback_part(part, buffer, nitems, &hasread, 0); + ret = readback_part(part, buffer, nitems, &hasread); /* * 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 @@ -1706,13 +1740,15 @@ static CURLcode add_content_disposition(struct Curl_easy *data, const char *contenttype, enum mimestrategy strategy) { - if(!disposition) - if(part->filename || part->name || - (contenttype && !curl_strnequal(contenttype, "multipart/", 10))) - disposition = DISPOSITION_DEFAULT; + if(!disposition && + (part->filename || part->name || + (contenttype && !curl_strnequal(contenttype, "multipart/", 10)))) + disposition = DISPOSITION_DEFAULT; + if(disposition && curl_strequal(disposition, "attachment") && !part->name && !part->filename) disposition = NULL; + if(disposition) { CURLcode result = CURLE_OK; char *name = NULL; @@ -1756,11 +1792,12 @@ static CURLcode add_content_disposition(struct Curl_easy *data, return CURLE_OK; } -CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, - curl_mimepart *part, - const char *contenttype, - const char *disposition, - enum mimestrategy strategy) +static CURLcode mime_prepare_headers(struct Curl_easy *data, + curl_mimepart *part, + const char *contenttype, + const char *disposition, + enum mimestrategy strategy, + size_t call_depth) { curl_mime *mime = NULL; const char *boundary = NULL; @@ -1768,6 +1805,9 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, const char *cte = NULL; CURLcode result = CURLE_OK; + if(++call_depth > MAX_MIME_LEVELS) + return CURLE_TOO_LARGE; + /* Get rid of previously prepared headers. */ curl_slist_free_all(part->curlheaders); part->curlheaders = NULL; @@ -1808,9 +1848,9 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, boundary = mime->boundary; } else if(contenttype && !customct && - content_type_match(contenttype, STRCONST("text/plain"))) - if(strategy == MIMESTRATEGY_MAIL || !part->filename) - contenttype = NULL; + content_type_match(contenttype, STRCONST("text/plain")) && + (strategy == MIMESTRATEGY_MAIL || !part->filename)) + contenttype = NULL; /* Issue content-disposition header only if not already set by caller. */ if(!search_header(part->userheaders, STRCONST("Content-Disposition"))) { @@ -1856,8 +1896,8 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, if(content_type_match(contenttype, STRCONST("multipart/form-data"))) disposition = "form-data"; for(subpart = mime->firstpart; subpart; subpart = subpart->nextpart) { - result = Curl_mime_prepare_headers(data, subpart, NULL, - disposition, strategy); + result = mime_prepare_headers(data, subpart, NULL, + disposition, strategy, call_depth); if(result) return result; } @@ -1865,6 +1905,16 @@ CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, return result; } +CURLcode Curl_mime_prepare_headers(struct Curl_easy *data, + curl_mimepart *part, + const char *contenttype, + const char *disposition, + enum mimestrategy strategy) +{ + return mime_prepare_headers(data, part, contenttype, disposition, + strategy, 0); +} + /* Recursively reset paused status in the given part. */ static void mime_unpause(curl_mimepart *part) { diff --git a/lib/mprintf.c b/lib/mprintf.c index d4725c905fe1..9f172c377bba 100644 --- a/lib/mprintf.c +++ b/lib/mprintf.c @@ -126,9 +126,22 @@ struct nsprintf { size_t max; }; +/* Output bytes are staged here and appended to the dynbuf in chunks: the + dynbuf append overhead (bounds checks, growth, memcpy, null-termination) + once per byte dominates formatting. 512 bytes of stack holds a typical + request line or header in a single flush. */ +#define ASPRINTF_STAGE_SIZE 512 + struct asprintf { struct dynbuf *b; char merr; + size_t nstage; + unsigned char stage[ASPRINTF_STAGE_SIZE]; +}; + +struct ioprintf { + FILE *fd; + bool error; }; /* the provided input number is 1-based but this returns the number 0-based. @@ -244,7 +257,7 @@ static int parse_flags(const char **fmtp, unsigned int *flagsp, int use_dollar, fmt += 2; } else { -#if SIZEOF_CURL_OFF_T > SIZEOF_LONG +#if SIZEOF_SIZE_T > SIZEOF_LONG flags |= FLAGS_LONGLONG; #else flags |= FLAGS_LONG; @@ -643,8 +656,87 @@ struct mproperty { unsigned int flags; }; +/* block output callback: consume up to 'len' bytes, return the number of + bytes accepted. Accepting fewer than 'len' bytes aborts formatting, like + the byte callback returning failure. May be NULL, in which case output + falls back to the per-byte 'stream' callback. */ +typedef size_t (*mp_streamn)(const unsigned char *buf, size_t len, void *f); + +/* commit any output the sink is holding. Returns non-zero on failure. Sinks + whose accepted bytes are already committed do not need one. */ +typedef int (*mp_flush)(void *f); + +/* emit a run of bytes through the block callback when available, else byte + by byte. Returns TRUE to abort formatting (mirroring OUTCHAR). */ +static bool stream_run(void *userp, + int (*stream)(unsigned char, void *), + mp_streamn streamn, + const unsigned char *buf, size_t len, int *donep) +{ + if(!len) + return FALSE; + if(streamn) { + size_t n = streamn(buf, len, userp); + *donep += (int)n; + return n != len; + } + while(len--) { + if(stream(*buf++, userp)) + return TRUE; + (*donep)++; + } + return FALSE; +} + +/* Padding is always spaces or zeros, so it comes from a constant rather than a + buffer that has to be filled first. 16 keeps every width used in the tree to + a single run; wider padding loops. */ +static const char pad_spaces[] = " "; +static const char pad_zeros[] = "0000000000000000"; +#define PAD_RUN 16 /* strlen() of both of the above */ + +/* emit 'num' copies of the pad byte. Returns TRUE to abort formatting. */ +static bool stream_pad(void *userp, + int (*stream)(unsigned char, void *), + mp_streamn streamn, + unsigned char pad, int num, int *donep) +{ + const unsigned char *src; + if(num <= 0) + return FALSE; + if(pad == '0') + src = (const unsigned char *)pad_zeros; + else + src = (const unsigned char *)pad_spaces; + while(num > PAD_RUN) { + if(stream_run(userp, stream, streamn, src, PAD_RUN, donep)) + return TRUE; + num -= PAD_RUN; + } + return stream_run(userp, stream, streamn, src, (size_t)num, donep); +} + +/* emit the leading null-terminated part of a run of at most 'len' bytes. + Returns TRUE to abort formatting. + + A byte loop and not memchr(): 'len' is the requested precision, which + out_string() does not clamp to the string length, so "%.64s" on a 10-byte + string arrives with len == 64 and memchr() would read past the object. */ +static bool stream_zrun(void *userp, + int (*stream)(unsigned char, void *), + mp_streamn streamn, + const char *str, size_t len, int *donep) +{ + size_t n = 0; + while(n < len && str[n]) + n++; + return stream_run(userp, stream, streamn, + (const unsigned char *)str, n, donep); +} + static bool out_double(void *userp, int (*stream)(unsigned char, void *), + mp_streamn streamn, struct mproperty *p, double dnum, char *work, int *donep) @@ -720,23 +812,20 @@ static bool out_double(void *userp, #ifdef _WIN32 curlx_win32_snprintf(work, BUFFSIZE, fmt, dnum); #else - /* !checksrc! disable BANNEDFUNC 2 */ + /* !checksrc! disable BANNEDFUNC 1 */ snprintf(work, BUFFSIZE, fmt, dnum); #endif #ifdef CURL_HAVE_DIAG #pragma GCC diagnostic pop #endif DEBUGASSERT(strlen(work) < BUFFSIZE); - while(*work) { - if(stream(*work++, userp)) - return TRUE; - (*donep)++; - } - return 0; + return stream_run(userp, stream, streamn, + (const unsigned char *)work, strlen(work), donep); } static bool out_number(void *userp, int (*stream)(unsigned char, void *), + mp_streamn streamn, struct mproperty *p, uint64_t num, int64_t nums, @@ -758,13 +847,16 @@ static bool out_number(void *userp, if(flags & FLAGS_CHAR) { /* Character. */ - if(!(flags & FLAGS_LEFT)) - while(--width > 0) - OUTCHAR(' '); + if(!(flags & FLAGS_LEFT)) { + if(stream_pad(userp, stream, streamn, ' ', width - 1, donep)) + return TRUE; + width = 1; + } OUTCHAR((char)num); - if(flags & FLAGS_LEFT) - while(--width > 0) - OUTCHAR(' '); + if(flags & FLAGS_LEFT) { + if(stream_pad(userp, stream, streamn, ' ', width - 1, donep)) + return TRUE; + } return FALSE; } if(flags & FLAGS_OCTAL) @@ -834,9 +926,11 @@ static bool out_number(void *userp, if(is_neg || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) --width; - if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR(' '); + if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + width = 0; + } if(is_neg) OUTCHAR('-'); @@ -853,18 +947,21 @@ static bool out_number(void *userp, OUTCHAR('x'); } - if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR('0'); + if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) { + if(stream_pad(userp, stream, streamn, '0', width, donep)) + return TRUE; + width = 0; + } /* Write the number. */ - while(++w <= workend) { - OUTCHAR(*w); - } + if(stream_run(userp, stream, streamn, (const unsigned char *)w + 1, + (size_t)(workend - w), donep)) + return TRUE; - if(flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); + if(flags & FLAGS_LEFT) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + } return FALSE; } @@ -873,6 +970,7 @@ static const char nilstr[] = "(nil)"; static bool out_string(void *userp, int (*stream)(unsigned char, void *), + mp_streamn streamn, struct mproperty *p, const char *str, int *donep) @@ -907,15 +1005,26 @@ static bool out_string(void *userp, if(flags & FLAGS_ALT) OUTCHAR('"'); - if(!(flags & FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); + if(!(flags & FLAGS_LEFT)) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + width = 0; + } - for(; len && *str; len--) - OUTCHAR(*str++); - if(flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); + /* With no precision, len is already the exact length; emit it without + scanning again. With precision, scan up to len but stop at NUL, since + the string may be shorter or the buffer may not be null-terminated. */ + if(prec == -1) { + if(stream_run(userp, stream, streamn, + (const unsigned char *)str, len, donep)) + return TRUE; + } + else if(stream_zrun(userp, stream, streamn, str, len, donep)) + return TRUE; + if(flags & FLAGS_LEFT) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + } if(flags & FLAGS_ALT) OUTCHAR('"'); @@ -925,6 +1034,7 @@ static bool out_string(void *userp, static bool out_pointer(void *userp, int (*stream)(unsigned char, void *), + mp_streamn streamn, struct mproperty *p, const char *ptr, char *work, @@ -936,24 +1046,27 @@ static bool out_pointer(void *userp, /* If the pointer is not NULL, write it as a %#x spec. */ p->flags |= FLAGS_HEX | FLAGS_ALT; - if(out_number(userp, stream, p, num, 0, work, donep)) + if(out_number(userp, stream, streamn, p, num, 0, work, donep)) return TRUE; } else { /* Write "(nil)" for a nil pointer. */ - const char *point; int width = p->width; int flags = p->flags; width -= (int)CURL_CSTRLEN(nilstr); - if(flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - for(point = nilstr; *point; ++point) - OUTCHAR(*point); - if(!(flags & FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); + if(flags & FLAGS_LEFT) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + width = 0; + } + if(stream_run(userp, stream, streamn, (const unsigned char *)nilstr, + sizeof(nilstr) - 1, donep)) + return TRUE; + if(!(flags & FLAGS_LEFT)) { + if(stream_pad(userp, stream, streamn, ' ', width, donep)) + return TRUE; + } } return FALSE; } @@ -977,6 +1090,10 @@ static int formatf(void *userp, /* untouched by format(), sent to the stream() function in the second argument */ /* function pointer called for each output character */ int (*stream)(unsigned char, void *), + /* optional block-output function pointer */ + mp_streamn streamn, + /* optional, commits output the sink is holding */ + mp_flush flush, const char *format, /* %-formatted string */ va_list ap_save) /* list of parameters */ { @@ -1000,12 +1117,8 @@ static int formatf(void *userp, /* untouched by format(), sent to the size_t outlen = optr->outlen; if(outlen) { - const char *str = optr->start; - for(; outlen && *str; outlen--) { - if(stream(*str++, userp)) - return done; - done++; - } + if(stream_zrun(userp, stream, streamn, optr->start, outlen, &done)) + return done; if(optr->flags & FLAGS_SUBSTR) /* this is a substring */ continue; @@ -1048,35 +1161,43 @@ static int formatf(void *userp, /* untouched by format(), sent to the case MTYPE_LONGU: case MTYPE_LONGLONGU: p.flags |= FLAGS_UNSIGNED; - if(out_number(userp, stream, &p, iptr->val.numu, 0, work, &done)) + if(out_number(userp, stream, streamn, &p, iptr->val.numu, 0, work, + &done)) return done; break; case MTYPE_INT: case MTYPE_LONG: case MTYPE_LONGLONG: - if(out_number(userp, stream, &p, iptr->val.numu, + if(out_number(userp, stream, streamn, &p, iptr->val.numu, iptr->val.nums, work, &done)) return done; break; case MTYPE_STRING: - if(out_string(userp, stream, &p, iptr->val.str, &done)) + if(out_string(userp, stream, streamn, &p, iptr->val.str, &done)) return done; break; case MTYPE_PTR: - if(out_pointer(userp, stream, &p, iptr->val.ptr, work, &done)) + if(out_pointer(userp, stream, streamn, &p, iptr->val.ptr, work, + &done)) return done; break; case MTYPE_DOUBLE: case MTYPE_LONGDOUBLE: - if(out_double(userp, stream, &p, iptr->val.dnum, work, &done)) + if(out_double(userp, stream, streamn, &p, iptr->val.dnum, work, + &done)) return done; break; case MTYPE_INTPTR: + /* %n is observable by the caller, so commit what is pending first and + perform it only if that succeeded, as a sink that never holds output + would. */ + if(flush && flush(userp)) + return done; /* Answer the count of characters written. */ if(p.flags & FLAGS_LONGLONG) *(int64_t *)iptr->val.ptr = (int64_t)done; @@ -1108,6 +1229,21 @@ static int addbyter(unsigned char outc, void *f) return 1; } +/* block variant of addbyter */ +static size_t addrun(const unsigned char *buf, size_t len, void *f) +{ + struct nsprintf *infop = f; + size_t fit = infop->max - infop->length; + if(len > fit) + len = fit; + if(len) { + memcpy(infop->buffer, buf, len); + infop->buffer += len; + infop->length += len; + } + return len; +} + int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, va_list args) { @@ -1118,14 +1254,14 @@ int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, info.length = 0; info.max = maxlength; - retcode = formatf(&info, addbyter, format, args); + retcode = formatf(&info, addbyter, addrun, NULL, format, args); if(info.max) { - /* we terminate this with a zero byte */ + /* we null-terminate this */ if(info.max == info.length) { /* we are at maximum, scrap the last letter */ info.buffer[-1] = 0; DEBUGASSERT(retcode); - retcode--; /* do not count the nul byte */ + retcode--; /* do not count the NUL byte */ } else info.buffer[0] = 0; @@ -1143,11 +1279,27 @@ int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) return retcode; } -/* fputc() look-alike */ -static int alloc_addbyter(unsigned char outc, void *f) +/* append the staged bytes to the dynbuf. Returns 1 on failure (and sets + merr), 0 on success. */ +static int alloc_flush(void *f) { struct asprintf *infop = f; - CURLcode result = curlx_dyn_addn(infop->b, &outc, 1); + if(infop->nstage) { + CURLcode result = curlx_dyn_addn(infop->b, infop->stage, infop->nstage); + infop->nstage = 0; + if(result) { + infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM; + return 1; /* fail */ + } + } + return 0; +} + +/* append directly, bypassing the stage. Returns 1 on failure. */ +static int alloc_addn(struct asprintf *infop, const unsigned char *buf, + size_t len) +{ + CURLcode result = curlx_dyn_addn(infop->b, buf, len); if(result) { infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM; return 1; /* fail */ @@ -1155,14 +1307,84 @@ static int alloc_addbyter(unsigned char outc, void *f) return 0; } +/* TRUE if 'more' additional bytes still fit the dynbuf size limit, counting + what is already staged. curlx_dyn_addn() rejects an append when + len + leng + 1 exceeds it. */ +static bool alloc_fits(struct asprintf *infop, size_t more) +{ + return (infop->b->leng + infop->nstage + more + 1) <= infop->b->toobig; +} + +/* fputc() look-alike */ +static int alloc_addbyter(unsigned char outc, void *f) +{ + struct asprintf *infop = f; + if(infop->nstage == sizeof(infop->stage) && alloc_flush(infop)) + return 1; /* fail */ + if(!alloc_fits(infop, 1)) { + /* Does not fit the dynbuf size limit. Flush and append this byte on + its own, so the limit is reported at exactly this byte and formatting + stops where it would without staging. %n depends on that. */ + if(alloc_flush(infop)) + return 1; + return alloc_addn(infop, &outc, 1); + } + infop->stage[infop->nstage++] = outc; + return 0; +} + +/* block variant of alloc_addbyter */ +static size_t alloc_addrun(const unsigned char *buf, size_t len, void *f) +{ + struct asprintf *infop = f; + size_t accepted = 0; + while(accepted < len) { + size_t room = sizeof(infop->stage) - infop->nstage; + size_t n; + if(!room) { + if(alloc_flush(infop)) + return accepted; /* accepted < len aborts formatting */ + room = sizeof(infop->stage); + } + n = len - accepted; + if(n > room) + n = room; + if(!alloc_fits(infop, n)) { + /* Run does not fit the dynbuf size limit. Stage what does, then + append the first byte that does not on its own, so the limit is + reported at exactly that byte. */ + size_t fits = infop->b->toobig - infop->b->leng - infop->nstage - 1; + if(fits) { + memcpy(&infop->stage[infop->nstage], buf + accepted, fits); + infop->nstage += fits; + accepted += fits; + } + if(alloc_flush(infop)) + return accepted; + if(alloc_addn(infop, buf + accepted, 1)) + return accepted; /* accepted < len aborts formatting */ + accepted++; + continue; + } + memcpy(&infop->stage[infop->nstage], buf + accepted, n); + infop->nstage += n; + accepted += n; + } + return accepted; +} + /* appends the formatted string, returns MERR error code */ int curlx_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list args) { struct asprintf info; info.b = dyn; info.merr = MERR_OK; + info.nstage = 0; - (void)formatf(&info, alloc_addbyter, format, args); + (void)formatf(&info, alloc_addbyter, alloc_addrun, alloc_flush, + format, args); + if(!info.merr) + (void)alloc_flush(&info); if(info.merr) { curlx_dyn_free(info.b); return info.merr; @@ -1177,8 +1399,12 @@ char *curl_mvaprintf(const char *format, va_list args) info.b = &dyn; curlx_dyn_init(info.b, DYN_APRINTF); info.merr = MERR_OK; + info.nstage = 0; - (void)formatf(&info, alloc_addbyter, format, args); + (void)formatf(&info, alloc_addbyter, alloc_addrun, alloc_flush, + format, args); + if(!info.merr) + (void)alloc_flush(&info); if(info.merr) { curlx_dyn_free(info.b); return NULL; @@ -1206,58 +1432,87 @@ static int storebuffer(unsigned char outc, void *f) return 0; } +/* block variant of storebuffer */ +static size_t storerun(const unsigned char *buf, size_t len, void *f) +{ + char **buffer = f; + memcpy(*buffer, buf, len); + *buffer += len; + return len; +} + int curl_msprintf(char *buffer, const char *format, ...) { va_list args; /* argument pointer */ int retcode; va_start(args, format); - retcode = formatf(&buffer, storebuffer, format, args); + retcode = formatf(&buffer, storebuffer, storerun, NULL, format, args); va_end(args); - *buffer = 0; /* we terminate this with a zero byte */ + *buffer = 0; /* we null-terminate this */ return retcode; } static int fputc_wrapper(unsigned char outc, void *f) { + struct ioprintf *i = f; int out = outc; - FILE *s = f; + FILE *s = i->fd; int rc = fputc(out, s); - return rc == EOF; + i->error = (rc == EOF); + return (rc == EOF); } -int curl_mprintf(const char *format, ...) +/* block variant of fputc_wrapper */ +static size_t fwrite_wrapper(const unsigned char *buf, size_t len, void *f) +{ + struct ioprintf *i = f; + size_t n; + FILE *s = i->fd; + n = fwrite(buf, 1, len, s); + i->error = (n != len); + return n; +} + +int curl_mvfprintf(FILE *fd, const char *format, va_list args) +{ + struct ioprintf info; + int n; + info.fd = fd; + info.error = FALSE; + n = formatf(&info, fputc_wrapper, fwrite_wrapper, NULL, format, args); + if(info.error) + return -1; + return n; +} + +int curl_mfprintf(FILE *fd, const char *format, ...) { - int retcode; va_list args; /* argument pointer */ + int n; va_start(args, format); - retcode = formatf(stdout, fputc_wrapper, format, args); + n = curl_mvfprintf(fd, format, args); va_end(args); - return retcode; + return n; } -int curl_mfprintf(FILE *fd, const char *format, ...) +int curl_mprintf(const char *format, ...) { - int retcode; va_list args; /* argument pointer */ + int n; va_start(args, format); - retcode = formatf(fd, fputc_wrapper, format, args); + n = curl_mvfprintf(stdout, format, args); va_end(args); - return retcode; + return n; } int curl_mvsprintf(char *buffer, const char *format, va_list args) { - int retcode = formatf(&buffer, storebuffer, format, args); - *buffer = 0; /* we terminate this with a zero byte */ + int retcode = formatf(&buffer, storebuffer, storerun, NULL, format, args); + *buffer = 0; /* we null-terminate this */ return retcode; } int curl_mvprintf(const char *format, va_list args) { - return formatf(stdout, fputc_wrapper, format, args); -} - -int curl_mvfprintf(FILE *fd, const char *format, va_list args) -{ - return formatf(fd, fputc_wrapper, format, args); + return curl_mvfprintf(stdout, format, args); } diff --git a/lib/mqtt.c b/lib/mqtt.c index 29e92ce5c133..fc1d4b0ac4e5 100644 --- a/lib/mqtt.c +++ b/lib/mqtt.c @@ -67,8 +67,10 @@ enum mqttstate { MQTT_SUBACK_COMING, /* 4 - the SUBACK remainder */ MQTT_PUBWAIT, /* 5 - wait for publish */ MQTT_PUB_REMAIN, /* 6 - wait for the remainder of the publish */ + MQTT_POST_DRAIN, /* 7 - finish sending PUBLISH */ + MQTT_DISCONNECT_DRAIN, /* 8 - finish sending DISCONNECT */ - MQTT_NOSTATE /* 7 - never used an actual state */ + MQTT_NOSTATE /* 9 - never used an actual state */ }; struct mqtt_conn { @@ -91,7 +93,7 @@ struct MQTT { BIT(pingsent); /* 1 while we wait for ping response */ }; -static void mqtt_easy_dtor(void *key, size_t klen, void *entry) +static void mqtt_easy_dtor(const void *key, size_t klen, void *entry) { struct MQTT *mq = entry; (void)key; @@ -101,7 +103,7 @@ static void mqtt_easy_dtor(void *key, size_t klen, void *entry) curlx_free(mq); } -static void mqtt_conn_dtor(void *key, size_t klen, void *entry) +static void mqtt_conn_dtor(const void *key, size_t klen, void *entry) { (void)key; (void)klen; @@ -140,32 +142,48 @@ static CURLcode mqtt_send(struct Curl_easy *data, if(!mq) return CURLE_FAILED_INIT; + /* A new packet must not replace a previously queued packet tail. */ + DEBUGASSERT(!curlx_dyn_len(&mq->sendbuf)); result = Curl_xfer_send(data, buf, len, FALSE, &n); if(result) return result; mq->lastTime = *Curl_pgrs_now(data); Curl_debug(data, CURLINFO_HEADER_OUT, buf, n); - if(len != n) { - size_t nsend = len - n; - if(curlx_dyn_len(&mq->sendbuf)) { - DEBUGASSERT(curlx_dyn_len(&mq->sendbuf) >= nsend); - result = curlx_dyn_tail(&mq->sendbuf, nsend); /* keep this much */ - } - else { - result = curlx_dyn_addn(&mq->sendbuf, &buf[n], nsend); - } - } - else - curlx_dyn_reset(&mq->sendbuf); + if(len != n) + result = curlx_dyn_addn(&mq->sendbuf, &buf[n], len - n); return result; } +/* Send the queued tail, preserving any bytes that still cannot be sent. */ +static CURLcode mqtt_flush(struct Curl_easy *data) +{ + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + size_t len, n; + CURLcode result; + + if(!mq) + return CURLE_FAILED_INIT; + len = curlx_dyn_len(&mq->sendbuf); + if(!len) + return CURLE_OK; + + result = Curl_xfer_send(data, curlx_dyn_ptr(&mq->sendbuf), len, FALSE, &n); + if(result) + return result; + mq->lastTime = *Curl_pgrs_now(data); + Curl_debug(data, CURLINFO_HEADER_OUT, curlx_dyn_ptr(&mq->sendbuf), n); + return curlx_dyn_tail(&mq->sendbuf, len - n); +} + /* Generic function called by the multi interface to figure out what socket(s) to wait for and for what actions during the DOING and PROTOCONNECT states */ static CURLcode mqtt_pollset(struct Curl_easy *data, struct easy_pollset *ps) { + struct MQTT *mq = Curl_meta_get(data, CURL_META_MQTT_EASY); + if(mq && curlx_dyn_len(&mq->sendbuf)) + return Curl_pollset_add_out(data, ps, data->conn->sock[FIRSTSOCKET]); return Curl_pollset_add_in(data, ps, data->conn->sock[FIRSTSOCKET]); } @@ -572,6 +590,14 @@ static CURLcode mqtt_publish(struct Curl_easy *data) if(result) goto fail; + /* silly check for silly analyzers. On 32-bit systems a payload length close + to 4GB can wrap, but it is impossible to have such a large buffer in + memory on those systems */ + if((SIZE_MAX - topiclen - 2) < payloadlen) { + result = CURLE_TOO_LARGE; + goto fail; + } + remaininglength = payloadlen + 2 + topiclen; encodelen = mqtt_encode_len(encodedbytes, remaininglength); if(remaininglength > (MAX_MQTT_MESSAGE_SIZE - encodelen - 1)) { @@ -637,6 +663,8 @@ static const char * const statenames[] = { "MQTT_SUBACK_COMING", "MQTT_PUBWAIT", "MQTT_PUB_REMAIN", + "MQTT_POST_DRAIN", + "MQTT_DISCONNECT_DRAIN", "NOT A STATE" }; @@ -843,14 +871,16 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) if(curlx_dyn_len(&mq->sendbuf)) { /* send the remainder of an outgoing packet */ - result = mqtt_send(data, curlx_dyn_ptr(&mq->sendbuf), - curlx_dyn_len(&mq->sendbuf)); - if(result) + result = mqtt_flush(data); + /* CURLE_OK can still mean a short write. Wait for writable progress + before sending another packet or processing a reply. */ + if(result || curlx_dyn_len(&mq->sendbuf)) return result; } result = mqtt_ping(data); - if(result) + /* Packet processing may send more output, so finish PINGREQ first. */ + if(result || curlx_dyn_len(&mq->sendbuf)) return result; infof(data, "mqtt_doing: state [%d]", (int)mqtt->state); @@ -935,20 +965,31 @@ static CURLcode mqtt_doing(struct Curl_easy *data, bool *done) if(result) break; - if(data->state.httpreq == HTTPREQ_POST) { - result = mqtt_publish(data); - if(!result) { - result = mqtt_disconnect(data); - *done = TRUE; - } - mqtt->nextstate = MQTT_FIRST; - } - else { + if(data->state.httpreq != HTTPREQ_POST) { result = mqtt_subscribe(data); - if(!result) { + if(!result) mqstate(data, MQTT_FIRST, MQTT_SUBACK); - } + break; } + + result = mqtt_publish(data); + if(result) + break; + mqstate(data, MQTT_POST_DRAIN, MQTT_NOSTATE); + FALLTHROUGH(); + case MQTT_POST_DRAIN: + /* PUBLISH must be complete before DISCONNECT can use the send buffer. */ + if(curlx_dyn_len(&mq->sendbuf)) + break; + result = mqtt_disconnect(data); + if(result) + break; + mqstate(data, MQTT_DISCONNECT_DRAIN, MQTT_NOSTATE); + FALLTHROUGH(); + case MQTT_DISCONNECT_DRAIN: + /* Completing now would discard any DISCONNECT bytes still queued. */ + if(!curlx_dyn_len(&mq->sendbuf)) + *done = TRUE; break; case MQTT_SUBACK: diff --git a/lib/multi.c b/lib/multi.c index fbefa6f30d91..8e2d1fd5cf42 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -47,6 +47,7 @@ #include "http2.h" #include "socketpair.h" #include "bufref.h" +#include "curlx/strparse.h" /* initial multi->xfers table size for a full multi */ #define CURL_XFER_TABLE_SIZE 128 @@ -83,12 +84,8 @@ static void multi_xfer_bufs_free(struct Curl_multi *multi); #ifdef DEBUGBUILD static void multi_xfer_tbl_dump(struct Curl_multi *multi); #endif - -static const struct curltime *multi_now(struct Curl_multi *multi) -{ - curlx_pnow(&multi->now); - return &multi->now; -} +/* Get the # of transfers current in process/pending. */ +static uint32_t multi_xfers_running(struct Curl_multi *multi); /* function pointer called once when entering a state */ typedef void (*mstate_enter_func)(struct Curl_easy *data, @@ -128,6 +125,8 @@ static void mstate_enter_completed(struct Curl_easy *data, Curl_pgrsTime(data, TIMER_POSTRANSFER); Curl_pgrsTime(data, TIMER_STARTTRANSFER); } + /* multi_done() should have run and detached any connection already */ + DEBUGASSERT(!data->conn); Curl_pgrsCompleted(data); if(from_state < MSTATE_DONE) CURLM_NTFY(data, CURLMNOTIFY_EASY_DONE); @@ -210,14 +209,13 @@ static void ph_freeentry(void *p) /* * multi_addmsg() * - * Called when a transfer is completed. Adds the given msg pointer to - * the list kept in the multi handle. + * Called when a transfer is completed. Marks its message as unread. */ -static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) +static void multi_addmsg(struct Curl_multi *multi, struct Curl_easy *data) { - if(!Curl_llist_count(&multi->msglist)) + if(Curl_uint32_bset_empty(&multi->msgsent)) CURLM_NTFY(multi->admin, CURLMNOTIFY_INFO_READ); - Curl_llist_append(&multi->msglist, msg, &msg->list); + Curl_uint32_bset_add(&multi->msgsent, data->mid); } static void multi_timeouts_init(struct Curl_easy *data); @@ -228,6 +226,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, size_t dnssize, /* dns hash */ size_t sesssize) /* TLS session cache */ { + struct curltime created; struct Curl_multi *multi = curlx_calloc(1, sizeof(struct Curl_multi)); if(!multi) @@ -248,25 +247,23 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, multi->admin->state.internal = TRUE; /* Now we can use curlx_* things safely */ - curlx_pnow(&multi->now); - Curl_timeouts_init(&multi->timeouts, &multi->now); + curlx_pnow(&created); + Curl_timeouts_init(&multi->timeouts, &created); multi_timeouts_init(multi->admin); Curl_dnscache_init(&multi->dnscache, dnssize); Curl_mntfy_init(multi); Curl_multi_ev_init(multi, ev_hashsize); - Curl_uint32_tbl_init(&multi->xfers, NULL); + Curl_uint32_tbl_init(&multi->xfers); Curl_uint32_bset_init(&multi->process); Curl_uint32_bset_init(&multi->dirty); Curl_uint32_bset_init(&multi->pending); Curl_uint32_bset_init(&multi->msgsent); - Curl_hash_init(&multi->proto_hash, 23, - Curl_hash_str, curlx_str_key_compare, ph_freeentry); - Curl_llist_init(&multi->msglist, NULL); + Curl_hash_init(&multi->proto_hash, 23, CURL_HASH_TYPE_BYTES, ph_freeentry); + Curl_cshutdn_init(&multi->cshutdn); 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; @@ -276,8 +273,7 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, multi->wakeup_internal[1] = CURL_SOCKET_BAD; #endif - if(Curl_mntfy_resize(multi) || - Curl_uint32_bset_resize(&multi->process, xfer_table_size) || + if(Curl_uint32_bset_resize(&multi->process, xfer_table_size) || Curl_uint32_bset_resize(&multi->pending, xfer_table_size) || Curl_uint32_bset_resize(&multi->dirty, xfer_table_size) || Curl_uint32_bset_resize(&multi->msgsent, xfer_table_size) || @@ -285,15 +281,22 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, goto error; #ifdef DEBUGBUILD - if(getenv("CURL_DEBUG")) - multi->admin->set.verbose = TRUE; + { + const char *p; + if(getenv("CURL_DEBUG")) + multi->admin->set.verbose = TRUE; + p = getenv("CURL_DBG_MAX_TOTAL_CONNECTIONS"); + if(p) { + curl_off_t l; + if(!curlx_str_number(&p, &l, UINT32_MAX)) { + multi->max_total_connections = (uint32_t)l; + } + } + } #endif Curl_uint32_tbl_add(&multi->xfers, multi->admin, &multi->admin->mid); Curl_uint32_bset_add(&multi->process, multi->admin->mid); - if(Curl_cshutdn_init(&multi->cshutdn, multi)) - goto error; - Curl_cpool_init(&multi->cpool, NULL, chashsize); #ifdef USE_SSL @@ -562,7 +565,7 @@ CURLMcode Curl_multi_add_handle(struct Curl_multi *multi, multi->admin->set.no_signal = data->set.no_signal; CURL_TRC_M(data, "added to multi, mid=%u, running=%u, total=%u", - data->mid, Curl_multi_xfers_running(multi), + data->mid, multi_xfers_running(multi), Curl_uint32_tbl_count(&multi->xfers)); return CURLM_OK; } @@ -575,7 +578,7 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *curl) if(CURL_MAPI_ENTER(&guard, m, multi_add_handle, &mresult)) { struct Curl_easy *data = curl; /* Verify that we got a somewhat good easy handle too */ - if(!GOOD_EASY_HANDLE(data)) + if(!GOOD_EASY_HANDLE(data) || data->state.internal) mresult = CURLM_BAD_EASY_HANDLE; else mresult = Curl_multi_add_handle(m, data); @@ -606,8 +609,8 @@ static bool multi_conn_should_close(struct connectdata *conn, conn->proxy_ntlm_state == NTLMSTATE_TYPE2) #endif #ifdef USE_SPNEGO - && !(conn->http_negotiate_state == GSS_AUTHRECV || - conn->proxy_negotiate_state == GSS_AUTHRECV) + && !(conn->http_negotiate_state == CURL_NEGO_AUTH_RECV || + conn->proxy_negotiate_state == CURL_NEGO_AUTH_RECV) #endif ) return TRUE; @@ -629,26 +632,22 @@ static bool multi_conn_should_close(struct connectdata *conn, return FALSE; } -static void multi_done_locked(struct connectdata *conn, - struct Curl_easy *data, - void *userdata) +static cpool_do_result multi_done_locked(struct Curl_easy *data, + struct connectdata *conn, + void *userdata, + const struct curltime *pnow) { struct multi_done_ctx *mdctx = userdata; - Curl_detach_connection(data); - + (void)pnow; CURL_TRC_M(data, "multi_done_locked, in use=%u", conn->attached_xfers); if(CONN_INUSE(conn)) { /* Stop if still used. */ CURL_TRC_M(data, "Connection still in use %u, no more multi_done now!", conn->attached_xfers); - return; + return CPOOL_DO_KEEP; } - data->state.done = TRUE; /* called now! */ - - 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:%u, " "forbid=%d, close=%d, premature=%d, conn_multiplex=%d", @@ -657,27 +656,17 @@ static void multi_done_locked(struct connectdata *conn, data->set.reuse_forbid, conn->bits.close, mdctx->premature, Curl_conn_is_multiplex(conn, FIRSTSOCKET)); connclose(conn); - Curl_conn_close(data, conn, (bool)mdctx->premature); + return mdctx->premature ? CPOOL_DO_TERMINATE : CPOOL_DO_CLOSE; } else if(!Curl_conn_get_max_concurrent(data, conn, FIRSTSOCKET)) { CURL_TRC_M(data, "multi_done, conn #%" FMT_OFF_T " to %s:%u was shutdown" " by server, not reusing", conn->connection_id, conn->origin->user_hostname, conn->origin->port); connclose(conn); - Curl_conn_close(data, conn, (bool)mdctx->premature); + return mdctx->premature ? CPOOL_DO_TERMINATE : CPOOL_DO_CLOSE; } else { - /* the connection is no longer in use by any transfer */ - if(Curl_cpool_conn_now_idle(data, conn)) { - /* connection kept in the cpool */ - infof(data, "Connection #%" FMT_OFF_T " to host %s:%u left intact", - conn->connection_id, conn->origin->user_hostname, - conn->origin->port); - } - else { - /* connection was removed from the cpool and destroyed. */ - data->state.lastconnect_id = -1; - } + return CPOOL_DO_IDLE; } } @@ -688,13 +677,16 @@ static CURLcode multi_done(struct Curl_easy *data, { CURLcode result; struct connectdata *conn = data->conn; + const struct curltime *pnow = Curl_pgrs_now(data); CURL_TRC_M(data, "multi_done: status: %d prem: %d done: %d", (int)status, (int)premature, data->state.done); - if(data->state.done) + if(data->state.done) { /* Stop if multi_done() has already been called */ return CURLE_OK; + } + data->state.done = TRUE; /* called now! */ /* Shut down any ongoing async resolver operation. */ Curl_resolv_shutdown_all(data); @@ -729,7 +721,7 @@ static CURLcode multi_done(struct Curl_easy *data, * - the transfer has not connected * - we already aborted by callback to avoid this calling another callback */ - int rc = Curl_pgrsDone(data); + int rc = Curl_pgrsDone(data, pnow); if(!result && rc) result = CURLE_ABORTED_BY_CALLBACK; } @@ -737,23 +729,25 @@ static CURLcode multi_done(struct Curl_easy *data, /* Make sure that transfer client writes are really done now. */ result = Curl_1st_fatal(result, Curl_xfer_write_done(data, premature)); - /* Inform connection filters that this transfer is done */ - if(conn) - Curl_conn_ev_data_done(data, premature); - - multi_schedule_pending(data->multi); /* connection / multiplex */ - if(!result) result = Curl_req_done(&data->req, data, premature); if(conn) { - /* Under the potential connection pool's share lock, decide what to - * do with the transfer's connection. */ + /* Detach connection and decide what to do with it. */ struct multi_done_ctx mdctx; + /* Inform connection filters that this transfer is done and they + * need to give up any references to `data`. */ + Curl_conn_ev_data_done(data, premature); + Curl_detach_connection(data); + Curl_dnscache_prune(data, pnow); + memset(&mdctx, 0, sizeof(mdctx)); mdctx.premature = premature; - Curl_cpool_do_locked(data, data->conn, multi_done_locked, &mdctx); + Curl_cpool_return(data, conn, multi_done_locked, &mdctx, pnow); + + if(data->multi) + multi_schedule_pending(data->multi); } /* flush the netrc cache */ @@ -766,7 +760,6 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, { CURLMcode mresult; bool premature; - struct Curl_llist_node *e; uint32_t mid; /* Prevent users from trying to remove same easy handle more than once */ @@ -806,8 +799,8 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, called. Do it after multi_done() in case that sets another time! */ Curl_expire_clear_all(data); - /* If in `msgsent`, it was deducted from `multi->xfers_alive` already. */ - if(!Curl_uint32_bset_contains(&multi->msgsent, data->mid)) + /* In MSGSENT, it was deducted from `multi->xfers_alive` already. */ + if(data->mstate != MSTATE_MSGSENT) --multi->xfers_alive; if(data->state.really_alive) { @@ -829,7 +822,7 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, if(data->set.connect_only) { if(data->multi_easy) { - if(data->state.lastconnect_id != -1) { + if(data->state.last_conn_id != -1) { /* Mark any connect-only connection for closure */ struct connectdata *conn; (void)Curl_getconnectinfo(data, &conn); @@ -858,18 +851,6 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, data->psl = NULL; #endif - /* make sure there is no pending message in the queue sent from this easy - handle */ - for(e = Curl_llist_head(&multi->msglist); e; e = Curl_node_next(e)) { - struct Curl_message *msg = Curl_node_elem(e); - - if(msg->extmsg.easy_handle == data) { - Curl_node_remove(e); - /* there can only be one from this specific handle */ - break; - } - } - /* clear the association to this multi handle */ mid = data->mid; DEBUGASSERT(Curl_uint32_tbl_contains(&multi->xfers, mid)); @@ -895,7 +876,7 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi, } CURL_TRC_M(data, "removed from multi, mid=%u, running=%u, total=%u", - mid, Curl_multi_xfers_running(multi), + mid, multi_xfers_running(multi), Curl_uint32_tbl_count(&multi->xfers)); return CURLM_OK; } @@ -907,7 +888,7 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *curl) if(CURL_MAPI_ENTER(&guard, m, multi_remove_handle, &mresult)) { struct Curl_easy *data = curl; - if(!GOOD_EASY_HANDLE(data)) + if(!GOOD_EASY_HANDLE(data) || data->state.internal) mresult = CURLM_BAD_EASY_HANDLE; else mresult = Curl_multi_remove_handle(m, data); @@ -959,8 +940,10 @@ void Curl_attach_connection(struct Curl_easy *data, DEBUGASSERT(conn); DEBUGASSERT(conn->attached_xfers < UINT32_MAX); data->conn = conn; - if(matched) - data->state.lastconnect_id = conn->connection_id; + if(matched) { + data->state.last_conn_id = conn->connection_id; + data->state.last_cpid = conn->cpid; + } else DEBUGASSERT(!data->mid); /* admin handle */ conn->attached_xfers++; @@ -1297,8 +1280,8 @@ CURLMcode curl_multi_fdset(CURLM *m, } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); } - Curl_cshutdn_setfds(&multi->cshutdn, read_fd_set, write_fd_set, - &this_max_fd); + Curl_cshutdn_setfds(&multi->cshutdn, multi->admin, + read_fd_set, write_fd_set, &this_max_fd); *max_fd = this_max_fd; Curl_pollset_cleanup(&ps); @@ -1346,7 +1329,7 @@ CURLMcode curl_multi_waitfds(CURLM *m, } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); } - need += Curl_cshutdn_add_waitfds(&multi->cshutdn, &cwfds); + need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds); if(need != cwfds.n && ufds) mresult = CURLM_OUT_OF_MEMORY; @@ -1402,11 +1385,9 @@ static CURLMcode multi_winsock_select(struct Curl_multi *multi, 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)) { - mresult = CURLM_OUT_OF_MEMORY; - goto out; - } + if(mask && WSAEventSelect(cpfds->pfds[i].fd, multi->wsa_event, mask)) { + mresult = CURLM_OUT_OF_MEMORY; + goto out; } } @@ -1583,7 +1564,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } while(Curl_uint32_bset_next(&multi->process, mid, &mid)); } - if(Curl_cshutdn_add_pollfds(&multi->cshutdn, &cpfds)) { + if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) { mresult = CURLM_OUT_OF_MEMORY; goto out; } @@ -1840,10 +1821,14 @@ static bool multi_handle_timeout(struct Curl_easy *data, timerid base_timer = Curl_is_connecting(data) ? TIMER_STARTSINGLE : TIMER_STARTOP; timediff_t elapsed_ms = Curl_pgrs_since_ms(data, NULL, base_timer); - if(data->mstate == MSTATE_CONNECTING) + if(data->mstate == MSTATE_CONNECTING) { failf(data, "%s timed out after %" FMT_TIMEDIFF_T " milliseconds", data->conn->bits.dns_resolved ? "Connection" : "Resolving", elapsed_ms); + /* keep what we know about a partly done connect, e.g. TCP done + * but TLS handshake still ongoing */ + Curl_conn_report_failed(data, FIRSTSOCKET); + } else { struct SingleRequest *k = &data->req; if(k->size != -1) { @@ -2421,9 +2406,9 @@ static CURLcode is_finished(struct Curl_multi *multi, connection */ streamclose(data->conn); - /* if not yet in DONE state, go there, otherwise COMPLETED */ - multistate(data, (data->mstate < MSTATE_DONE) ? - MSTATE_DONE : MSTATE_COMPLETED); + /* if not yet in DONE state, go there. */ + if(data->mstate < MSTATE_DONE) + multistate(data, MSTATE_DONE); return result; } } @@ -2435,8 +2420,10 @@ static void handle_completed(struct Curl_multi *multi, struct Curl_easy *data, CURLcode result) { - if(data->master_mid != UINT32_MAX) { - /* A sub transfer, not for msgsent to application. Is anyone still + bool msg_to_app = data->master_mid == UINT32_MAX; + + if(!msg_to_app) { + /* A sub transfer, not reported to the application. Is anyone still * interested in processing its results? */ if(data->sub_xfer_done) { struct Curl_easy *master = Curl_multi_get_easy(multi, data->master_mid); @@ -2449,23 +2436,23 @@ static void handle_completed(struct Curl_multi *multi, } } else { - /* now fill in the Curl_message with this info */ - struct Curl_message *msg = &data->msg; + /* now fill in the CURLMsg with this info */ + struct CURLMsg *msg = &data->msg; - msg->extmsg.msg = CURLMSG_DONE; - msg->extmsg.easy_handle = data; - msg->extmsg.data.result = result; + msg->msg = CURLMSG_DONE; + msg->easy_handle = data; + msg->data.result = result; - multi_addmsg(multi, msg); DEBUGASSERT(!data->conn); } multistate(data, MSTATE_MSGSENT); - /* remove from the other sets, add to msgsent */ + /* remove from the other sets */ Curl_uint32_bset_remove(&multi->process, data->mid); Curl_uint32_bset_remove(&multi->dirty, data->mid); Curl_uint32_bset_remove(&multi->pending, data->mid); - Curl_uint32_bset_add(&multi->msgsent, data->mid); + if(msg_to_app) + multi_addmsg(multi, data); if(data->state.really_alive) { data->state.really_alive = FALSE; --multi->xfers_really_alive; @@ -2759,7 +2746,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, #ifdef USE_RESOLV_THREADED Curl_async_thrdd_multi_process(multi); #endif - Curl_cshutdn_perform(&multi->cshutdn, sigpipe_ctx); + Curl_cshutdn_perform(&multi->cshutdn, multi->admin, sigpipe_ctx); goto out; } @@ -2910,15 +2897,16 @@ static CURLMcode multi_perform(struct Curl_multi *multi, int *running_handles) { CURLMcode returncode = CURLM_OK; - struct curltime start = *multi_now(multi); + struct curltime start; uint32_t mid; struct Curl_sigpipe_ctx sigpipe_ctx; + curlx_pnow(&start); sigpipe_init(&sigpipe_ctx); if(Curl_uint32_bset_first(&multi->process, &mid)) { CURL_TRC_M(multi->admin, "multi_perform(running=%u)", - Curl_multi_xfers_running(multi)); + multi_xfers_running(multi)); do { struct Curl_easy *data = Curl_multi_get_easy(multi, mid); CURLMcode mresult; @@ -2962,7 +2950,7 @@ static CURLMcode multi_perform(struct Curl_multi *multi, if(data->mstate == MSTATE_PENDING) { bool stream_unused; CURLcode result_unused; - if(multi_handle_timeout(data, multi_now(multi), + if(multi_handle_timeout(data, Curl_pgrs_now(data), &stream_unused, &result_unused)) { infof(data, "PENDING handle timeout"); move_pending_to_connect(multi, data); @@ -2971,7 +2959,7 @@ static CURLMcode multi_perform(struct Curl_multi *multi, } if(running_handles) { - uint32_t running = Curl_multi_xfers_running(multi); + uint32_t running = multi_xfers_running(multi); *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; } @@ -3100,10 +3088,7 @@ CURLMcode curl_multi_cleanup(CURLM *m) * curl_multi_info_read() * * This function is the primary way for a multi/multi_socket application to - * figure out if a transfer has ended. We MUST make this function as fast as - * possible as it will be polled frequently and we MUST NOT scan any lists in - * here to figure out things. We must scale fine to thousands of handles and - * beyond. The current design is fully O(1). + * figure out if a transfer has ended. */ CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue) @@ -3114,22 +3099,16 @@ CURLMsg *curl_multi_info_read(CURLM *m, int *msgs_in_queue) *msgs_in_queue = 0; /* default to none */ if(CURL_MAPI_ENTER(&guard, m, multi_info_read, NULL)) { struct Curl_multi *multi = m; - if(Curl_llist_count(&multi->msglist)) { - /* there is one or more messages in the list */ - struct Curl_llist_node *e; - struct Curl_message *msg; - - /* extract the head of the list to return */ - e = Curl_llist_head(&multi->msglist); - - msg = Curl_node_elem(e); - - /* remove the extracted entry */ - Curl_node_remove(e); - - *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); + uint32_t mid; + if(Curl_uint32_bset_first(&multi->msgsent, &mid)) { + struct Curl_easy *data = Curl_multi_get_easy(multi, mid); - msg_result = &msg->extmsg; + DEBUGASSERT(data); + Curl_uint32_bset_remove(&multi->msgsent, mid); + *msgs_in_queue = + curlx_uztosi(Curl_uint32_bset_count(&multi->msgsent)); + if(data) + msg_result = &data->msg; } } CURL_MAPI_LEAVE(&guard); @@ -3232,9 +3211,15 @@ static CURLMcode multi_run_dirty(struct Curl_multi *multi, CURL_TRC_M(data, "multi_run_dirty"); if(!Curl_uint32_bset_contains(&multi->process, mid)) { - /* We are no longer processing this transfer */ - Curl_uint32_bset_remove(&multi->dirty, mid); - continue; + if(Curl_uint32_bset_contains(&multi->pending, mid)) { + /* Something happened on the pending transfer, act on it. */ + move_pending_to_connect(multi, data); + } + else { + /* We are no longer processing this transfer */ + Curl_uint32_bset_remove(&multi->dirty, mid); + continue; + } } (*pnum)++; @@ -3267,6 +3252,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, { CURLMcode mresult = CURLM_OK; struct Curl_sigpipe_ctx pipe_ctx; + struct curltime now; uint32_t run_xfers; (void)ev_bitmask; @@ -3304,7 +3290,8 @@ static CURLMcode multi_socket(struct Curl_multi *multi, goto out; } - multi_mark_expired_as_dirty(multi, multi_now(multi)); + curlx_pnow(&now); + multi_mark_expired_as_dirty(multi, &now); mresult = multi_run_dirty(multi, &pipe_ctx, &run_xfers); if(mresult) goto out; @@ -3315,7 +3302,8 @@ static CURLMcode multi_socket(struct Curl_multi *multi, * to set a 0 timeout and call us again, we run them here. * Do that only once or it might be unfair to transfers on other * sockets. */ - multi_mark_expired_as_dirty(multi, &multi->now); + curlx_pnow(&now); + multi_mark_expired_as_dirty(multi, &now); mresult = multi_run_dirty(multi, &pipe_ctx, &run_xfers); } @@ -3329,7 +3317,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi, mresult = Curl_mntfy_dispatch_all(multi); if(running_handles) { - uint32_t running = Curl_multi_xfers_running(multi); + uint32_t running = multi_xfers_running(multi); *running_handles = (running < INT_MAX) ? (int)running : INT_MAX; } @@ -3348,6 +3336,7 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) struct Curl_multi *multi = m; va_list param; unsigned long uarg; + size_t szarg; va_start(param, option); @@ -3375,16 +3364,20 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) break; case CURLMOPT_MAXCONNECTS: uarg = va_arg(param, unsigned long); - if(uarg <= UINT_MAX) - multi->maxconnects = (unsigned int)uarg; + if(uarg <= UINT32_MAX) + multi->maxconnects = (uint32_t)uarg; break; case CURLMOPT_MAX_HOST_CONNECTIONS: - if(!curlx_sltouz(va_arg(param, long), &multi->max_host_connections)) + if(!curlx_sltouz(va_arg(param, long), &szarg)) mresult = CURLM_BAD_FUNCTION_ARGUMENT; + multi->max_host_connections = (szarg < UINT32_MAX) ? + (uint32_t)szarg : UINT32_MAX; break; case CURLMOPT_MAX_TOTAL_CONNECTIONS: - if(!curlx_sltouz(va_arg(param, long), &multi->max_total_connections)) + if(!curlx_sltouz(va_arg(param, long), &szarg)) mresult = CURLM_BAD_FUNCTION_ARGUMENT; + multi->max_total_connections = (szarg < UINT32_MAX) ? + (uint32_t)szarg : UINT32_MAX; break; /* options formerly used for pipelining */ case CURLMOPT_MAX_PIPELINE_LENGTH: @@ -3398,10 +3391,12 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) case CURLMOPT_PIPELINING_SERVER_BL: break; case CURLMOPT_MAX_CONCURRENT_STREAMS: { - long streams = va_arg(param, long); - if((streams < 1) || (streams > INT_MAX)) - streams = 100; - multi->max_concurrent_streams = (unsigned int)streams; + if(!curlx_sltouz(va_arg(param, long), &szarg) || + !szarg || (szarg > (size_t)INT_MAX)) /* preserve previous cutoff */ + multi->max_concurrent_streams = 100; + else + multi->max_concurrent_streams = (szarg < UINT32_MAX) ? + (uint32_t)szarg : UINT32_MAX; break; } case CURLMOPT_NETWORK_CHANGED: { @@ -3536,17 +3531,20 @@ static void multi_timeout(struct Curl_multi *multi, } if(multi_has_dirties(multi)) { - if(pexire_offset_us) - *pexire_offset_us = Curl_timeouts_offset_us(&multi->timeouts, - multi_now(multi)); + if(pexire_offset_us) { + struct curltime now; + curlx_pnow(&now); + *pexire_offset_us = Curl_timeouts_offset_us(&multi->timeouts, &now); + } *timeout_ms = 0; return; } else { - const struct curltime *pnow = multi_now(multi); + struct curltime now; uint32_t mid; - *timeout_ms = Curl_timeouts_next_ms(&multi->timeouts, pnow, + curlx_pnow(&now); + *timeout_ms = Curl_timeouts_next_ms(&multi->timeouts, &now, pexire_offset_us, &mid); #ifdef CURLVERBOSE if(mid != UINT32_MAX) { @@ -3593,7 +3591,7 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) return CURLM_OK; multi_timeout(multi, &timeouts_offset_us, &timeout_ms); - if(timeout_ms < 0 && multi->last_timeout_ms < 0) { + if(timeout_ms < 0 && !multi->last_timeout_set) { /* nothing to do */ } else if(timeout_ms < 0) { @@ -3602,7 +3600,7 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) timeout_ms = -1; /* normalize */ set_value = TRUE; } - else if(multi->last_timeout_ms < 0) { + else if(!multi->last_timeout_set) { CURL_TRC_M(multi->admin, "[TIMER] set %dms, none before", timeout_ms); set_value = TRUE; } @@ -3624,7 +3622,7 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi) struct Curl_mapi_guard guard; multi->last_expire_offset_us = timeouts_offset_us; - multi->last_timeout_ms = timeout_ms; + multi->last_timeout_set = timeout_ms >= 0; CURL_CBAPI_MULTI_START(&guard, multi, multi_timer_cb); rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); CURL_CBAPI_MULTI_END(&guard); @@ -3909,7 +3907,7 @@ static void multi_schedule_pending(struct Curl_multi *multi) } } -unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) +uint32_t Curl_multi_max_concurrent_streams(struct Curl_multi *multi) { DEBUGASSERT(multi); return multi->max_concurrent_streams; @@ -3994,6 +3992,17 @@ CURLMcode curl_multi_get_offt(CURLM *m, return mresult; } +static struct Curl_fixed_buf *fixed_buf_create(size_t len) +{ + struct Curl_fixed_buf *fbuf; + if((SIZE_MAX - sizeof(*fbuf)) < len) + return NULL; /* too large */ + fbuf = curlx_malloc(len + sizeof(*fbuf)); + if(fbuf) + fbuf->len = len; + return fbuf; +} + CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, char **pbuf, size_t *pbuflen) { @@ -4015,25 +4024,24 @@ CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, } if(data->multi->xfer_buf && - data->set.buffer_size > data->multi->xfer_buf_len) { + data->set.buffer_size > data->multi->xfer_buf->len) { /* not large enough, get a new one */ curlx_safefree(data->multi->xfer_buf); - data->multi->xfer_buf_len = 0; } if(!data->multi->xfer_buf) { - data->multi->xfer_buf = curlx_malloc(curlx_uitouz(data->set.buffer_size)); + data->multi->xfer_buf = + fixed_buf_create(curlx_uitouz(data->set.buffer_size)); if(!data->multi->xfer_buf) { failf(data, "could not allocate xfer_buf of %u bytes", data->set.buffer_size); return CURLE_OUT_OF_MEMORY; } - data->multi->xfer_buf_len = data->set.buffer_size; } data->multi->xfer_buf_borrowed = TRUE; - *pbuf = data->multi->xfer_buf; - *pbuflen = data->multi->xfer_buf_len; + *pbuf = data->multi->xfer_buf->data; + *pbuflen = data->multi->xfer_buf->len; return CURLE_OK; } @@ -4042,7 +4050,8 @@ void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf) (void)buf; DEBUGASSERT(data); DEBUGASSERT(data->multi); - DEBUGASSERT(!buf || data->multi->xfer_buf == buf); + DEBUGASSERT(!buf || (data->multi->xfer_buf && + data->multi->xfer_buf->data == buf)); data->multi->xfer_buf_borrowed = FALSE; } @@ -4057,36 +4066,30 @@ CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, failf(data, "transfer has no multi handle"); return CURLE_FAILED_INIT; } - if(!data->set.upload_buffer_size) { - failf(data, "transfer upload buffer size is 0"); - return CURLE_FAILED_INIT; - } if(data->multi->xfer_ulbuf_borrowed) { failf(data, "attempt to borrow xfer_ulbuf when already borrowed"); return CURLE_AGAIN; } if(data->multi->xfer_ulbuf && - data->set.upload_buffer_size > data->multi->xfer_ulbuf_len) { + data->set.upload_buffer_size > data->multi->xfer_ulbuf->len) { /* not large enough, get a new one */ curlx_safefree(data->multi->xfer_ulbuf); - data->multi->xfer_ulbuf_len = 0; } if(!data->multi->xfer_ulbuf) { data->multi->xfer_ulbuf = - curlx_malloc(curlx_uitouz(data->set.upload_buffer_size)); + fixed_buf_create(curlx_uitouz(data->set.upload_buffer_size)); if(!data->multi->xfer_ulbuf) { failf(data, "could not allocate xfer_ulbuf of %u bytes", data->set.upload_buffer_size); return CURLE_OUT_OF_MEMORY; } - data->multi->xfer_ulbuf_len = data->set.upload_buffer_size; } data->multi->xfer_ulbuf_borrowed = TRUE; - *pbuf = data->multi->xfer_ulbuf; - *pbuflen = data->multi->xfer_ulbuf_len; + *pbuf = data->multi->xfer_ulbuf->data; + *pbuflen = data->multi->xfer_ulbuf->len; return CURLE_OK; } @@ -4095,7 +4098,8 @@ void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf) (void)buf; DEBUGASSERT(data); DEBUGASSERT(data->multi); - DEBUGASSERT(!buf || data->multi->xfer_ulbuf == buf); + DEBUGASSERT(!buf || (data->multi->xfer_ulbuf && + data->multi->xfer_ulbuf->data == buf)); data->multi->xfer_ulbuf_borrowed = FALSE; } @@ -4115,23 +4119,21 @@ CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, return CURLE_AGAIN; } - if(data->multi->xfer_sockbuf && blen > data->multi->xfer_sockbuf_len) { + if(data->multi->xfer_sockbuf && blen > data->multi->xfer_sockbuf->len) { /* not large enough, get a new one */ curlx_safefree(data->multi->xfer_sockbuf); - data->multi->xfer_sockbuf_len = 0; } if(!data->multi->xfer_sockbuf) { - data->multi->xfer_sockbuf = curlx_malloc(blen); + data->multi->xfer_sockbuf = fixed_buf_create(blen); if(!data->multi->xfer_sockbuf) { failf(data, "could not allocate xfer_sockbuf of %zu bytes", blen); return CURLE_OUT_OF_MEMORY; } - data->multi->xfer_sockbuf_len = blen; } data->multi->xfer_sockbuf_borrowed = TRUE; - *pbuf = data->multi->xfer_sockbuf; + *pbuf = data->multi->xfer_sockbuf->data; return CURLE_OK; } @@ -4144,7 +4146,8 @@ void Curl_multi_xfer_sockbuf_release(struct Curl_easy *data, char *buf) curlx_free(buf); } else { - DEBUGASSERT(!buf || data->multi->xfer_sockbuf == buf); + DEBUGASSERT(!buf || (data->multi->xfer_sockbuf && + data->multi->xfer_sockbuf->data == buf)); data->multi->xfer_sockbuf_borrowed = FALSE; } } @@ -4153,13 +4156,10 @@ static void multi_xfer_bufs_free(struct Curl_multi *multi) { DEBUGASSERT(multi); curlx_safefree(multi->xfer_buf); - multi->xfer_buf_len = 0; multi->xfer_buf_borrowed = FALSE; curlx_safefree(multi->xfer_ulbuf); - multi->xfer_ulbuf_len = 0; multi->xfer_ulbuf_borrowed = FALSE; curlx_safefree(multi->xfer_sockbuf); - multi->xfer_sockbuf_len = 0; multi->xfer_sockbuf_borrowed = FALSE; } @@ -4180,7 +4180,7 @@ bool Curl_multi_knows_easy(struct Curl_multi *multi, struct Curl_easy *data) return Curl_uint32_tbl_get(&multi->xfers, data->mid) == data; } -uint32_t Curl_multi_xfers_running(struct Curl_multi *multi) +static uint32_t multi_xfers_running(struct Curl_multi *multi) { if(!multi) { DEBUGASSERT(0); diff --git a/lib/multi_ev.c b/lib/multi_ev.c index 3b7eeb0dc3a6..7215ee25e383 100644 --- a/lib/multi_ev.c +++ b/lib/multi_ev.c @@ -56,21 +56,6 @@ struct mev_sh_entry { callback at least once */ }; -static size_t mev_sh_entry_hash(void *key, size_t key_length, size_t slots_num) -{ - curl_socket_t fd = *((curl_socket_t *)key); - (void)key_length; - return (fd % (curl_socket_t)slots_num); -} - -static size_t mev_sh_entry_compare(void *k1, size_t k1_len, - void *k2, size_t k2_len) -{ - (void)k1_len; - (void)k2_len; - return (*((curl_socket_t *)k1)) == (*((curl_socket_t *)k2)); -} - /* sockhash entry destructor callback */ static void mev_sh_entry_dtor(void *freethis) { @@ -88,7 +73,7 @@ static struct mev_sh_entry *mev_sh_entry_get(struct Curl_hash *sh, { if(s != CURL_SOCKET_BAD) { /* only look for proper sockets */ - return Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); + return Curl_hash_pick(sh, (const char *)&s, sizeof(curl_socket_t)); } return NULL; } @@ -146,6 +131,52 @@ static bool mev_sh_entry_conn_known(struct mev_sh_entry *e, return (e->conn == conn); } +static void mev_pollset_dtor(const void *key, size_t klen, void *entry) +{ + struct easy_pollset *ps = entry; + (void)key; + (void)klen; + if(ps) { + Curl_pollset_cleanup(ps); + curlx_free(ps); + } +} + +static struct easy_pollset *mev_add_new_conn_pollset(struct connectdata *conn) +{ + struct easy_pollset *ps; + + ps = Curl_pollset_create(); + if(!ps) + return NULL; + if(Curl_conn_meta_set(conn, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) + return NULL; + return ps; +} + +static struct easy_pollset *mev_add_new_xfer_pollset(struct Curl_easy *data) +{ + struct easy_pollset *ps; + + ps = Curl_pollset_create(); + if(!ps) + return NULL; + if(Curl_meta_set(data, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) + return NULL; + return ps; +} + +static struct easy_pollset *mev_get_last_pollset(struct Curl_easy *data, + struct connectdata *conn) +{ + if(data) { + if(conn) + return Curl_conn_meta_get(conn, CURL_META_MEV_POLLSET); + return Curl_meta_get(data, CURL_META_MEV_POLLSET); + } + return NULL; +} + static bool mev_sh_entry_xfer_add(struct mev_sh_entry *e, struct Curl_easy *data) { @@ -194,11 +225,30 @@ static CURLMcode mev_forget_socket(struct Curl_multi *multi, const char *cause) { struct mev_sh_entry *entry = mev_sh_entry_get(&multi->ev.sh_entries, s); + uint32_t mid; int rc = 0; if(!entry) /* we never knew or already forgot about this socket */ return CURLM_OK; + /* Remove the socket from any pollset that is still registered. */ + if(Curl_uint32_spbset_first(&entry->xfers, &mid)) { + do { + struct Curl_easy *sdata = Curl_multi_get_easy(multi, mid); + if(sdata) { + struct easy_pollset *ps = mev_get_last_pollset(sdata, NULL); + if(ps) + Curl_pollset_remove(ps, s); + } + } while(Curl_uint32_spbset_next(&entry->xfers, mid, &mid)); + } + + if(entry->conn) { + struct easy_pollset *ps = mev_get_last_pollset(data, entry->conn); + if(ps) + Curl_pollset_remove(ps, s); + } + /* We managed this socket before, tell the socket callback to forget it. */ if(entry->announced && multi->socket_cb) { struct Curl_mapi_guard guard; @@ -448,52 +498,6 @@ static CURLMcode mev_pollset_diff(struct Curl_multi *multi, return CURLM_OK; } -static void mev_pollset_dtor(void *key, size_t klen, void *entry) -{ - struct easy_pollset *ps = entry; - (void)key; - (void)klen; - if(ps) { - Curl_pollset_cleanup(ps); - curlx_free(ps); - } -} - -static struct easy_pollset *mev_add_new_conn_pollset(struct connectdata *conn) -{ - struct easy_pollset *ps; - - ps = Curl_pollset_create(); - if(!ps) - return NULL; - if(Curl_conn_meta_set(conn, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) - return NULL; - return ps; -} - -static struct easy_pollset *mev_add_new_xfer_pollset(struct Curl_easy *data) -{ - struct easy_pollset *ps; - - ps = Curl_pollset_create(); - if(!ps) - return NULL; - if(Curl_meta_set(data, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor)) - return NULL; - return ps; -} - -static struct easy_pollset *mev_get_last_pollset(struct Curl_easy *data, - struct connectdata *conn) -{ - if(data) { - if(conn) - return Curl_conn_meta_get(conn, CURL_META_MEV_POLLSET); - return Curl_meta_get(data, CURL_META_MEV_POLLSET); - } - return NULL; -} - static CURLMcode mev_assess(struct Curl_multi *multi, struct Curl_easy *data, struct connectdata *conn) @@ -637,8 +641,8 @@ void Curl_multi_ev_conn_done(struct Curl_multi *multi, void Curl_multi_ev_init(struct Curl_multi *multi, size_t hashsize) { - Curl_hash_init(&multi->ev.sh_entries, hashsize, mev_sh_entry_hash, - mev_sh_entry_compare, mev_sh_entry_dtor); + Curl_hash_init(&multi->ev.sh_entries, hashsize, CURL_HASH_TYPE_SOCKET, + mev_sh_entry_dtor); } void Curl_multi_ev_cleanup(struct Curl_multi *multi) diff --git a/lib/multi_ntfy.c b/lib/multi_ntfy.c index 48c37239519a..4723c8025bdd 100644 --- a/lib/multi_ntfy.c +++ b/lib/multi_ntfy.c @@ -29,6 +29,11 @@ #include "multiif.h" #include "multi_ntfy.h" +#if CURLMNOTIFY_LAST > 31 +#error "notification types must fit below the curl_multi_ntfy high flag bit" +#endif + +#define CURL_MNTFY_TYPE_FLAG(type) ((uint32_t)1 << (type)) struct mntfy_entry { uint32_t mid; @@ -56,7 +61,7 @@ static void mnfty_chunk_destroy(struct mntfy_chunk *chunk) static void mnfty_chunk_reset(struct mntfy_chunk *chunk) { - memset(chunk, 0, sizeof(*chunk)); + chunk->r_offset = chunk->w_offset = 0; } static bool mntfy_chunk_append(struct mntfy_chunk *chunk, @@ -103,20 +108,19 @@ static void mntfy_chunk_dispatch_all(struct Curl_multi *multi, struct mntfy_entry *e; struct Curl_easy *data; - if(multi->ntfy.ntfy_cb) { - while((chunk->r_offset < chunk->w_offset) && !multi->ntfy.failure) { - e = &chunk->entries[chunk->r_offset]; - data = e->mid ? Curl_multi_get_easy(multi, e->mid) : multi->admin; - /* only when notification has not been disabled in the meantime */ - if(data && Curl_uint32_bset_contains(&multi->ntfy.enabled, e->type)) { - /* this may cause new notifications to be added! */ - CURL_TRC_M(multi->admin, "[NTFY] dispatch %u to xfer %u", - e->type, e->mid); - multi->ntfy.ntfy_cb(multi, e->type, data, multi->ntfy.ntfy_cb_data); - } - /* once dispatched, safe to increment */ - chunk->r_offset++; + while(multi->ntfy.ntfy_cb && + (chunk->r_offset < chunk->w_offset) && !multi->ntfy.failure) { + e = &chunk->entries[chunk->r_offset]; + data = e->mid ? Curl_multi_get_easy(multi, e->mid) : multi->admin; + /* only when notification has not been disabled in the meantime */ + if(data && (multi->ntfy.flags & CURL_MNTFY_TYPE_FLAG(e->type))) { + /* this may cause new notifications to be added! */ + CURL_TRC_M(multi->admin, "[NTFY] dispatch %u to xfer %u", + e->type, e->mid); + multi->ntfy.ntfy_cb(multi, e->type, data, multi->ntfy.ntfy_cb_data); } + /* once dispatched, safe to increment */ + chunk->r_offset++; } mnfty_chunk_reset(chunk); } @@ -124,14 +128,6 @@ static void mntfy_chunk_dispatch_all(struct Curl_multi *multi, void Curl_mntfy_init(struct Curl_multi *multi) { memset(&multi->ntfy, 0, sizeof(multi->ntfy)); - Curl_uint32_bset_init(&multi->ntfy.enabled); -} - -CURLMcode Curl_mntfy_resize(struct Curl_multi *multi) -{ - if(Curl_uint32_bset_resize(&multi->ntfy.enabled, CURLMNOTIFY_EASY_DONE + 1)) - return CURLM_OUT_OF_MEMORY; - return CURLM_OK; } void Curl_mntfy_cleanup(struct Curl_multi *multi) @@ -142,22 +138,21 @@ void Curl_mntfy_cleanup(struct Curl_multi *multi) mnfty_chunk_destroy(chunk); } multi->ntfy.tail = NULL; - Curl_uint32_bset_destroy(&multi->ntfy.enabled); } CURLMcode Curl_mntfy_enable(struct Curl_multi *multi, unsigned int type) { - if(type > CURLMNOTIFY_EASY_DONE) + if(type >= CURLMNOTIFY_LAST) return CURLM_UNKNOWN_OPTION; - Curl_uint32_bset_add(&multi->ntfy.enabled, type); + multi->ntfy.flags |= CURL_MNTFY_TYPE_FLAG(type); return CURLM_OK; } CURLMcode Curl_mntfy_disable(struct Curl_multi *multi, unsigned int type) { - if(type > CURLMNOTIFY_EASY_DONE) + if(type >= CURLMNOTIFY_LAST) return CURLM_UNKNOWN_OPTION; - Curl_uint32_bset_remove(&multi->ntfy.enabled, (uint32_t)type); + multi->ntfy.flags &= ~CURL_MNTFY_TYPE_FLAG(type); return CURLM_OK; } @@ -165,7 +160,8 @@ void Curl_mntfy_add(struct Curl_easy *data, unsigned int type) { struct Curl_multi *multi = data ? data->multi : NULL; if(multi && multi->ntfy.ntfy_cb && !multi->ntfy.failure && - Curl_uint32_bset_contains(&multi->ntfy.enabled, (uint32_t)type)) { + type < CURLMNOTIFY_LAST && + (multi->ntfy.flags & CURL_MNTFY_TYPE_FLAG(type))) { /* append to list of outstanding notifications */ struct mntfy_chunk *tail = mntfy_non_full_tail(&multi->ntfy); CURL_TRC_M(data, "[NTFY] add %u for xfer %u", type, data->mid); @@ -173,7 +169,7 @@ void Curl_mntfy_add(struct Curl_easy *data, unsigned int type) mntfy_chunk_append(tail, data, (uint32_t)type); else multi->ntfy.failure = CURLM_OUT_OF_MEMORY; - multi->ntfy.has_entries = TRUE; + multi->ntfy.flags |= CURL_MNTFY_FLAG_HAS_ENTRIES; } } @@ -208,6 +204,6 @@ CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi) return mresult; } else - multi->ntfy.has_entries = FALSE; + multi->ntfy.flags &= ~CURL_MNTFY_FLAG_HAS_ENTRIES; return CURLM_OK; } diff --git a/lib/multi_ntfy.h b/lib/multi_ntfy.h index 05df2d6681b6..acdd6bd92c2e 100644 --- a/lib/multi_ntfy.h +++ b/lib/multi_ntfy.h @@ -23,7 +23,9 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#include "uint-bset.h" +#include "curl_setup.h" + +#define CURL_MNTFY_FLAG_HAS_ENTRIES ((uint32_t)1 << 31) struct Curl_easy; struct Curl_multi; @@ -31,15 +33,13 @@ struct Curl_multi; struct curl_multi_ntfy { curl_notify_callback ntfy_cb; void *ntfy_cb_data; - struct uint32_bset enabled; struct mntfy_chunk *head; struct mntfy_chunk *tail; + uint32_t flags; CURLMcode failure; - BIT(has_entries); }; void Curl_mntfy_init(struct Curl_multi *multi); -CURLMcode Curl_mntfy_resize(struct Curl_multi *multi); void Curl_mntfy_cleanup(struct Curl_multi *multi); CURLMcode Curl_mntfy_enable(struct Curl_multi *multi, unsigned int type); @@ -53,7 +53,8 @@ void Curl_mntfy_add(struct Curl_easy *data, unsigned int type); Curl_mntfy_add((d), (t)); \ } while(0) -#define CURL_MNTFY_HAS_ENTRIES(m) ((m)->ntfy.has_entries) +#define CURL_MNTFY_HAS_ENTRIES(m) \ + ((m)->ntfy.flags & CURL_MNTFY_FLAG_HAS_ENTRIES) CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi); diff --git a/lib/multihandle.h b/lib/multihandle.h index 2a8fba158300..6a0bf5118cc6 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -41,12 +41,6 @@ struct connectdata; struct Curl_easy; -struct Curl_message { - struct Curl_llist_node list; - /* the 'CURLMsg' is the part that is visible to the external user */ - struct CURLMsg extmsg; -}; - /* NOTE: if you add a state here, add the name to the statenames[] array * in curl_trc.c as well! */ @@ -84,6 +78,11 @@ typedef enum { /* value for MAXIMUM CONCURRENT STREAMS upper limit */ #define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1) +struct Curl_fixed_buf { + size_t len; /* the allocated data length */ + char data[1]; /* the actual buffer */ +}; + /* This is the struct known as CURLM on the outside */ struct Curl_multi { /* First a simple identifier to more easily detect if a user mixes up @@ -95,19 +94,20 @@ struct Curl_multi { uint32_t xfers_really_alive; /* amount of added transfers that have passed INIT state but are not COMPLETE yet */ uint32_t max_concurrent_streams; + curl_off_t xfers_total_ever; /* total of added transfers, ever. */ struct uint32_tbl xfers; /* transfers added to this multi */ - /* Each transfer's mid may be present in at most one of these */ + /* A transfer's mid may be present in at most one of + * process/pending/msgsent. The dirty set may overlap with process. */ struct uint32_bset process; /* transfer being processed */ struct uint32_bset dirty; /* transfer to be run NOW, e.g. ASAP. */ struct uint32_bset pending; /* transfers in waiting (conn limit etc.) */ - struct uint32_bset msgsent; /* transfers done with message for application */ + struct uint32_bset msgsent; /* transfers with unread application messages */ struct Curl_mapi_stack callstack; /* multi api calls ongoing */ - struct Curl_llist msglist; /* a list of messages from completed transfers */ - - curl_off_t xfers_total_ever; /* total of added transfers, ever. */ + /* expiration times for all attached easy handles */ + struct Curl_timeouts timeouts; struct Curl_easy *admin; /* internal easy handle for admin operations. gets assigned `mid` 0 on multi init */ @@ -131,20 +131,9 @@ struct Curl_multi { struct PslCache psl; #endif - /* current time for transfers running in this multi handle */ - struct curltime now; - /* expiration times for all attached easy handles */ - struct Curl_timeouts timeouts; - - /* buffer used for transfer data, lazy initialized */ - char *xfer_buf; /* the actual buffer */ - size_t xfer_buf_len; /* the allocated length */ - /* buffer used for upload data, lazy initialized */ - char *xfer_ulbuf; /* the actual buffer */ - size_t xfer_ulbuf_len; /* the allocated length */ - /* buffer used for socket I/O operations, lazy initialized */ - char *xfer_sockbuf; /* the actual buffer */ - size_t xfer_sockbuf_len; /* the allocated length */ + struct Curl_fixed_buf *xfer_buf; /* transfer downloads, lazy */ + struct Curl_fixed_buf *xfer_ulbuf; /* transfer uploads, lazy */ + struct Curl_fixed_buf *xfer_sockbuf; /* transfer socket I/O, lazy */ /* multi event related things */ struct curl_multi_ev ev; @@ -161,17 +150,12 @@ struct Curl_multi { struct cshutdn cshutdn; /* connection shutdown handling */ struct cpool cpool; /* connection pool (bundles) */ - timediff_t last_expire_offset_us; /* times offset of last expiry */ - size_t max_host_connections; /* if >0, a fixed limit of the maximum number - of connections per host */ - size_t max_total_connections; /* if >0, a fixed limit of the maximum number - of connections in total */ + timediff_t last_expire_offset_us; /* times offset of last expiry */ /* timer callback and user data pointer for the *socket() API */ curl_multi_timer_callback timer_cb; void *timer_userp; - int last_timeout_ms; /* the last timeout value set via timer_cb */ #ifdef USE_WINSOCK WSAEVENT wsa_event; /* Winsock event used for waits */ @@ -187,12 +171,13 @@ struct Curl_multi { for write. Used for internal wakeups, e.g. threaded resolver. */ #endif - unsigned int maxconnects; /* if >0, a fixed limit of the maximum number of + uint32_t max_host_connections; /* if >0, a fixed limit of the maximum number + of connections per host */ + uint32_t max_total_connections; /* if >0, a fixed limit of the maximum number + of connections in total */ + uint32_t maxconnects; /* if >0, a fixed limit of the maximum number of entries we are allowed to grow the connection cache to */ -#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); @@ -203,6 +188,7 @@ struct Curl_multi { #endif BIT(dead); /* a callback returned error, everything needs to crash and burn */ + BIT(last_timeout_set); /* last timer callback value was nonnegative */ 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 */ diff --git a/lib/multiif.h b/lib/multiif.h index d3ad8fffb334..30b453e7589a 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -73,7 +73,7 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, struct connectdata *conn); /* Return the value of the CURLMOPT_MAX_CONCURRENT_STREAMS option */ -unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi); +uint32_t Curl_multi_max_concurrent_streams(struct Curl_multi *multi); CURLMcode Curl_multi_pollset(struct Curl_easy *data, struct easy_pollset *ps); @@ -89,7 +89,7 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data, * @param pbuf on return, the buffer to use or NULL on error * @param pbuflen on return, the size of *pbuf or 0 on error * @return CURLE_OK when buffer is available and is returned. - * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_OUT_OF_MEMORY on failure to allocate the buffer, * CURLE_FAILED_INIT if the easy handle is without multi. * CURLE_AGAIN if the buffer is borrowed already. */ @@ -113,7 +113,7 @@ void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf); * @param pbuf on return, the buffer to use or NULL on error * @param pbuflen on return, the size of *pbuf or 0 on error * @return CURLE_OK when buffer is available and is returned. - * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_OUT_OF_MEMORY on failure to allocate the buffer, * CURLE_FAILED_INIT if the easy handle is without multi. * CURLE_AGAIN if the buffer is borrowed already. */ @@ -138,8 +138,7 @@ void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf); * @param blen requested length of the buffer * @param pbuf on return, the buffer to use or NULL on error * @return CURLE_OK when buffer is available and is returned. - * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, - * CURLE_FAILED_INIT if the easy handle is without multi. + * CURLE_OUT_OF_MEMORY on failure to allocate the buffer, * CURLE_AGAIN if the buffer is borrowed already. */ CURLcode Curl_multi_xfer_sockbuf_borrow(struct Curl_easy *data, @@ -165,9 +164,6 @@ bool Curl_multi_knows_easy(struct Curl_multi *multi, struct Curl_easy *data); * admin handle. */ uint32_t Curl_multi_xfers_attached(struct Curl_multi *multi); -/* Get the # of transfers current in process/pending. */ -uint32_t Curl_multi_xfers_running(struct Curl_multi *multi); - /* Mark a transfer as dirty, e.g. to be rerun at earliest convenience. * A cheap operation, can be done many times repeatedly. */ void Curl_multi_mark_dirty(struct Curl_easy *data); diff --git a/lib/netrc.c b/lib/netrc.c index 7c8e1fb2ab3f..6a6049a7d2eb 100644 --- a/lib/netrc.c +++ b/lib/netrc.c @@ -41,7 +41,7 @@ #include "curl_trc.h" #include "strcase.h" #include "curl_get_line.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/strparse.h" @@ -223,7 +223,9 @@ static NETRCcode netrc_lexer_quoted(struct netrc_lexer *lexer) } else if(c == '\"') { ++s; /* pass the ending quote */ - rc = NETRC_OK; + /* Ensure even an empty literal has a null-terminator. */ + result = curlx_dyn_addn(&lexer->literal, "", 0); + rc = curl2netrc(result); goto out; } result = curlx_dyn_addn(&lexer->literal, &c, 1); @@ -562,6 +564,8 @@ static NETRCcode netrc_scan_file(struct Curl_easy *data, } store->loaded = TRUE; } + if(!curlx_dyn_len(filebuf)) + return NETRC_NO_MATCH; return netrc_scan(data, curlx_dyn_ptr(filebuf), hostname, user, pcreds); } diff --git a/lib/openldap.c b/lib/openldap.c index 887336e5ca7a..f67c841e6ac8 100644 --- a/lib/openldap.c +++ b/lib/openldap.c @@ -100,6 +100,7 @@ struct ldapconninfo { Curl_send *send; struct berval *servercred; /* SASL data from server. */ ldapstate state; /* Current machine state. */ + curl_socket_t dupfd; /* duplicated socket */ int proto; /* LDAP_PROTO_TCP/LDAP_PROTO_UDP/LDAP_PROTO_IPC */ int msgid; /* Current message id. */ }; @@ -425,10 +426,21 @@ static int ldapsb_tls_remove(Sockbuf_IO_Desc *sbiod) return 0; } -/* We do not need to do anything because libcurl does it already */ +/* if there is a duplicated socket to close, close it */ static int ldapsb_tls_close(Sockbuf_IO_Desc *sbiod) { - (void)sbiod; + struct Curl_easy *data = sbiod->sbiod_pvt; + if(data) { + struct connectdata *conn = data->conn; + if(conn) { + struct ldapconninfo *li = Curl_conn_meta_get(conn, CURL_META_LDAP_CONN); + if(li && (li->dupfd != CURL_SOCKET_BAD)) { + sclose(li->dupfd); + li->dupfd = CURL_SOCKET_BAD; + } + } + } + return 0; } @@ -554,7 +566,7 @@ static CURLcode oldap_perform_starttls(struct Curl_easy *data) } #endif -static void oldap_easy_dtor(void *key, size_t klen, void *entry) +static void oldap_easy_dtor(const void *key, size_t klen, void *entry) { struct ldapreqinfo *lr = entry; (void)key; @@ -562,7 +574,7 @@ static void oldap_easy_dtor(void *key, size_t klen, void *entry) curlx_free(lr); } -static void oldap_conn_dtor(void *key, size_t klen, void *entry) +static void oldap_conn_dtor(const void *key, size_t klen, void *entry) { struct ldapconninfo *li = entry; (void)key; @@ -607,6 +619,7 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) result = CURLE_OUT_OF_MEMORY; goto out; } + li->dupfd = CURL_SOCKET_BAD; result = Curl_conn_meta_set(conn, CURL_META_LDAP_CONN, li, oldap_conn_dtor); if(result) @@ -632,7 +645,43 @@ static CURLcode oldap_connect(struct Curl_easy *data, bool *done) goto out; } - rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld); + { +#ifdef _WIN32 + SOCKET dupfd = INVALID_SOCKET; + WSAPROTOCOL_INFO pi; + if(WSADuplicateSocket(conn->sock[FIRSTSOCKET], GetCurrentProcessId(), + &pi)) + ; /* error */ + else + dupfd = CURL_SOCKET(pi.iAddressFamily, pi.iSocketType, pi.iProtocol); +#else +#ifdef F_DUPFD_CLOEXEC + int dupfd = fcntl(conn->sock[FIRSTSOCKET], F_DUPFD_CLOEXEC, 0); +#else + int dupfd = dup(conn->sock[FIRSTSOCKET]); +#ifdef HAVE_FCNTL + if((dupfd != CURL_SOCKET_BAD) && + (fcntl(dupfd, F_SETFD, FD_CLOEXEC) < 0)) { + sclose(dupfd); + dupfd = CURL_SOCKET_BAD; + } +#endif /* HAVE_FCNTL */ +#endif /* F_DUPFD_CLOEXEC */ +#endif /* _WIN32 */ + + if(dupfd == CURL_SOCKET_BAD) { + result = CURLE_COULDNT_CONNECT; + goto out; + } + rc = ldap_init_fd((ber_socket_t)dupfd, li->proto, hosturl, &li->ld); + if(rc) + sclose(dupfd); + else + /* store the socket, as when this is used by TLS we close this ourselves + and at this point the code does not yet know if this will use TLS or + not */ + li->dupfd = dupfd; + } if(rc) { failf(data, "LDAP local: Cannot connect to %s, %s", hosturl, ldap_err2string(rc)); @@ -1100,6 +1149,23 @@ static CURLcode client_write(struct Curl_easy *data, return result; } +/* RFC 2849: a string that is not a SAFE-STRING (leading ':' or '<', leading or + trailing space, or any non-printable byte such as CR/LF) cannot go into LDIF + as-is. DNs and values get base64-encoded instead; an attribute type has no + base64 form in LDIF and is rejected by the caller. */ +static bool oldap_str_needs_base64(const char *s, size_t len) +{ + size_t i; + if(!len) + return FALSE; + if((s[0] == ':') || (s[0] == '<') || ISBLANK(s[0]) || ISBLANK(s[len - 1])) + return TRUE; + for(i = 0; i < len; i++) + if(!ISPRINT(s[i])) + return TRUE; + return FALSE; +} + static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, size_t len, size_t *pnread) { @@ -1172,8 +1238,21 @@ static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, break; } - result = client_write(data, STRCONST("DN: "), bv.bv_val, bv.bv_len, - STRCONST("\n")); + if(oldap_str_needs_base64(bv.bv_val, bv.bv_len)) { + char *dn_b64 = NULL; + size_t dn_b64_sz = 0; + + /* A DN with e.g. CR/LF would inject LDIF lines, so base64-encode it. */ + result = curlx_base64_encode((uint8_t *)bv.bv_val, bv.bv_len, + &dn_b64, &dn_b64_sz); + if(!result) + result = client_write(data, STRCONST("DN:: "), dn_b64, dn_b64_sz, + STRCONST("\n")); + curlx_free(dn_b64); + } + else + result = client_write(data, STRCONST("DN: "), bv.bv_val, bv.bv_len, + STRCONST("\n")); if(result) break; @@ -1186,6 +1265,17 @@ static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, if(!bv.bv_val) break; + /* An attribute type has no base64 form in LDIF, so a type carrying + control bytes (CR/LF etc.) would let a hostile server inject lines. + A conformant type name never contains them, so reject it. */ + if(oldap_str_needs_base64(bv.bv_val, bv.bv_len)) { + failf(data, "LDAP local: attribute type contains illegal bytes"); + if(bvals) + ber_memfree(bvals); + result = CURLE_WEIRD_SERVER_REPLY; + break; + } + if(!bvals) { result = client_write(data, STRCONST("\t"), bv.bv_val, bv.bv_len, STRCONST(":\n")); @@ -1205,24 +1295,8 @@ static CURLcode oldap_recv(struct Curl_easy *data, int8_t sockindex, char *buf, if(result) break; - if(!binary) { - /* check for a leading ':' or '<' (not a SAFE-INIT-CHAR per RFC - 2849) or leading or trailing whitespace */ - if(bvals[i].bv_len && - ((bvals[i].bv_val[0] == ':') || (bvals[i].bv_val[0] == '<') || - ISBLANK(bvals[i].bv_val[0]) || - ISBLANK(bvals[i].bv_val[bvals[i].bv_len - 1]))) - binval = TRUE; - else { - /* check for unprintable characters */ - unsigned int j; - for(j = 0; j < bvals[i].bv_len; j++) - if(!ISPRINT(bvals[i].bv_val[j])) { - binval = TRUE; - break; - } - } - } + if(!binary) + binval = oldap_str_needs_base64(bvals[i].bv_val, bvals[i].bv_len); if(binary || binval) { char *val_b64 = NULL; size_t val_b64_sz = 0; diff --git a/lib/parsedate.c b/lib/parsedate.c index ec9f21296b5e..3c11d76ad925 100644 --- a/lib/parsedate.c +++ b/lib/parsedate.c @@ -103,126 +103,220 @@ const char * const Curl_month[] = { #define PARSEDATE_SOONER 2 #endif -static const char * const weekday[] = { - "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" -}; - struct tzinfo { - char name[5]; + uint32_t tz; int16_t offset; /* +/- in minutes */ }; -#define tDAYZONE (-60) /* offset for daylight savings time */ - -/* alpha-sorted list of time zones */ -static const struct tzinfo tz[] = { - { "A", -1 * 60 }, /* Alpha */ - { "ADT", 240 + tDAYZONE }, /* Atlantic Daylight */ - { "AHST", 600 }, /* Alaska-Hawaii Standard */ - { "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 */ - { "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 */ +#define DST (-60) /* offset for daylight savings time */ + +/* alpha-sorted list of single-letter time zones, A - Z */ +static const int16_t tzone[] = { + -1 * 60, /* Alpha */ + -2 * 60, /* Bravo */ + -3 * 60, /* Charlie */ + -4 * 60, /* Delta */ + -5 * 60, /* Echo */ + -6 * 60, /* Foxtrot */ + -7 * 60, /* Golf */ + -8 * 60, /* Hotel */ + -9 * 60, /* India */ + -1, /* J - not a timezone */ + -10 * 60, /* Kilo */ + -11 * 60, /* Lima */ + -12 * 60, /* Mike */ + 60, /* November */ + 2 * 60, /* Oscar */ + 3 * 60, /* Papa */ + 4 * 60, /* Quebec */ + 5 * 60, /* Romeo */ + 6 * 60, /* Sierra */ + 7 * 60, /* Tango */ + 8 * 60, /* Uniform */ + 9 * 60, /* Victor */ + 10 * 60, /* Whiskey */ + 11 * 60, /* X-ray */ + 12 * 60, /* Yankee */ + 0 /* Zulu, zero meridian, a.k.a. UTC */ +}; + +/* two-letter time zones + + 11 * 60, NT - Nome spellchecker:disable-line + 0, UT - Universal Time + +*/ + +#define MKTZ(a,b,c,d) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d)) + +/* three-letter time zones */ +static const struct tzinfo tzthree[] = { + { MKTZ('A', 'D', 'T', '\0'), 240 + DST }, /* Atlantic Daylight */ + { MKTZ('A', 'S', 'T', '\0'), 240 }, /* Atlantic Standard */ + { MKTZ('B', 'S', 'T', '\0'), 0 + DST }, /* British Summer */ + { MKTZ('C', 'A', 'T', '\0'), 600 }, /* Central Alaska */ + { MKTZ('C', 'C', 'T', '\0'), -480 }, /* China Coast, USSR Zone 7 */ + { MKTZ('C', 'D', 'T', '\0'), 360 + DST }, /* Central Daylight */ + { MKTZ('C', 'E', 'T', '\0'), -60 }, /* Central European */ + { MKTZ('C', 'S', 'T', '\0'), 360 }, /* Central Standard */ + { MKTZ('E', 'D', 'T', '\0'), 300 + DST }, /* Eastern Daylight */ + { MKTZ('E', 'E', 'T', '\0'), -120 }, /* Eastern Europe, USSR Zone 1 */ + { MKTZ('E', 'S', 'T', '\0'), 300 }, /* Eastern Standard */ + { MKTZ('F', 'S', 'T', '\0'), -60 + DST }, /* French Summer */ + { MKTZ('F', 'W', 'T', '\0'), -60 }, /* French Winter */ + { MKTZ('G', 'M', 'T', '\0'), 0 }, /* Greenwich Mean */ + { MKTZ('G', 'S', 'T', '\0'), -600 }, /* Guam Standard, USSR Zone 9 */ + { MKTZ('H', 'D', 'T', '\0'), 600 + DST }, /* Hawaii Daylight */ + { MKTZ('H', 'S', 'T', '\0'), 600 }, /* Hawaii Standard */ + { MKTZ('J', 'S', 'T', '\0'), -540 }, /* Japan Standard, USSR Zone 8 */ + { MKTZ('M', 'D', 'T', '\0'), 420 + DST }, /* Mountain Daylight */ + { MKTZ('M', 'E', 'T', '\0'), -60 }, /* Middle European */ + { MKTZ('M', 'S', 'T', '\0'), 420 }, /* Mountain Standard */ + { MKTZ('N', 'Z', 'T', '\0'), -720 }, /* New Zealand */ + { MKTZ('P', 'D', 'T', '\0'), 480 + DST }, /* Pacific Daylight */ + { MKTZ('P', 'S', 'T', '\0'), 480 }, /* Pacific Standard */ + { MKTZ('U', 'T', 'C', '\0'), 0 }, /* Universal (Coordinated) */ + { MKTZ('W', 'A', 'T', '\0'), 60 }, /* West Africa */ + { MKTZ('W', 'E', 'T', '\0'), 0 }, /* Western European */ + { MKTZ('Y', 'D', 'T', '\0'), 540 + DST }, /* Yukon Daylight */ + { MKTZ('Y', 'S', 'T', '\0'), 540 }, /* Yukon Standard */ +}; + +/* four-letter time zones */ +static const struct tzinfo tzfoura[] = { + { MKTZ('A', 'H', 'S', 'T'), 600 }, /* Alaska-Hawaii Standard */ + { MKTZ('C', 'E', 'S', 'T'), -60 + DST }, /* Central European Summer */ + { MKTZ('E', 'A', 'D', 'T'), -600 + DST }, /* Eastern Australian Daylight */ + { MKTZ('E', 'A', 'S', 'T'), -600 }, /* Eastern Australian Standard */ + { MKTZ('I', 'D', 'L', 'E'), -720 }, /* International Date Line East */ + { MKTZ('I', 'D', 'L', 'W'), 720 }, /* International Date Line West */ + { MKTZ('M', 'E', 'S', 'T'), -60 + DST }, /* Middle European Summer */ + { MKTZ('M', 'E', 'S', 'Z'), -60 + DST }, /* Middle European Summer */ + { MKTZ('M', 'E', 'W', 'T'), -60 }, /* Middle European Winter */ + { MKTZ('N', 'Z', 'D', 'T'), -720 + DST }, /* New Zealand Daylight */ + { MKTZ('N', 'Z', 'S', 'T'), -720 }, /* New Zealand Standard */ + { MKTZ('W', 'A', 'D', 'T'), -420 + DST }, /* West Australian Daylight */ + { MKTZ('W', 'A', 'S', 'T'), -420 }, /* spellchecker:disable-line */ /* West Australian Standard */ - { "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 */ }; +#define LOWERCASE(x) ((x) | 0x20) + /* returns: -1 no day 0 monday - 6 sunday - */ -static int checkday(const char *check, size_t len) + + @unittest 4781 +*/ +UNITTEST int checkday(const char *check, size_t len); +UNITTEST int checkday(const char *check, size_t len) { - int i; - const char * const *what; - if(len > 3) - what = &weekday[0]; - else if(len == 3) - what = &Curl_wkday[0]; - else + int day = -1; + if(len < 3) return -1; /* too short */ - for(i = 0; i < 7; i++) { - size_t ilen = strlen(what[0]); - if((ilen == len) && - curl_strnequal(check, what[0], len)) - return i; - what++; + + switch(LOWERCASE(check[1])) { + case 'o': /* monday */ + if((LOWERCASE(check[0]) == 'm') && LOWERCASE(check[2]) == 'n') + day = 0; + break; + case 'u': /* tuesday or sunday */ + if((LOWERCASE(check[0]) == 't') && LOWERCASE(check[2]) == 'e') + day = 1; + else if((LOWERCASE(check[0]) == 's') && LOWERCASE(check[2]) == 'n') + day = 6; + break; + case 'e': /* wednesday */ + if((LOWERCASE(check[0]) == 'w') && LOWERCASE(check[2]) == 'd') + day = 2; + break; + case 'h': /* thursday */ + if((LOWERCASE(check[0]) == 't') && LOWERCASE(check[2]) == 'u') + day = 3; + break; + case 'r': /* friday */ + if((LOWERCASE(check[0]) == 'f') && LOWERCASE(check[2]) == 'i') + day = 4; + break; + case 'a': /* saturday */ + if((LOWERCASE(check[0]) == 's') && LOWERCASE(check[2]) == 't') + day = 5; + break; } - return -1; + if((len > 3) && (day != -1)) { + /* when more than three letters are provided, verify the tail name case + insensitively */ + static const char * const daysuffix[] = { + /* without the leading three letters */ + "day", "sday", "nesday", "rsday", "day", "urday", "day" + }; + + size_t wlen = strlen(daysuffix[day]); + if(((len - 3) != wlen) || + !curl_strnequal(&check[3], daysuffix[day], wlen)) + return -1; + } + return day; } -static int checkmonth(const char *check, size_t len) +/* @unittest 4781 */ + +UNITTEST int checkmonth(const char *check, size_t len); +UNITTEST int checkmonth(const char *check, size_t len) { - int i; - const char * const *what = &Curl_month[0]; if(len != 3) return -1; /* not a month */ - for(i = 0; i < 12; i++) { - if(curl_strnequal(check, what[0], 3)) - return i; - what++; + switch(LOWERCASE(check[2])) { + case 'n': /* jan, jun */ + if(LOWERCASE(check[0]) == 'j') { + uint8_t c2 = LOWERCASE(check[1]); + if(c2 == 'a') + return 0; + else if(c2 == 'u') + return 5; + } + break; + case 'b': /* feb */ + if((LOWERCASE(check[0]) == 'f') && LOWERCASE(check[1]) == 'e') + return 1; + break; + case 'r': /* mar, apr */ + if(LOWERCASE(check[0]) == 'm') { + if(LOWERCASE(check[1]) == 'a') + return 2; + } + else if((LOWERCASE(check[0]) == 'a') && LOWERCASE(check[1]) == 'p') + return 3; + break; + case 'y': /* may */ + if((LOWERCASE(check[0]) == 'm') && LOWERCASE(check[1]) == 'a') + return 4; + break; + case 'l': /* jul */ + if((LOWERCASE(check[0]) == 'j') && LOWERCASE(check[1]) == 'u') + return 6; + break; + case 'g': /* aug */ + if((LOWERCASE(check[0]) == 'a') && LOWERCASE(check[1]) == 'u') + return 7; + break; + case 'p': /* sep */ + if((LOWERCASE(check[0]) == 's') && LOWERCASE(check[1]) == 'e') + return 8; + break; + case 't': /* oct */ + if((LOWERCASE(check[0]) == 'o') && LOWERCASE(check[1]) == 'c') + return 9; + break; + case 'v': /* nov */ + if((LOWERCASE(check[0]) == 'n') && LOWERCASE(check[1]) == 'o') + return 10; + break; + case 'c': /* dec */ + if((LOWERCASE(check[0]) == 'd') && LOWERCASE(check[1]) == 'e') + return 11; + break; } return -1; /* return the offset or -1, no real offset is -1 */ } @@ -231,18 +325,60 @@ 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 tz1->tz - tz2->tz; } /* 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) + seconds or -1 if the timezone was not found/legal + + @unittest 4781 +*/ +UNITTEST int checktz(const char *check, size_t len); +UNITTEST int checktz(const char *check, size_t len) { - if(len <= 4) { + char first; + if(len > 4) + return -1; + first = check[0]; + if(first < 'A') + return -1; + if(len == 1) { + /* short-cut single letter names */ + if((first > 'Z') || (first == 'J')) + return -1; /* no such tz */ + return tzone[first - 'A'] * 60; + } + else if(len == 2) { + /* two-letter names */ + if(check[1] == 'T') { + if(first == 'N') + return 11 * 60 * 60; + else if(first == 'U') + return 0; + } + return -1; /* nope */ + } + else if(len == 3) { + /* three-letter name */ 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(first > 'Y') + return -1; + find.tz = MKTZ(first, check[1], check[2], 0); + what = bsearch(&find, tzthree, CURL_ARRAYSIZE(tzthree), + sizeof(tzthree[0]), tzcompare); + if(what) + return what->offset * 60; + } + else { + /* four-letter name */ + const struct tzinfo *what; + struct tzinfo find; + if(first > 'W') + return -1; + find.tz = MKTZ(first, check[1], check[2], check[3]); + what = bsearch(&find, tzfoura, CURL_ARRAYSIZE(tzfoura), + sizeof(tzfoura[0]), tzcompare); if(what) return what->offset * 60; } @@ -582,9 +718,13 @@ static int parsedate(const char *date, time_t *output) time_t curl_getdate(const char *p, const time_t *unused) { time_t parsed = -1; - int rc = parsedate(p, &parsed); + int rc; (void)unused; /* legacy argument from the past that we ignore */ + if(!p) + return -1; + + rc = parsedate(p, &parsed); if(rc == PARSEDATE_OK) { if(parsed == (time_t)-1) /* avoid returning -1 for a working scenario */ diff --git a/lib/peer.c b/lib/peer.c index 4a7a12d8cf06..b5bf7b7334e0 100644 --- a/lib/peer.c +++ b/lib/peer.c @@ -151,13 +151,13 @@ static CURLcode peer_create(struct peer_parse *pp, if(!peer->scopeid) { const char *p = peer->zoneid; curl_off_t scope; - if(!curlx_str_number(&p, &scope, UINT_MAX)) { + if(!curlx_str_number(&p, &scope, UINT_MAX) && !*p) { /* A plain number, use it directly as a scope id. */ peer->scopeid = (uint32_t)scope; } #ifdef HAVE_IF_NAMETOINDEX else { - /* Zone identifier is not numeric */ + /* Zone identifier is not fully numeric */ unsigned int idx = 0; idx = if_nametoindex(peer->zoneid); if(idx) { @@ -333,9 +333,18 @@ static bool peer_same_hostname(struct Curl_peer *p1, struct Curl_peer *p2) return (p1->unix_socket == p2->unix_socket) && (p1->abstract_uds == p2->abstract_uds) && (p1->ipv6 == p2->ipv6) && - (p1->unix_socket ? - !strcmp(p1->hostname, p2->hostname) : - curl_strequal(p1->hostname, p2->hostname)); + /* unix_socket paths are case-sensitive, hostnames are not. + * Do strcmp() first on both, since we do a lot of matches + * on the same names and platform strcmp is way faster. */ + (!strcmp(p1->hostname, p2->hostname) || + (!p1->unix_socket && + curl_strequal(p1->hostname, p2->hostname))); +} + +static bool peer_same_zoneid(const char *z1, const char *z2) +{ + return (z1 == z2) || + (z1 && z2 && !strcmp(z1, z2)); } bool Curl_peer_same_destination(struct Curl_peer *p1, struct Curl_peer *p2) @@ -345,7 +354,7 @@ bool Curl_peer_same_destination(struct Curl_peer *p1, struct Curl_peer *p2) (p1->port == p2->port) && peer_same_hostname(p1, p2) && (p1->scopeid == p2->scopeid) && - (p1->scopeid || curl_strequal(p1->zoneid, p2->zoneid))); + (p1->scopeid || peer_same_zoneid(p1->zoneid, p2->zoneid))); } CURLcode Curl_peer_from_url(CURLU *uh, struct Curl_easy *data, @@ -461,6 +470,7 @@ CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, { struct peer_parse pp; const char *portstr = NULL; + bool port_switch = FALSE; CURLcode result; Curl_peer_unlink(ppeer); @@ -491,6 +501,7 @@ CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, } if(!pp.host_user.len) { /* no hostname found, only port switch */ + port_switch = TRUE; pp.host_user.str = dest->user_hostname; pp.host_user.len = strlen(dest->user_hostname); } @@ -498,6 +509,8 @@ CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, result = peer_parse_host(data, &pp, FALSE); if(result) goto out; + /* On a port switch, ipv6 must stay the same */ + DEBUGASSERT(!port_switch || ((bool)pp.ipv6 == (bool)dest->ipv6)); if(portstr && portstr[1]) { const char *p = portstr + 1; @@ -519,6 +532,13 @@ CURLcode Curl_peer_from_connect_to(struct Curl_easy *data, goto out; } #endif + if(pp.ipv6 && port_switch) { + if(dest->zoneid) { + pp.zoneid.str = dest->zoneid; + pp.zoneid.len = strlen(dest->zoneid); + } + pp.scopeid = dest->scopeid; + } result = peer_create(&pp, ppeer); CURL_TRC_M(data, "connect-to peer_create2 -> %d", (int)result); diff --git a/lib/pingpong.c b/lib/pingpong.c index b365772fb71a..f59e31add019 100644 --- a/lib/pingpong.c +++ b/lib/pingpong.c @@ -80,8 +80,11 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data, if(timeout_ms < interval_ms) interval_ms = timeout_ms; } - else + else { interval_ms = 0; /* immediate */ + /* Wake the multi socket API even when the server sends no response. */ + Curl_expire(data, timeout_ms, EXPIRE_SERVER_RESPONSE); + } if(Curl_conn_data_pending(data, FIRSTSOCKET)) rc = 1; @@ -182,7 +185,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data, Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written); - if(bytes_written != write_len) { + if(bytes_written < write_len) { /* the whole chunk was not sent, keep it around and adjust sizes */ pp->sendthis = s; pp->sendsize = write_len; @@ -245,6 +248,7 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, CURLcode result = CURLE_OK; size_t gotbytes; char buffer[900]; + int maxloops = 10; *code = 0; /* 0 for errors or not done */ *size = 0; @@ -262,6 +266,9 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, pp->nfinal = 0; /* now gone */ } if(!pp->overflow) { + if(!maxloops--) /* don't get stuck */ + return CURLE_OK; + result = pingpong_read(data, sockindex, buffer, sizeof(buffer), &gotbytes); if(result == CURLE_AGAIN) @@ -294,8 +301,8 @@ CURLcode Curl_pp_readresp(struct Curl_easy *data, if(memchr(line, 0, length)) { /* The response line is passed on as a "header" below, so reject an - embedded nul the same way verify_header() does for HTTP. */ - failf(data, "Nul byte in server response line"); + embedded NUL byte the same way verify_header() does for HTTP. */ + failf(data, "NUL byte in server response line"); return CURLE_WEIRD_SERVER_REPLY; } @@ -382,7 +389,7 @@ CURLcode Curl_pp_flushsend(struct Curl_easy *data, if(result) return result; - if(written != pp->sendleft) { + if(written < pp->sendleft) { /* only a fraction was sent */ pp->sendleft -= written; } diff --git a/lib/pop3.c b/lib/pop3.c index b32663ce1473..7ec3d2520e65 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -697,9 +697,8 @@ static CURLcode pop3_perform_authentication(struct Curl_easy *data, /* Calculate the SASL login details */ result = Curl_sasl_start(&pop3c->sasl, data, FALSE, &progress); - if(!result) - if(progress == SASL_INPROGRESS) - pop3_state(data, POP3_AUTH); + if(!result && progress == SASL_INPROGRESS) + pop3_state(data, POP3_AUTH); } if(!result && progress == SASL_IDLE) { @@ -1622,10 +1621,9 @@ static CURLcode pop3_disconnect(struct Curl_easy *data, disconnect wait in vain and cause more problems than we need to. */ if(!dead_connection && conn->bits.protoconnstart && - !Curl_pp_needs_flush(data, &pop3c->pp)) { - if(!pop3_perform_quit(data, conn)) - (void)pop3_block_statemach(data, conn, TRUE); /* ignore errors on QUIT */ - } + !Curl_pp_needs_flush(data, &pop3c->pp) && + !pop3_perform_quit(data, conn)) + (void)pop3_block_statemach(data, conn, TRUE); /* ignore errors on QUIT */ /* Disconnect from the server */ Curl_pp_disconnect(&pop3c->pp); @@ -1652,7 +1650,7 @@ static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done) return result; } -static void pop3_easy_dtor(void *key, size_t klen, void *entry) +static void pop3_easy_dtor(const void *key, size_t klen, void *entry) { struct POP3 *pop3 = entry; (void)key; @@ -1664,7 +1662,7 @@ static void pop3_easy_dtor(void *key, size_t klen, void *entry) curlx_free(pop3); } -static void pop3_conn_dtor(void *key, size_t klen, void *entry) +static void pop3_conn_dtor(const void *key, size_t klen, void *entry) { struct pop3_conn *pop3c = entry; (void)key; diff --git a/lib/progress.c b/lib/progress.c index fcb8bf24229b..c6432d68f733 100644 --- a/lib/progress.c +++ b/lib/progress.c @@ -185,11 +185,11 @@ const struct curltime *Curl_pgrs_now(struct Curl_easy *data) pgrsDone() - transfer complete */ -int Curl_pgrsDone(struct Curl_easy *data) +int Curl_pgrsDone(struct Curl_easy *data, const struct curltime *pnow) { int rc; data->progress.delta.lastshow_us = -1; - rc = Curl_pgrsUpdate(data); /* the final (forced) update */ + rc = Curl_pgrsUpdateX(data, pnow); /* the final (forced) update */ if(rc) return rc; diff --git a/lib/progress.h b/lib/progress.h index 19bb09517cbd..4554b34ca3f6 100644 --- a/lib/progress.h +++ b/lib/progress.h @@ -45,7 +45,7 @@ typedef enum { /* Get the current timestamp of the transfer */ const struct curltime *Curl_pgrs_now(struct Curl_easy *data); -int Curl_pgrsDone(struct Curl_easy *data); +int Curl_pgrsDone(struct Curl_easy *data, const struct curltime *pnow); void Curl_pgrsStart(struct Curl_easy *data, const struct curltime *pnow); void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size); void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size); diff --git a/lib/protocol.c b/lib/protocol.c index 3bfcd88ebe46..a7b14601dee2 100644 --- a/lib/protocol.c +++ b/lib/protocol.c @@ -36,7 +36,6 @@ #include "mqtt.h" #include "pop3.h" #include "rtsp.h" -#include "smb.h" #include "smtp.h" #include "telnet.h" #include "tftp.h" @@ -308,32 +307,6 @@ const struct Curl_scheme Curl_scheme_scp = { 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 @@ -486,8 +459,6 @@ static const struct Curl_scheme *three_letter_scheme(const char *scheme) else if(s0 == 's') { if(s1 == 'c' && s2 == 'p') return &Curl_scheme_scp; - if(s1 == 'm' && s2 == 'b') - return &Curl_scheme_smb; } return NULL; } @@ -544,10 +515,6 @@ static const struct Curl_scheme *four_letter_scheme(const char *scheme) if(s1 == 'i' && s2 == 'c' && s3 == 't') return &Curl_scheme_dict; } - else if(s0 == 's') { - if(s1 == 'm' && s2 == 'b' && s3 == 's') - return &Curl_scheme_smbs; - } return NULL; } diff --git a/lib/protocol.h b/lib/protocol.h index 031bb834e417..a11166f09a5f 100644 --- a/lib/protocol.h +++ b/lib/protocol.h @@ -45,8 +45,6 @@ struct easy_pollset; #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 @@ -85,7 +83,6 @@ typedef curl_off_t curl_prot_t; 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) @@ -278,8 +275,6 @@ 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_socks; diff --git a/lib/ptrarray.c b/lib/ptrarray.c new file mode 100644 index 000000000000..fd10a1c8d68b --- /dev/null +++ b/lib/ptrarray.c @@ -0,0 +1,98 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "ptrarray.h" + +#define CURL_PTRARRAY_MAX_GROW (16 * 1024) + +void Curl_ptrarray_init(struct ptrarray *pa) +{ + pa->data = NULL; + pa->n = pa->capacity = 0; +} + +void Curl_ptrarray_clear(struct ptrarray *pa) +{ + curlx_safefree(pa->data); + pa->n = pa->capacity = 0; +} + +uint32_t Curl_ptrarray_count(struct ptrarray *pa) +{ + return pa->n; +} + +CURLcode Curl_ptrarray_add(struct ptrarray *pa, void *ptr) +{ + if(!ptr) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(pa->n >= pa->capacity) { + uint32_t growth; + void **ndata; + + if(pa->capacity == UINT32_MAX) + return CURLE_OUT_OF_MEMORY; + if(!pa->capacity) + growth = 8; + else if((UINT32_MAX / 2) > pa->capacity) + growth = CURLMIN(2 * pa->capacity, CURL_PTRARRAY_MAX_GROW); + else + growth = ((UINT32_MAX - pa->capacity) > CURL_PTRARRAY_MAX_GROW) ? + CURL_PTRARRAY_MAX_GROW : (UINT32_MAX - pa->capacity); + + ndata = curlx_calloc(1, (pa->capacity + growth) * sizeof(void *)); + if(!ndata) + return CURLE_OUT_OF_MEMORY; + if(pa->n) + memcpy(ndata, pa->data, pa->n * sizeof(void *)); + curlx_free(pa->data); + pa->data = ndata; + pa->capacity += growth; + } + DEBUGASSERT(pa->n < pa->capacity); + pa->data[pa->n++] = ptr; + return CURLE_OK; +} + +void *Curl_ptrarray_remove(struct ptrarray *pa, uint32_t idx) +{ + if(idx < pa->n) { + uint32_t rem = pa->n - idx - 1; + void *ptr = pa->data[idx]; + pa->data[idx] = NULL; + if(rem) + memmove(pa->data + idx, pa->data + idx + 1, rem * sizeof(void *)); + pa->n--; + if(!pa->n) + Curl_ptrarray_clear(pa); + return ptr; + } + return NULL; +} + +void *Curl_ptrarray_get(struct ptrarray *pa, uint32_t idx) +{ + return (idx < pa->n) ? pa->data[idx] : NULL; +} diff --git a/lib/system_win32.h b/lib/ptrarray.h similarity index 56% rename from lib/system_win32.h rename to lib/ptrarray.h index d504b563db4b..5fcf810d8c86 100644 --- a/lib/system_win32.h +++ b/lib/ptrarray.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_SYSTEM_WIN32_H -#define HEADER_CURL_SYSTEM_WIN32_H +#ifndef HEADER_CURL_PTRARRAY_H +#define HEADER_CURL_PTRARRAY_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) Steve Holme, . + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -25,11 +25,26 @@ ***************************************************************************/ #include "curl_setup.h" -#ifdef _WIN32 -CURLcode Curl_win32_init(long flags); -void Curl_win32_cleanup(long init_flags); -#else -#define Curl_win32_init(x) CURLE_OK -#endif /* _WIN32 */ +/* An array of pointers with up to UINT32_MAX entries */ +struct ptrarray { + void **data; /* #capacity array of pointers */ + uint32_t n; + uint32_t capacity; +}; -#endif /* HEADER_CURL_SYSTEM_WIN32_H */ +void Curl_ptrarray_init(struct ptrarray *pa); + +void Curl_ptrarray_clear(struct ptrarray *pa); + +uint32_t Curl_ptrarray_count(struct ptrarray *pa); + +CURLcode Curl_ptrarray_add(struct ptrarray *pa, void *ptr); + +void *Curl_ptrarray_remove(struct ptrarray *pa, uint32_t idx); + +void *Curl_ptrarray_get(struct ptrarray *pa, uint32_t idx); + +#define CURL_PTRARRAY_COUNT(a) ((a)->n) +#define CURL_PTRARRAY_GET(a, i) (((i) < ((a)->n)) ? (a)->data[(i)] : NULL) + +#endif /* HEADER_CURL_PTRARRAY_H */ diff --git a/lib/rand.h b/lib/rand.h index 452c1b67ef90..7d59337ad4e7 100644 --- a/lib/rand.h +++ b/lib/rand.h @@ -23,6 +23,8 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +struct Curl_easy; + CURLcode Curl_rand_bytes(struct Curl_easy *data, #ifdef DEBUGBUILD bool env_override, diff --git a/lib/rtsp.c b/lib/rtsp.c index fabdda3d3eed..72b57c0ca5ef 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -41,6 +41,7 @@ #include "curlx/strdup.h" #include "bufref.h" #include "curlx/strparse.h" +#include "peer.h" /* meta key for storing protocol meta at easy handle */ #define CURL_META_RTSP_EASY "meta:proto:rtsp:easy" @@ -85,7 +86,7 @@ static CURLcode rtsp_do_pollset(struct Curl_easy *data, #define MAX_RTP_BUFFERSIZE 1000000 /* arbitrary */ -static void rtsp_easy_dtor(void *key, size_t klen, void *entry) +static void rtsp_easy_dtor(const void *key, size_t klen, void *entry) { struct RTSP *rtsp = entry; (void)key; @@ -93,7 +94,7 @@ static void rtsp_easy_dtor(void *key, size_t klen, void *entry) curlx_free(rtsp); } -static void rtsp_conn_dtor(void *key, size_t klen, void *entry) +static void rtsp_conn_dtor(const void *key, size_t klen, void *entry) { struct rtsp_conn *rtspc = entry; (void)key; @@ -410,16 +411,14 @@ static CURLcode rtsp_setup_request(struct Curl_easy *data, data->state.use_range && ((rtspreq == RTSPREQ_PLAY) || (rtspreq == RTSPREQ_PAUSE) || - (rtspreq == RTSPREQ_RECORD))) { - - /* Check to see if there is a range set in the custom headers */ - if(!Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { - result = rtsp_header_alloc("Range", - data->state.range, - &data->state.rangeline); - if(!result) - b->range = data->state.rangeline; - } + (rtspreq == RTSPREQ_RECORD)) && + /* Check to see if there is a range set in the custom headers */ + !Curl_checkheaders(data, STRCONST("Range")) && data->state.range) { + result = rtsp_header_alloc("Range", + data->state.range, + &data->state.rangeline); + if(!result) + b->range = data->state.rangeline; } return result; } @@ -459,6 +458,12 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) result = rtsp_setup_request(data, &block, rtspreq); if(result) goto out; + + if(block.session_id && data->state.rtsp_session_origin && + !data->set.allow_auth_to_other_hosts && + !Curl_peer_equal(data->state.origin, data->state.rtsp_session_origin)) + block.session_id = NULL; + /* * Sanity check the custom headers */ @@ -935,12 +940,10 @@ static CURLcode rtsp_parse_transport(struct Curl_easy *data, if(!curlx_str_number(&p, &chan1, 255)) { unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; chan2 = chan1; - if(!curlx_str_single(&p, '-')) { - if(curlx_str_number(&p, &chan2, 255)) { - infof(data, "Unable to read the interleaved parameter from " - "Transport header: [%s]", transport); - chan2 = chan1; - } + if(!curlx_str_single(&p, '-') && curlx_str_number(&p, &chan2, 255)) { + infof(data, "Unable to read the interleaved parameter from " + "Transport header: [%s]", transport); + chan2 = chan1; } for(chan = chan1; chan <= chan2; chan++) { int idx = (int)chan / 8; @@ -1018,6 +1021,7 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) if(!mem || CURL_EASY_STR_SETN(data, STRING_RTSP_SESSION_ID, mem)) return CURLE_OUT_OF_MEMORY; + Curl_peer_link(&data->state.rtsp_session_origin, data->state.origin); } } else if(checkprefix("Transport:", header)) { diff --git a/lib/select.c b/lib/select.c index 76b56bc0426b..3e45fda78018 100644 --- a/lib/select.c +++ b/lib/select.c @@ -348,9 +348,11 @@ void Curl_pollfds_cleanup(struct curl_pollfds *cpfds) { DEBUGASSERT(cpfds); if(cpfds->allocated_pfds) { - curlx_free(cpfds->pfds); + curlx_safefree(cpfds->pfds); + cpfds->count = 0; + cpfds->allocated_pfds = FALSE; } - memset(cpfds, 0, sizeof(*cpfds)); + cpfds->n = 0; } static CURLcode cpfds_increase(struct curl_pollfds *cpfds, unsigned int inc) @@ -385,10 +387,8 @@ static CURLcode cpfds_add_sock(struct curl_pollfds *cpfds, } } /* not folded, add new entry */ - if(cpfds->n >= cpfds->count) { - if(cpfds_increase(cpfds, 100)) - return CURLE_OUT_OF_MEMORY; - } + if(cpfds->n >= cpfds->count && cpfds_increase(cpfds, 100)) + return CURLE_OUT_OF_MEMORY; cpfds->pfds[cpfds->n].fd = sock; cpfds->pfds[cpfds->n].events = events; ++cpfds->n; @@ -414,10 +414,8 @@ CURLcode Curl_pollfds_add_ps(struct curl_pollfds *cpfds, events |= POLLIN; if(ps->actions[i] & CURL_POLL_OUT) events |= POLLOUT; - if(events) { - if(cpfds_add_sock(cpfds, ps->sockets[i], events, TRUE)) - return CURLE_OUT_OF_MEMORY; - } + if(events && cpfds_add_sock(cpfds, ps->sockets[i], events, TRUE)) + return CURLE_OUT_OF_MEMORY; } return CURLE_OK; } @@ -480,15 +478,10 @@ unsigned int Curl_waitfds_add_ps(struct Curl_waitfds *cwfds, void Curl_pollset_reset(struct easy_pollset *ps) { - unsigned int i; - ps->n = 0; #ifdef DEBUGBUILD DEBUGASSERT(ps->init == CURL_EASY_POLLSET_MAGIC); #endif - DEBUGASSERT(ps->count); - for(i = 0; i < ps->count; i++) - ps->sockets[i] = CURL_SOCKET_BAD; - memset(ps->actions, 0, ps->count * sizeof(ps->actions[0])); + ps->n = 0; } void Curl_pollset_init(struct easy_pollset *ps) @@ -636,6 +629,23 @@ CURLcode Curl_pollset_set(struct Curl_easy *data, (!do_out ? CURL_POLL_OUT : 0)); } +void Curl_pollset_remove(struct easy_pollset *ps, curl_socket_t sock) +{ + unsigned int i; + for(i = 0; i < ps->n; ++i) { + if(ps->sockets[i] == sock) { + if((i + 1) < ps->n) { + memmove(&ps->sockets[i], &ps->sockets[i + 1], + (ps->n - (i + 1)) * sizeof(ps->sockets[0])); + memmove(&ps->actions[i], &ps->actions[i + 1], + (ps->n - (i + 1)) * sizeof(ps->actions[0])); + } + --ps->n; + return; + } + } +} + /* * Return values: * -1 = error diff --git a/lib/select.h b/lib/select.h index 273068b6d1dc..cbaa49ef128e 100644 --- a/lib/select.h +++ b/lib/select.h @@ -173,6 +173,8 @@ CURLcode Curl_pollset_set(struct Curl_easy *data, #define Curl_pollset_set_out_only(data, ps, sock) \ Curl_pollset_change(data, ps, sock, CURL_POLL_OUT, CURL_POLL_IN) +void Curl_pollset_remove(struct easy_pollset *ps, curl_socket_t sock); + /* return < = on error, 0 on timeout or how many sockets are ready */ int Curl_pollset_poll(struct Curl_easy *data, struct easy_pollset *ps, diff --git a/lib/sendf.c b/lib/sendf.c index abd2e3fdbfba..d6ff92500fb6 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -1446,14 +1446,6 @@ curl_off_t Curl_creader_client_length(struct Curl_easy *data) return r ? r->crt->total_length(data, r) : -1; } -CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset) -{ - struct Curl_creader *r = data->req.reader.stack; - while(r && r->phase != CURL_CR_CLIENT) - r = r->next; - return r ? r->crt->resume_from(data, r, offset) : CURLE_READ_ERROR; -} - CURLcode Curl_creader_unpause(struct Curl_easy *data) { struct Curl_creader *reader = data->req.reader.stack; diff --git a/lib/sendf.h b/lib/sendf.h index 9863c5b0dcaa..39c7a8aaec5b 100644 --- a/lib/sendf.h +++ b/lib/sendf.h @@ -376,20 +376,6 @@ curl_off_t Curl_creader_total_length(struct Curl_easy *data); */ curl_off_t Curl_creader_client_length(struct Curl_easy *data); -/** - * Ask the installed reader at phase CURL_CR_CLIENT to start - * reading from the given offset. On success, this will reduce - * the `total_length()` by the amount. - * @param data the transfer to read client bytes for - * @param offset the offset where to start reads from, negative - * values will be ignored. - * @return CURLE_OK if offset could be set - * CURLE_READ_ERROR if not supported by reader or seek/read failed - * of offset larger than total length - * CURLE_PARTIAL_FILE if offset led to 0 total length - */ -CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset); - /** * Unpause all installed readers. */ diff --git a/lib/setopt.c b/lib/setopt.c index a26460944099..742db1a576bf 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -154,32 +154,18 @@ static CURLcode setstropt_userpwd(const char *option, char **userp, static CURLcode setstropt_interface(struct Curl_easy *data, char *option) { - char *dev = NULL; - char *iface = NULL; - char *host = NULL; - CURLcode result; - + if(option && !option[0]) + option = NULL; if(option) { - /* Parse the interface details if set, otherwise clear them all */ - result = Curl_parse_interface(option, &dev, &iface, &host); + /* Parse for checking syntax */ + CURLcode result = Curl_parse_interface(option, NULL, NULL, NULL); if(result) return result; + return CURL_EASY_STR_SET(data, STRING_INTERFACE_OPT, + option, strlen(option)); } - - result = CURL_EASY_STR_SETN(data, STRING_DEVICE, dev); - dev = NULL; - if(!result) { - result = CURL_EASY_STR_SETN(data, STRING_INTERFACE, iface); - iface = NULL; - } - if(!result) { - result = CURL_EASY_STR_SETN(data, STRING_BINDHOST, host); - host = NULL; - } - curlx_free(dev); - curlx_free(iface); - curlx_free(host); - return result; + CURL_EASY_STR_CLEAR(data, STRING_INTERFACE_OPT); + return CURLE_OK; } #ifdef USE_SSL @@ -313,10 +299,10 @@ CURLcode Curl_setopt_SSLVERSION(struct Curl_easy *data, CURLoption option, */ { long version, version_max; - struct ssl_primary_config *primary = &data->set.ssl.primary; + struct ssl_easy_config *sslc = &data->set.ssl; #ifndef CURL_DISABLE_PROXY if(option != CURLOPT_SSLVERSION) - primary = &data->set.proxy_ssl.primary; + sslc = &data->set.proxy_ssl; #else (void)option; /* unused */ #endif @@ -333,8 +319,8 @@ CURLcode Curl_setopt_SSLVERSION(struct Curl_easy *data, CURLoption option, if(version == CURL_SSLVERSION_DEFAULT) version = CURL_SSLVERSION_TLSv1_2; - primary->version = (unsigned char)version; - primary->version_max = (unsigned int)version_max; + sslc->version = (unsigned char)version; + sslc->version_max = (unsigned int)version_max; } return CURLE_OK; } @@ -522,7 +508,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, /* * Enable peer SSL verifying for proxy. */ - s->proxy_ssl.primary.verifypeer = enabled; + s->proxy_ssl.verifypeer = enabled; /* Update the current connection proxy_ssl_config. */ Curl_ssl_conn_config_update(data, TRUE); @@ -531,7 +517,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, /* * Enable verification of the hostname in the peer certificate for proxy */ - s->proxy_ssl.primary.verifyhost = enabled; + s->proxy_ssl.verifyhost = enabled; ok = 2; /* Update the current connection proxy_ssl_config. */ Curl_ssl_conn_config_update(data, TRUE); @@ -616,7 +602,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, /* * Enable peer SSL verifying. */ - s->ssl.primary.verifypeer = enabled; + s->ssl.verifypeer = enabled; /* Update the current connection ssl_config. */ Curl_ssl_conn_config_update(data, FALSE); @@ -654,7 +640,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, /* Obviously people are not reading documentation and too many thought this argument took a boolean when it was not and misused it. Treat 1 and 2 the same */ - s->ssl.primary.verifyhost = enabled; + s->ssl.verifyhost = enabled; ok = 2; /* Update the current connection ssl_config. */ @@ -667,7 +653,7 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, if(!Curl_ssl_cert_status_request()) return CURLE_NOT_BUILT_IN; - s->ssl.primary.verifystatus = enabled; + s->ssl.verifystatus = enabled; /* Update the current connection ssl_config. */ Curl_ssl_conn_config_update(data, FALSE); @@ -698,9 +684,9 @@ static CURLcode setopt_long_bool(struct Curl_easy *data, CURLoption option, s->ignorecl = enabled; break; case CURLOPT_SSL_SESSIONID_CACHE: - s->ssl.primary.cache_session = enabled; + s->ssl.cache_session = enabled; #ifndef CURL_DISABLE_PROXY - s->proxy_ssl.primary.cache_session = s->ssl.primary.cache_session; + s->proxy_ssl.cache_session = s->ssl.cache_session; #endif break; #ifdef USE_SSH @@ -914,7 +900,7 @@ static CURLcode setopt_long_ssl(struct Curl_easy *data, CURLoption option, if(Curl_ssl_supports(data, SSLSUPP_CA_CACHE)) { result = value_range(&arg, -1, -1, INT_MAX); if(!result) - s->general_ssl.ca_cache_timeout = (int)arg; + s->ssl_ca_cache_timeout = (int)arg; } else result = CURLE_NOT_BUILT_IN; @@ -934,11 +920,11 @@ static CURLcode setopt_long_ssl(struct Curl_easy *data, CURLoption option, s->use_ssl = (unsigned char)arg; break; case CURLOPT_SSL_OPTIONS: - s->ssl.primary.ssl_options = (unsigned char)(arg & 0xff); + s->ssl.ssl_options = (unsigned char)(arg & 0xff); break; #ifndef CURL_DISABLE_PROXY case CURLOPT_PROXY_SSL_OPTIONS: - s->proxy_ssl.primary.ssl_options = (unsigned char)(arg & 0xff); + s->proxy_ssl.ssl_options = (unsigned char)(arg & 0xff); break; #endif case CURLOPT_SSL_ENABLE_NPN: @@ -990,11 +976,16 @@ static CURLcode setopt_long_proxy(struct Curl_easy *data, CURLoption option, #endif s->proxytype = (unsigned char)arg; break; - case CURLOPT_SOCKS5_AUTH: - if(arg & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI)) + case CURLOPT_SOCKS5_AUTH: { + unsigned long auth = (unsigned long)arg; + if(auth & ~(CURLAUTH_BASIC | CURLAUTH_GSSAPI | CURLAUTH_ONLY)) return CURLE_NOT_BUILT_IN; - s->socks5auth = (unsigned char)arg; + if((auth & CURLAUTH_ONLY) && !(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI))) + return CURLE_BAD_FUNCTION_ARGUMENT; + s->socks5auth = (unsigned char)(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI)); + s->socks5_auth_only = !!(auth & CURLAUTH_ONLY); break; + } default: return CURLE_UNKNOWN_OPTION; } @@ -1748,8 +1739,8 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, * CA certificate */ result = Curl_setstropt(data, STRING_SSL_CAFILE_PROXY, ptr); - s->proxy_ssl.custom_cafile = - !!CURL_EASY_STR(data, STRING_SSL_CAFILE_PROXY); + if(!result) + s->proxy_ssl.custom_cafile = TRUE; return result; case CURLOPT_PROXY_CRLFILE: /* @@ -1775,8 +1766,8 @@ static CURLcode setopt_cptr_proxy(struct Curl_easy *data, CURLoption option, if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ result = Curl_setstropt(data, STRING_SSL_CAPATH_PROXY, ptr); - s->proxy_ssl.custom_capath = - !!CURL_EASY_STR(data, STRING_SSL_CAPATH_PROXY); + if(!result) + s->proxy_ssl.custom_capath = TRUE; return result; } #endif @@ -1840,26 +1831,26 @@ static CURLcode setopt_ech(struct Curl_easy *data, const char *ptr) CURLcode result = CURLE_OK; if(!ptr || !strcmp(ptr, "false")) - s->tls_ech = CURLECH_DISABLE; + s->ssl.ech = CURLECH_DISABLE; else { size_t plen = strlen(ptr); if(plen > CURL_MAX_INPUT_LENGTH) result = CURLE_BAD_FUNCTION_ARGUMENT; else { if(!strcmp(ptr, "grease")) - s->tls_ech = CURLECH_GREASE; + s->ssl.ech = CURLECH_GREASE; else if(!strcmp(ptr, "true")) - s->tls_ech = CURLECH_ENABLE; + s->ssl.ech = CURLECH_ENABLE; else if(!strcmp(ptr, "hard")) - s->tls_ech = CURLECH_HARD; + s->ssl.ech = CURLECH_HARD; else if(plen > 4 && !strncmp(ptr, "ecl:", 4)) { - if(!s->tls_ech) - s->tls_ech = CURLECH_HARD; + if(!s->ssl.ech) + s->ssl.ech = CURLECH_HARD; result = Curl_setstropt(data, STRING_ECH_CONFIG, ptr + 4); } else if(plen > 3 && !strncmp(ptr, "pn:", 3)) { - if(!s->tls_ech) - s->tls_ech = CURLECH_HARD; + if(!s->ssl.ech) + s->ssl.ech = CURLECH_HARD; result = Curl_setstropt(data, STRING_ECH_PUBLIC, ptr + 3); } else @@ -1892,7 +1883,8 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * Set CA info for SSL connection. Specify filename of the CA certificate */ result = Curl_setstropt(data, STRING_SSL_CAFILE, ptr); - data->set.ssl.custom_cafile = !!CURL_EASY_STR(data, STRING_SSL_CAFILE); + if(!result) + data->set.ssl.custom_cafile = TRUE; return result; case CURLOPT_CAPATH: /* @@ -1902,7 +1894,8 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, if(Curl_ssl_supports(data, SSLSUPP_CA_PATH)) { /* This does not work on Windows. */ result = Curl_setstropt(data, STRING_SSL_CAPATH, ptr); - data->set.ssl.custom_capath = !!CURL_EASY_STR(data, STRING_SSL_CAPATH); + if(!result) + data->set.ssl.custom_capath = TRUE; return result; } return CURLE_NOT_BUILT_IN; @@ -1935,7 +1928,7 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, * Set an SSL_CTX callback parameter pointer */ if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { - data->set.ssl.fsslctxp = ptr; + data->set.ssl_fsslctxp = ptr; break; } else @@ -1945,7 +1938,7 @@ static CURLcode setopt_cptr_ssl(struct Curl_easy *data, CURLoption option, /* * Unity: userdata for the peer certificate verification callback. */ - data->set.ssl.primary.unity_certverify_userp = ptr; + data->set.ssl.unity_certverify_userp = ptr; break; #endif /* UNITY_CERTVERIFY */ case CURLOPT_SSLCERT: @@ -2425,6 +2418,7 @@ static CURLcode setopt_cptr_misc(struct Curl_easy *data, CURLoption option, return Curl_setstropt(data, STRING_SASL_AUTHZID, ptr); #ifndef CURL_DISABLE_RTSP case CURLOPT_RTSP_SESSION_ID: + Curl_peer_unlink(&data->state.rtsp_session_origin); return Curl_setstropt(data, STRING_RTSP_SESSION_ID, ptr); case CURLOPT_RTSP_STREAM_URI: return Curl_setstropt(data, STRING_RTSP_STREAM_URI, ptr); @@ -2634,7 +2628,7 @@ static CURLcode setopt_func(struct Curl_easy *data, CURLoption option, */ #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_SSL_CTX)) { - s->ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); + s->ssl_fsslctx = va_arg(param, curl_ssl_ctx_callback); break; } else @@ -2647,7 +2641,7 @@ static CURLcode setopt_func(struct Curl_easy *data, CURLoption option, * Unity: set a callback that verifies the peer certificate. */ #ifdef USE_SSL - s->ssl.primary.unity_certverify = + s->ssl.unity_certverify = va_arg(param, curl_unity_certverify_callback); break; #else @@ -2857,7 +2851,8 @@ static CURLcode setopt_blob(struct Curl_easy *data, CURLoption option, #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) { CURLcode result = Curl_setblobopt(&s->blobs[BLOB_CAINFO_PROXY], blob); - s->proxy_ssl.custom_cablob = !!s->blobs[BLOB_CAINFO_PROXY]; + if(!result) + s->proxy_ssl.custom_cablob = TRUE; return result; } #endif @@ -2883,7 +2878,8 @@ static CURLcode setopt_blob(struct Curl_easy *data, CURLoption option, #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) { CURLcode result = Curl_setblobopt(&s->blobs[BLOB_CAINFO], blob); - s->ssl.custom_cablob = !!s->blobs[BLOB_CAINFO]; + if(!result) + s->ssl.custom_cablob = TRUE; return result; } #endif diff --git a/lib/sha256.c b/lib/sha256.c index 89b96017bdc2..a6b1f1373cb6 100644 --- a/lib/sha256.c +++ b/lib/sha256.c @@ -170,16 +170,16 @@ static void my_sha256_final(unsigned char *digest, void *ctx) } #elif (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ - (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1040)) || \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 1040) || \ (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - (__IPHONE_OS_VERSION_MAX_ALLOWED >= 20000)) + __IPHONE_OS_VERSION_MAX_ALLOWED >= 20000) #include typedef CC_SHA256_CTX my_sha256_ctx; static CURLcode my_sha256_init(void *ctx) { - (void)CC_SHA256_Init(ctx); + (void)CC_SHA256_Init(ctx); /* always returns 1 */ return CURLE_OK; } diff --git a/lib/smb.c b/lib/smb.c deleted file mode 100644 index 4299190ff577..000000000000 --- a/lib/smb.c +++ /dev/null @@ -1,1243 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , et al. - * Copyright (C) Bill Nagel , Exacq Technologies - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" -#include "urldata.h" - -#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE) - -#ifdef HAVE_ARPA_INET_H -#include /* for htons() */ -#endif - -#include "smb.h" -#include "url.h" -#include "sendf.h" -#include "curl_trc.h" -#include "cfilters.h" -#include "connect.h" -#include "progress.h" -#include "transfer.h" -#include "select.h" -#include "curl_ntlm_core.h" -#include "escape.h" -#include "curl_endian.h" -#include "curlx/strcopy.h" - -/* meta key for storing protocol meta at easy handle */ -#define CURL_META_SMB_EASY "meta:proto:smb:easy" -/* meta key for storing protocol meta at connection */ -#define CURL_META_SMB_CONN "meta:proto:smb:conn" - -enum smb_conn_state { - SMB_NOT_CONNECTED = 0, - SMB_CONNECTING, - SMB_NEGOTIATE, - SMB_SETUP, - SMB_CONNECTED -}; - -/* SMB connection data, kept at connection */ -struct smb_conn { - enum smb_conn_state state; - const char *user; - char *domain; - char *share; - unsigned char challenge[8]; - unsigned int session_key; - unsigned short uid; - char *recv_buf; - char *send_buf; - size_t upload_size; - size_t send_size; - size_t sent; - size_t got; -}; - -/* SMB request state */ -enum smb_req_state { - SMB_REQUESTING, - SMB_TREE_CONNECT, - SMB_OPEN, - SMB_DOWNLOAD, - SMB_UPLOAD, - SMB_CLOSE, - SMB_TREE_DISCONNECT, - SMB_DONE -}; - -/* SMB request data, kept at easy handle */ -struct smb_request { - enum smb_req_state state; - char *path; - unsigned short tid; /* Even if we connect to the same tree as another */ - unsigned short fid; /* request, the tid will be different */ - CURLcode result; -}; - -/* - * Definitions for SMB protocol data structures - */ -#if defined(_MSC_VER) || defined(__ILEC400__) -# define PACK -# pragma pack(push) -# pragma pack(1) -#elif defined(__GNUC__) -# define PACK __attribute__((packed)) -#else -# define PACK -#endif - -#define SMB_COM_CLOSE 0x04 -#define SMB_COM_READ_ANDX 0x2e -#define SMB_COM_WRITE_ANDX 0x2f -#define SMB_COM_TREE_DISCONNECT 0x71 -#define SMB_COM_NEGOTIATE 0x72 -#define SMB_COM_SETUP_ANDX 0x73 -#define SMB_COM_TREE_CONNECT_ANDX 0x75 -#define SMB_COM_NT_CREATE_ANDX 0xa2 -#define SMB_COM_NO_ANDX_COMMAND 0xff - -#define SMB_WC_CLOSE 0x03 -#define SMB_WC_READ_ANDX 0x0c -#define SMB_WC_WRITE_ANDX 0x0e -#define SMB_WC_SETUP_ANDX 0x0d -#define SMB_WC_TREE_CONNECT_ANDX 0x04 -#define SMB_WC_NT_CREATE_ANDX 0x18 - -#define SMB_FLAGS_CANONICAL_PATHNAMES 0x10 -#define SMB_FLAGS_CASELESS_PATHNAMES 0x08 -/* #define SMB_FLAGS2_UNICODE_STRINGS 0x8000 */ -#define SMB_FLAGS2_IS_LONG_NAME 0x0040 -#define SMB_FLAGS2_KNOWS_LONG_NAME 0x0001 - -#define SMB_CAP_LARGE_FILES 0x08 -#define SMB_GENERIC_WRITE 0x40000000 -#define SMB_GENERIC_READ 0x80000000 -#define SMB_FILE_SHARE_ALL 0x07 -#define SMB_FILE_OPEN 0x01 -#define SMB_FILE_OVERWRITE_IF 0x05 - -#define SMB_ERR_NOACCESS 0x00050001 - -struct smb_header { - unsigned char nbt_type; - unsigned char nbt_flags; - unsigned short nbt_length; - unsigned char magic[4]; - unsigned char command; - unsigned int status; - unsigned char flags; - unsigned short flags2; - unsigned short pid_high; - unsigned char signature[8]; - unsigned short pad; - unsigned short tid; - unsigned short pid; - unsigned short uid; - unsigned short mid; -} PACK; - -struct smb_negotiate_response { - struct smb_header h; - unsigned char word_count; - unsigned short dialect_index; - unsigned char security_mode; - unsigned short max_mpx_count; - unsigned short max_number_vcs; - unsigned int max_buffer_size; - unsigned int max_raw_size; - unsigned int session_key; - unsigned int capabilities; - unsigned int system_time_low; - unsigned int system_time_high; - unsigned short server_time_zone; - unsigned char encryption_key_length; - unsigned short byte_count; - char bytes[1]; -} PACK; - -struct andx { - unsigned char command; - unsigned char pad; - unsigned short offset; -} PACK; - -struct smb_setup { - unsigned char word_count; - struct andx andx; - unsigned short max_buffer_size; - unsigned short max_mpx_count; - unsigned short vc_number; - unsigned int session_key; - unsigned short lengths[2]; - unsigned int pad; - unsigned int capabilities; - unsigned short byte_count; - char bytes[1024]; -} PACK; - -struct smb_tree_connect { - unsigned char word_count; - struct andx andx; - unsigned short flags; - unsigned short pw_len; - unsigned short byte_count; - char bytes[1024]; -} PACK; - -struct smb_nt_create { - unsigned char word_count; - struct andx andx; - unsigned char pad; - unsigned short name_length; - unsigned int flags; - unsigned int root_fid; - unsigned int access; - curl_off_t allocation_size; - unsigned int ext_file_attributes; - unsigned int share_access; - unsigned int create_disposition; - unsigned int create_options; - unsigned int impersonation_level; - unsigned char security_flags; - unsigned short byte_count; - char bytes[1024]; -} PACK; - -struct smb_nt_create_response { - struct smb_header h; - unsigned char word_count; - struct andx andx; - unsigned char op_lock_level; - unsigned short fid; - unsigned int create_disposition; - - curl_off_t create_time; - curl_off_t last_access_time; - curl_off_t last_write_time; - curl_off_t last_change_time; - unsigned int ext_file_attributes; - curl_off_t allocation_size; - curl_off_t end_of_file; -} PACK; - -struct smb_read { - unsigned char word_count; - struct andx andx; - unsigned short fid; - unsigned int offset; - unsigned short max_bytes; - unsigned short min_bytes; - unsigned int timeout; - unsigned short remaining; - unsigned int offset_high; - unsigned short byte_count; -} PACK; - -struct smb_write { - struct smb_header h; - unsigned char word_count; - struct andx andx; - unsigned short fid; - unsigned int offset; - unsigned int timeout; - unsigned short write_mode; - unsigned short remaining; - unsigned short pad; - unsigned short data_length; - unsigned short data_offset; - unsigned int offset_high; - unsigned short byte_count; - unsigned char pad2; -} PACK; - -struct smb_close { - unsigned char word_count; - unsigned short fid; - unsigned int last_mtime; - unsigned short byte_count; -} PACK; - -struct smb_tree_disconnect { - unsigned char word_count; - unsigned short byte_count; -} PACK; - -#if defined(_MSC_VER) || defined(__ILEC400__) -# pragma pack(pop) -#endif - -#define MAX_PAYLOAD_SIZE 0x8000 -#define MAX_MESSAGE_SIZE (MAX_PAYLOAD_SIZE + 0x1000) -#define CLIENTNAME "curl" -#define SERVICENAME "?????" - -/* SMB is mostly little endian */ -#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ - defined(__OS400__) -static unsigned short smb_swap16(unsigned short x) -{ - return (unsigned short)((x << 8) | ((x >> 8) & 0xff)); -} - -static unsigned int smb_swap32(unsigned int x) -{ - return (x << 24) | ((x << 8) & 0xff0000) | ((x >> 8) & 0xff00) | - ((x >> 24) & 0xff); -} - -static curl_off_t smb_swap64(curl_off_t x) -{ - return ((curl_off_t)smb_swap32((unsigned int)x) << 32) | - smb_swap32((unsigned int)(x >> 32)); -} - -#else -# define smb_swap16(x) (x) -# define smb_swap32(x) (x) -# define smb_swap64(x) (x) -#endif - -static void conn_state(struct Curl_easy *data, struct smb_conn *smbc, - enum smb_conn_state newstate) -{ -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - /* For debug purposes */ - static const char * const names[] = { - "SMB_NOT_CONNECTED", - "SMB_CONNECTING", - "SMB_NEGOTIATE", - "SMB_SETUP", - "SMB_CONNECTED", - /* LAST */ - }; - - if(smbc->state != newstate) - infof(data, "SMB conn %p state change from %s to %s", - (void *)smbc, names[smbc->state], names[newstate]); -#else - (void)data; -#endif - smbc->state = newstate; -} - -static void request_state(struct Curl_easy *data, - enum smb_req_state newstate) -{ - struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); - if(req) { -#if defined(DEBUGBUILD) && defined(CURLVERBOSE) - /* For debug purposes */ - static const char * const names[] = { - "SMB_REQUESTING", - "SMB_TREE_CONNECT", - "SMB_OPEN", - "SMB_DOWNLOAD", - "SMB_UPLOAD", - "SMB_CLOSE", - "SMB_TREE_DISCONNECT", - "SMB_DONE", - /* LAST */ - }; - - if(req->state != newstate) - infof(data, "SMB request %p state change from %s to %s", - (void *)req, names[req->state], names[newstate]); -#endif - - req->state = newstate; - } -} - -static void smb_easy_dtor(void *key, size_t klen, void *entry) -{ - struct smb_request *req = entry; - (void)key; - (void)klen; - curlx_safefree(req->path); - curlx_free(req); -} - -static void smb_conn_dtor(void *key, size_t klen, void *entry) -{ - struct smb_conn *smbc = entry; - (void)key; - (void)klen; - curlx_safefree(smbc->share); - curlx_safefree(smbc->domain); - curlx_safefree(smbc->recv_buf); - curlx_safefree(smbc->send_buf); - curlx_free(smbc); -} - -static CURLcode smb_parse_url_path(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - char *path; - char *slash, *s; - CURLcode result; - - /* URL decode the path */ - result = Curl_urldecode(data->state.up.path, 0, &path, NULL, REJECT_CTRL); - if(result) - return result; - - /* Parse the path for the share */ - curlx_safefree(smbc->share); - smbc->share = curlx_strdup((*path == '/' || *path == '\\') - ? path + 1 : path); - curlx_free(path); - if(!smbc->share) - return CURLE_OUT_OF_MEMORY; - - slash = strchr(smbc->share, '/'); - if(!slash) - slash = strchr(smbc->share, '\\'); - - /* The share must be present */ - if(!slash) { - curlx_safefree(smbc->share); - failf(data, "missing share in URL path for SMB"); - return CURLE_URL_MALFORMAT; - } - - /* Parse the path for the file path converting any forward slashes into - backslashes */ - *slash++ = 0; - for(s = slash; *s; s++) { - if(*s == '/') - *s = '\\'; - } - /* keep a copy at easy struct to not share this with connection state */ - req->path = curlx_strdup(slash); - if(!req->path) - return CURLE_OUT_OF_MEMORY; - - return CURLE_OK; -} - -/* this should setup things in the connection, not in the easy - handle */ -static CURLcode smb_setup_connection(struct Curl_easy *data, - struct connectdata *conn) -{ - struct smb_conn *smbc; - struct smb_request *req; - - /* Initialize the connection state */ - smbc = curlx_calloc(1, sizeof(*smbc)); - if(!smbc || - Curl_conn_meta_set(conn, CURL_META_SMB_CONN, smbc, smb_conn_dtor)) - return CURLE_OUT_OF_MEMORY; - - /* Initialize the request state */ - req = curlx_calloc(1, sizeof(*req)); - if(!req || - Curl_meta_set(data, CURL_META_SMB_EASY, req, smb_easy_dtor)) - return CURLE_OUT_OF_MEMORY; - - /* Parse the URL path */ - return smb_parse_url_path(data, smbc, req); -} - -static CURLcode smb_connect(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - const char *slash; - const char *user = Curl_creds_user(conn->creds); - - (void)done; - if(!smbc) - return CURLE_FAILED_INIT; - - /* Check we have a username and password to authenticate with */ - if(!Curl_creds_has_user(data->state.creds)) - return CURLE_LOGIN_DENIED; - - /* Initialize the connection state */ - smbc->state = SMB_CONNECTING; - smbc->recv_buf = curlx_malloc(MAX_MESSAGE_SIZE); - if(!smbc->recv_buf) - return CURLE_OUT_OF_MEMORY; - smbc->send_buf = curlx_malloc(MAX_MESSAGE_SIZE); - if(!smbc->send_buf) - return CURLE_OUT_OF_MEMORY; - - /* Parse the username, domain, and password */ - slash = strchr(user, '/'); - if(!slash) - slash = strchr(user, '\\'); - - if(slash) { - smbc->user = slash + 1; - smbc->domain = curlx_strdup(user); - if(!smbc->domain) - return CURLE_OUT_OF_MEMORY; - smbc->domain[slash - user] = 0; - } - else { - smbc->user = user; - smbc->domain = curlx_strdup(conn->origin->hostname); - if(!smbc->domain) - return CURLE_OUT_OF_MEMORY; - } - - return CURLE_OK; -} - -static CURLcode smb_recv_message(struct Curl_easy *data, - struct smb_conn *smbc, - void **msg) -{ - char *buf = smbc->recv_buf; - size_t bytes_read; - size_t nbt_size; - size_t msg_size = sizeof(struct smb_header); - size_t len = MAX_MESSAGE_SIZE - smbc->got; - CURLcode result; - - result = Curl_xfer_recv(data, buf + smbc->got, len, &bytes_read); - if(result) - return result; - - if(!bytes_read) - return CURLE_OK; - - smbc->got += bytes_read; - - /* Check for a 32-bit nbt header */ - if(smbc->got < sizeof(unsigned int)) - return CURLE_OK; - - nbt_size = Curl_read16_be((const unsigned char *) - (buf + sizeof(unsigned short))) + - sizeof(unsigned int); - if(nbt_size > MAX_MESSAGE_SIZE) { - failf(data, "too large NetBIOS frame size %zu", nbt_size); - return CURLE_RECV_ERROR; - } - else if(nbt_size < msg_size) { - /* Each SMB message must be at least this large, e.g. 32 bytes */ - failf(data, "too small NetBIOS frame size %zu", nbt_size); - return CURLE_RECV_ERROR; - } - - if(smbc->got < nbt_size) - return CURLE_OK; - - if(nbt_size >= msg_size + 1) { - /* Add the word count */ - msg_size += 1 + (((unsigned char)buf[msg_size]) * sizeof(unsigned short)); - if(nbt_size >= msg_size + sizeof(unsigned short)) { - /* Add the byte count */ - msg_size += sizeof(unsigned short) + - Curl_read16_le((const unsigned char *)&buf[msg_size]); - if(nbt_size < msg_size) - return CURLE_RECV_ERROR; - } - } - - *msg = buf; - - return CURLE_OK; -} - -static void smb_pop_message(struct smb_conn *smbc) -{ - smbc->got = 0; -} - -static void smb_format_message(struct smb_conn *smbc, - struct smb_request *req, - struct smb_header *h, - unsigned char cmd, size_t len) -{ - const unsigned int pid = 0xbad71d; /* made up */ - - memset(h, 0, sizeof(*h)); - h->nbt_length = htons((unsigned short)(sizeof(*h) - sizeof(unsigned int) + - len)); - memcpy((char *)h->magic, "\xffSMB", 4); - h->command = cmd; - h->flags = SMB_FLAGS_CANONICAL_PATHNAMES | SMB_FLAGS_CASELESS_PATHNAMES; - h->flags2 = smb_swap16(SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_KNOWS_LONG_NAME); - h->uid = smb_swap16(smbc->uid); - h->tid = smb_swap16(req->tid); - h->pid_high = smb_swap16((unsigned short)(pid >> 16)); - h->pid = smb_swap16((unsigned short)pid); -} - -static CURLcode smb_send(struct Curl_easy *data, struct smb_conn *smbc, - size_t len, size_t upload_size) -{ - size_t bytes_written; - CURLcode result; - - result = Curl_xfer_send(data, smbc->send_buf, len, FALSE, &bytes_written); - if(result) - return result; - - if(bytes_written != len) { - smbc->send_size = len; - smbc->sent = bytes_written; - } - - smbc->upload_size = upload_size; - - return CURLE_OK; -} - -static CURLcode smb_flush(struct Curl_easy *data, struct smb_conn *smbc) -{ - size_t bytes_written; - size_t len = smbc->send_size - smbc->sent; - CURLcode result; - - if(!smbc->send_size) - return CURLE_OK; - - result = Curl_xfer_send(data, smbc->send_buf + smbc->sent, len, FALSE, - &bytes_written); - if(result) - return result; - - if(bytes_written != len) - smbc->sent += bytes_written; - else - smbc->send_size = 0; - - return CURLE_OK; -} - -static CURLcode smb_send_message(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req, - unsigned char cmd, - const void *msg, size_t msg_len) -{ - if((MAX_MESSAGE_SIZE - sizeof(struct smb_header)) < msg_len) { - DEBUGASSERT(0); - return CURLE_SEND_ERROR; - } - smb_format_message(smbc, req, (struct smb_header *)smbc->send_buf, - cmd, msg_len); - memcpy(smbc->send_buf + sizeof(struct smb_header), msg, msg_len); - - return smb_send(data, smbc, sizeof(struct smb_header) + msg_len, 0); -} - -static CURLcode smb_send_negotiate(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - static const char msg[] = "\x00\x0c\x00\x02NT LM 0.12"; - - return smb_send_message(data, smbc, req, SMB_COM_NEGOTIATE, msg, - sizeof(msg)); -} - -static CURLcode smb_send_setup(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); - struct smb_setup msg; - char *p = msg.bytes; - unsigned char lm_hash[21]; - unsigned char lm[24]; - unsigned char nt_hash[21]; - unsigned char nt[24]; - size_t byte_count; - const char *passwd = Curl_creds_passwd(conn->creds); - - if(!smbc || !req) - return CURLE_FAILED_INIT; - - byte_count = sizeof(lm) + sizeof(nt) + - strlen(smbc->user) + strlen(smbc->domain) + - CURL_CSTRLEN(CURL_OS) + CURL_CSTRLEN(CLIENTNAME) + 4; /* 4 null chars */ - if(byte_count > sizeof(msg.bytes)) - return CURLE_FILESIZE_EXCEEDED; - - Curl_ntlm_core_mk_lm_hash(passwd, lm_hash); - Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm); - Curl_ntlm_core_mk_nt_hash(passwd, nt_hash); - Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); - - memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); - msg.word_count = SMB_WC_SETUP_ANDX; - msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - msg.max_buffer_size = smb_swap16(MAX_MESSAGE_SIZE); - msg.max_mpx_count = smb_swap16(1); - msg.vc_number = smb_swap16(1); - msg.session_key = smb_swap32(smbc->session_key); - msg.capabilities = smb_swap32(SMB_CAP_LARGE_FILES); - msg.lengths[0] = smb_swap16(sizeof(lm)); - msg.lengths[1] = smb_swap16(sizeof(nt)); - memcpy(p, lm, sizeof(lm)); - p += sizeof(lm); - memcpy(p, nt, sizeof(nt)); - p += sizeof(nt); - p += curl_msnprintf(p, byte_count - sizeof(nt) - sizeof(lm), - "%s%c" /* user */ - "%s%c" /* domain */ - "%s%c" /* OS */ - "%s", /* client name */ - smbc->user, 0, smbc->domain, 0, CURL_OS, 0, CLIENTNAME); - p++; /* count the final null-termination */ - DEBUGASSERT(byte_count == (size_t)(p - msg.bytes)); - msg.byte_count = smb_swap16((unsigned short)byte_count); - - return smb_send_message(data, smbc, req, SMB_COM_SETUP_ANDX, &msg, - sizeof(msg) - sizeof(msg.bytes) + byte_count); -} - -static CURLcode smb_send_tree_connect(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_tree_connect msg; - struct connectdata *conn = data->conn; - char *p = msg.bytes; - const size_t byte_count = strlen(conn->origin->hostname) + - strlen(smbc->share) + - CURL_CSTRLEN(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ - - if(byte_count > sizeof(msg.bytes)) - return CURLE_FILESIZE_EXCEEDED; - - memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); - msg.word_count = SMB_WC_TREE_CONNECT_ANDX; - msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - msg.pw_len = 0; - - p += curl_msnprintf(p, byte_count, - "\\\\%s\\" /* hostname */ - "%s%c" /* share */ - "%s", /* service */ - conn->origin->hostname, smbc->share, 0, SERVICENAME); - p++; /* count the final null-termination */ - DEBUGASSERT(byte_count == (size_t)(p - msg.bytes)); - msg.byte_count = smb_swap16((unsigned short)byte_count); - - return smb_send_message(data, smbc, req, SMB_COM_TREE_CONNECT_ANDX, &msg, - sizeof(msg) - sizeof(msg.bytes) + byte_count); -} - -static CURLcode smb_send_open(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_nt_create msg; - const size_t byte_count = strlen(req->path) + 1; - - if(byte_count > sizeof(msg.bytes)) - return CURLE_FILESIZE_EXCEEDED; - - memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); - msg.word_count = SMB_WC_NT_CREATE_ANDX; - msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - msg.name_length = smb_swap16((unsigned short)(byte_count - 1)); - msg.share_access = smb_swap32(SMB_FILE_SHARE_ALL); - if(data->state.upload) { - msg.access = smb_swap32(SMB_GENERIC_READ | SMB_GENERIC_WRITE); - msg.create_disposition = smb_swap32(SMB_FILE_OVERWRITE_IF); - } - else { - msg.access = smb_swap32(SMB_GENERIC_READ); - msg.create_disposition = smb_swap32(SMB_FILE_OPEN); - } - msg.byte_count = smb_swap16((unsigned short)byte_count); - curlx_strcopy(msg.bytes, sizeof(msg.bytes), req->path, byte_count - 1); - - return smb_send_message(data, smbc, req, SMB_COM_NT_CREATE_ANDX, &msg, - sizeof(msg) - sizeof(msg.bytes) + byte_count); -} - -static CURLcode smb_send_close(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_close msg; - - memset(&msg, 0, sizeof(msg)); - msg.word_count = SMB_WC_CLOSE; - msg.fid = smb_swap16(req->fid); - - return smb_send_message(data, smbc, req, SMB_COM_CLOSE, &msg, sizeof(msg)); -} - -static CURLcode smb_send_tree_disconnect(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_tree_disconnect msg; - memset(&msg, 0, sizeof(msg)); - return smb_send_message(data, smbc, req, SMB_COM_TREE_DISCONNECT, - &msg, sizeof(msg)); -} - -static CURLcode smb_send_read(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - curl_off_t offset = data->req.offset; - struct smb_read msg; - - memset(&msg, 0, sizeof(msg)); - msg.word_count = SMB_WC_READ_ANDX; - msg.andx.command = SMB_COM_NO_ANDX_COMMAND; - msg.fid = smb_swap16(req->fid); - msg.offset = smb_swap32((unsigned int)offset); - msg.offset_high = smb_swap32((unsigned int)(offset >> 32)); - msg.min_bytes = smb_swap16(MAX_PAYLOAD_SIZE); - msg.max_bytes = smb_swap16(MAX_PAYLOAD_SIZE); - - return smb_send_message(data, smbc, req, SMB_COM_READ_ANDX, - &msg, sizeof(msg)); -} - -static CURLcode smb_send_write(struct Curl_easy *data, - struct smb_conn *smbc, - struct smb_request *req) -{ - struct smb_write *msg; - curl_off_t offset = data->req.offset; - curl_off_t upload_size = data->req.size - data->req.bytecount; - - msg = (struct smb_write *)smbc->send_buf; - if(upload_size >= MAX_PAYLOAD_SIZE - 1) /* There is one byte of padding */ - upload_size = MAX_PAYLOAD_SIZE - 1; - - memset(msg, 0, sizeof(*msg)); - msg->word_count = SMB_WC_WRITE_ANDX; - msg->andx.command = SMB_COM_NO_ANDX_COMMAND; - msg->fid = smb_swap16(req->fid); - msg->offset = smb_swap32((unsigned int)offset); - msg->offset_high = smb_swap32((unsigned int)(offset >> 32)); - msg->data_length = smb_swap16((unsigned short)upload_size); - msg->data_offset = smb_swap16(sizeof(*msg) - sizeof(unsigned int)); - msg->byte_count = smb_swap16((unsigned short)(upload_size + 1)); - - smb_format_message(smbc, req, &msg->h, SMB_COM_WRITE_ANDX, - sizeof(*msg) - sizeof(msg->h) + (size_t)upload_size); - - return smb_send(data, smbc, sizeof(*msg), (size_t)upload_size); -} - -static CURLcode smb_send_and_recv(struct Curl_easy *data, - struct smb_conn *smbc, void **msg) -{ - CURLcode result; - *msg = NULL; /* if it returns early */ - - /* Check if there is data in the transfer buffer */ - if(!smbc->send_size && smbc->upload_size) { - size_t nread = smbc->upload_size > (size_t)MAX_MESSAGE_SIZE ? - (size_t)MAX_MESSAGE_SIZE : smbc->upload_size; - bool eos; - - result = Curl_client_read(data, smbc->send_buf, nread, &nread, &eos); - if(result && result != CURLE_AGAIN) - return result; - if(!nread) - return CURLE_OK; - - smbc->upload_size -= nread; - smbc->send_size = nread; - smbc->sent = 0; - } - - /* Check if there is data to send */ - if(smbc->send_size) { - result = smb_flush(data, smbc); - if(result) - return result; - } - - /* Check if there is still data to be sent */ - if(smbc->send_size || smbc->upload_size) - return CURLE_AGAIN; - - return smb_recv_message(data, smbc, msg); -} - -static CURLcode smb_connection_state(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); - struct smb_negotiate_response *nrsp; - struct smb_header *h; - CURLcode result; - void *msg = NULL; - - if(!smbc || !req) - return CURLE_FAILED_INIT; - - if(smbc->state == SMB_CONNECTING) { -#ifdef USE_SSL - if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - bool ssl_done = FALSE; - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssl_done); - if(result && result != CURLE_AGAIN) - return result; - if(!ssl_done) - return CURLE_OK; - } -#endif - - result = smb_send_negotiate(data, smbc, req); - if(result) { - CURL_TRC_M(data, "SMB: failed to send negotiate message"); - connclose(conn); - return result; - } - - conn_state(data, smbc, SMB_NEGOTIATE); - } - - /* Send the previous message and check for a response */ - result = smb_send_and_recv(data, smbc, &msg); - if(result && result != CURLE_AGAIN) { - CURL_TRC_M(data, "SMB: failed to communicate"); - connclose(conn); - return result; - } - - if(!msg) - return CURLE_OK; - - h = msg; - - switch(smbc->state) { - case SMB_NEGOTIATE: - if((smbc->got < sizeof(*nrsp) + sizeof(smbc->challenge) - 1) || - h->status) { - CURL_TRC_M(data, "SMB: negotiation failed"); - connclose(conn); - return CURLE_COULDNT_CONNECT; - } - nrsp = msg; -#if defined(__GNUC__) && __GNUC__ >= 13 -#pragma GCC diagnostic push -/* error: 'memcpy' offset [74, 80] from the object at '' is out of - the bounds of referenced subobject 'bytes' with type 'char[1]' */ -#pragma GCC diagnostic ignored "-Warray-bounds" -#endif - memcpy(smbc->challenge, nrsp->bytes, sizeof(smbc->challenge)); -#if defined(__GNUC__) && __GNUC__ >= 13 -#pragma GCC diagnostic pop -#endif - smbc->session_key = smb_swap32(nrsp->session_key); - result = smb_send_setup(data); - if(result) { - CURL_TRC_M(data, "SMB: failed to send setup message"); - connclose(conn); - return result; - } - conn_state(data, smbc, SMB_SETUP); - break; - - case SMB_SETUP: - if(h->status) { - CURL_TRC_M(data, "SMB: authentication failed"); - connclose(conn); - return CURLE_LOGIN_DENIED; - } - smbc->uid = smb_swap16(h->uid); - conn_state(data, smbc, SMB_CONNECTED); - *done = TRUE; - break; - - default: - smb_pop_message(smbc); - return CURLE_OK; /* ignore */ - } - - smb_pop_message(smbc); - - return CURLE_OK; -} - -/* - * Convert a timestamp from the Windows world (100 nsec units from 1 Jan 1601) - * to POSIX time. Cap the output to fit within a time_t. - */ -static void get_posix_time(time_t *out, curl_off_t timestamp) -{ - if(timestamp >= (curl_off_t)116444736000000000) { - timestamp -= (curl_off_t)116444736000000000; - timestamp /= 10000000; -#if SIZEOF_TIME_T < SIZEOF_CURL_OFF_T - if(timestamp > TIME_T_MAX) - *out = TIME_T_MAX; - else if(timestamp < TIME_T_MIN) - *out = TIME_T_MIN; - else -#endif - *out = (time_t)timestamp; - } - else - *out = 0; -} - -static CURLcode smb_request_state(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - struct smb_request *req = Curl_meta_get(data, CURL_META_SMB_EASY); - struct smb_header *h; - enum smb_req_state next_state = SMB_DONE; - size_t len; - size_t off; - CURLcode result; - void *msg = NULL; - const struct smb_nt_create_response *smb_m; - - if(!smbc || !req) - return CURLE_FAILED_INIT; - - if(data->state.upload && (data->state.infilesize < 0)) { - failf(data, "SMB upload needs to know the size up front"); - return CURLE_SEND_ERROR; - } - - /* Start the request */ - if(req->state == SMB_REQUESTING) { - result = smb_send_tree_connect(data, smbc, req); - if(result) { - CURL_TRC_M(data, "SMB: failed to send tree connect message"); - connclose(conn); - return result; - } - - request_state(data, SMB_TREE_CONNECT); - } - - /* Send the previous message and check for a response */ - result = smb_send_and_recv(data, smbc, &msg); - if(result && result != CURLE_AGAIN) { - CURL_TRC_M(data, "SMB: failed to communicate"); - connclose(conn); - return result; - } - - if(!msg) - return CURLE_OK; - - h = msg; - - switch(req->state) { - case SMB_TREE_CONNECT: - if(h->status) { - req->result = CURLE_REMOTE_FILE_NOT_FOUND; - if(h->status == smb_swap32(SMB_ERR_NOACCESS)) - req->result = CURLE_REMOTE_ACCESS_DENIED; - break; - } - req->tid = smb_swap16(h->tid); - next_state = SMB_OPEN; - break; - - case SMB_OPEN: - if(h->status || smbc->got < sizeof(struct smb_nt_create_response)) { - req->result = CURLE_REMOTE_FILE_NOT_FOUND; - if(h->status == smb_swap32(SMB_ERR_NOACCESS)) - req->result = CURLE_REMOTE_ACCESS_DENIED; - next_state = SMB_TREE_DISCONNECT; - break; - } - smb_m = (const struct smb_nt_create_response *)msg; - req->fid = smb_swap16(smb_m->fid); - data->req.offset = 0; - if(data->state.upload) { - data->req.size = data->state.infilesize; - Curl_pgrsSetUploadSize(data, data->req.size); - next_state = SMB_UPLOAD; - } - else { - data->req.size = smb_swap64(smb_m->end_of_file); - if(data->req.size < 0) { - req->result = CURLE_WEIRD_SERVER_REPLY; - next_state = SMB_CLOSE; - } - else { - Curl_pgrsSetDownloadSize(data, data->req.size); - if(data->set.get_filetime) - get_posix_time(&data->info.filetime, smb_m->last_change_time); - next_state = SMB_DOWNLOAD; - } - } - break; - - case SMB_DOWNLOAD: - if(h->status || smbc->got < sizeof(struct smb_header) + 15) { - req->result = CURLE_RECV_ERROR; - next_state = SMB_CLOSE; - break; - } - len = Curl_read16_le((const unsigned char *)msg + - sizeof(struct smb_header) + 11); - off = Curl_read16_le((const unsigned char *)msg + - sizeof(struct smb_header) + 13); - if(len > 0) { - if(off + sizeof(unsigned int) + len > smbc->got) { - failf(data, "Invalid input packet"); - result = CURLE_RECV_ERROR; - } - else - result = Curl_client_write(data, CLIENTWRITE_BODY, - (char *)msg + off + sizeof(unsigned int), - len); - if(result) { - req->result = result; - next_state = SMB_CLOSE; - break; - } - } - data->req.offset += len; - next_state = (len < MAX_PAYLOAD_SIZE) ? SMB_CLOSE : SMB_DOWNLOAD; - break; - - case SMB_UPLOAD: - if(h->status || smbc->got < sizeof(struct smb_header) + 7) { - req->result = CURLE_UPLOAD_FAILED; - next_state = SMB_CLOSE; - break; - } - len = Curl_read16_le((const unsigned char *)msg + - sizeof(struct smb_header) + 5); - data->req.bytecount += len; - data->req.offset += len; - Curl_pgrs_upload_inc(data, len); - if(data->req.bytecount >= data->req.size) - next_state = SMB_CLOSE; - else - next_state = SMB_UPLOAD; - break; - - case SMB_CLOSE: - /* We do not care if the close failed, proceed to tree disconnect anyway */ - next_state = SMB_TREE_DISCONNECT; - break; - - case SMB_TREE_DISCONNECT: - next_state = SMB_DONE; - break; - - default: - smb_pop_message(smbc); - return CURLE_OK; /* ignore */ - } - - smb_pop_message(smbc); - - switch(next_state) { - case SMB_OPEN: - result = smb_send_open(data, smbc, req); - break; - - case SMB_DOWNLOAD: - result = smb_send_read(data, smbc, req); - break; - - case SMB_UPLOAD: - result = smb_send_write(data, smbc, req); - break; - - case SMB_CLOSE: - result = smb_send_close(data, smbc, req); - break; - - case SMB_TREE_DISCONNECT: - result = smb_send_tree_disconnect(data, smbc, req); - break; - - case SMB_DONE: - result = req->result; - *done = TRUE; - break; - - default: - break; - } - - if(result) { - CURL_TRC_M(data, "SMB: failed to send message"); - connclose(conn); - return result; - } - - request_state(data, next_state); - - return CURLE_OK; -} - -static CURLcode smb_pollset(struct Curl_easy *data, - struct easy_pollset *ps) -{ - return Curl_pollset_add_inout(data, ps, data->conn->sock[FIRSTSOCKET]); -} - -static CURLcode smb_do(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - struct smb_conn *smbc = Curl_conn_meta_get(conn, CURL_META_SMB_CONN); - - *done = FALSE; - if(!smbc) - return CURLE_FAILED_INIT; - if(smbc->share) - return CURLE_OK; - return CURLE_URL_MALFORMAT; -} - -/* - * SMB handler interface - */ -const struct Curl_protocol Curl_protocol_smb = { - smb_setup_connection, /* setup_connection */ - smb_do, /* do_it */ - ZERO_NULL, /* done */ - ZERO_NULL, /* do_more */ - smb_connect, /* connect_it */ - smb_connection_state, /* connecting */ - smb_request_state, /* doing */ - smb_pollset, /* proto_pollset */ - smb_pollset, /* doing_pollset */ - ZERO_NULL, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_is_dead */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ -}; - -#endif /* CURL_ENABLE_SMB && USE_CURL_NTLM_CORE */ diff --git a/lib/smtp.c b/lib/smtp.c index 50e2af400a10..b02eea23f9b5 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -274,10 +274,8 @@ static CURLcode smtp_parse_address(struct Curl_easy *data, const char *fqma, if(fqma[0] != '<') { length = strlen(dup); - if(length) { - if(dup[length - 1] == '>') - dup[length - 1] = '\0'; - } + if(length && dup[length - 1] == '>') + dup[length - 1] = '\0'; } else { addressend = strrchr(dup, '>'); @@ -1018,11 +1016,8 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, result = Curl_mime_prepare_headers(data, postp, NULL, NULL, MIMESTRATEGY_MAIL); - if(!result) - if(!Curl_checkheaders(data, STRCONST("Mime-Version"))) - result = Curl_mime_add_header(&postp->curlheaders, - "Mime-Version: 1.0"); - + if(!result && !Curl_checkheaders(data, STRCONST("Mime-Version"))) + result = Curl_mime_add_header(&postp->curlheaders, "Mime-Version: 1.0"); if(!result) result = Curl_creader_set_mime(data, postp); if(result) @@ -1922,10 +1917,9 @@ static CURLcode smtp_disconnect(struct Curl_easy *data, disconnect wait in vain and cause more problems than we need to. */ if(!dead_connection && conn->bits.protoconnstart && - !Curl_pp_needs_flush(data, &smtpc->pp)) { - if(!smtp_perform_quit(data, smtpc)) - (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */ - } + !Curl_pp_needs_flush(data, &smtpc->pp) && + !smtp_perform_quit(data, smtpc)) + (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */ CURL_TRC_SMTP(data, "smtp_disconnect(), finished"); return CURLE_OK; @@ -1953,7 +1947,7 @@ static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) return result; } -static void smtp_easy_dtor(void *key, size_t klen, void *entry) +static void smtp_easy_dtor(const void *key, size_t klen, void *entry) { struct SMTP *smtp = entry; (void)key; @@ -1961,7 +1955,7 @@ static void smtp_easy_dtor(void *key, size_t klen, void *entry) curlx_free(smtp); } -static void smtp_conn_dtor(void *key, size_t klen, void *entry) +static void smtp_conn_dtor(const void *key, size_t klen, void *entry) { struct smtp_conn *smtpc = entry; (void)key; diff --git a/lib/socketpair.c b/lib/socketpair.c index d4ac66e95ebe..bbd6cbac76b3 100644 --- a/lib/socketpair.c +++ b/lib/socketpair.c @@ -55,10 +55,23 @@ static int wakeup_eventfd(curl_socket_t socks[2], bool nonblocking) static int wakeup_pipe(curl_socket_t socks[2], bool nonblocking) { #ifdef HAVE_PIPE2 - int flags = nonblocking ? O_NONBLOCK | O_CLOEXEC : O_CLOEXEC; - if(pipe2(socks, flags)) - return -1; -#else + return pipe2(socks, (nonblocking ? O_NONBLOCK : 0) | O_CLOEXEC) ? -1 : 0; +#elif defined(__APPLE__) && defined(HAVE_BUILTIN_AVAILABLE) && \ + ((defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 270000) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 270000) || \ + (defined(__TV_OS_VERSION_MAX_ALLOWED) && \ + __TV_OS_VERSION_MAX_ALLOWED >= 270000) || \ + (defined(__WATCH_OS_VERSION_MAX_ALLOWED) && \ + __WATCH_OS_VERSION_MAX_ALLOWED >= 270000) || \ + (defined(__VISION_OS_VERSION_MAX_ALLOWED) && \ + __VISION_OS_VERSION_MAX_ALLOWED >= 270000)) + if(__builtin_available(macOS 27, iOS 27, tvOS 27, watchOS 27, visionOS 27, + macCatalyst 27, *)) + return pipe2(socks, (nonblocking ? O_NONBLOCK : 0) | O_CLOEXEC) ? -1 : 0; + else +#endif if(pipe(socks)) return -1; #ifdef HAVE_FCNTL @@ -79,8 +92,6 @@ static int wakeup_pipe(curl_socket_t socks[2], bool nonblocking) return -1; } } -#endif - return 0; } @@ -221,17 +232,21 @@ static int wakeup_inet(curl_socket_t socks[2], bool nonblocking) do { ssize_t nread; + /* Do not block forever */ + if(curlx_timediff_ms(curlx_now(), start) > (60 * 1000)) + goto error; + pfd[0].fd = socks[1]; pfd[0].events = POLLIN; pfd[0].revents = 0; (void)Curl_poll(pfd, 1, 1000); /* one second */ nread = sread(socks[1], p, s); - if(nread == -1) { + if(!nread) { /* unexpected close before getting complete `check` */ + goto error; + } + else if(nread == -1) { /* error or close */ int sockerr = SOCKERRNO; - /* Do not block forever */ - if(curlx_timediff_ms(curlx_now(), start) > (60 * 1000)) - goto error; if(SOCK_EAGAIN(sockerr) #ifndef USE_WINSOCK || (sockerr == SOCKEINTR) || (sockerr == SOCKEINPROGRESS) diff --git a/lib/socks.c b/lib/socks.c index 949f20c13539..ca7d3e27daa8 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -293,7 +293,7 @@ static CURLproxycode socks4_req_add_user(struct socks_ctx *sx, failf(data, "Too long SOCKS proxy username"); return CURLPX_LONG_USER; } - /* add proxy name WITH trailing zero */ + /* add proxy name WITH null-terminator */ result = Curl_bufq_cwrite(&sx->iobuf, sx->creds->user, plen + 1, &nwritten); if(result || (nwritten != (plen + 1))) @@ -586,9 +586,16 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf, /* disable username/password auth */ Curl_creds_unlink(&sx->creds); + if(cf->sockindex == FIRSTSOCKET) + /* the reuse of this connection depends on how this negotiation ends */ + cf->conn->bits.socks5_authenticated = FALSE; + req[0] = 5; /* version */ - nauths = 1; - req[1 + nauths] = 0; /* 1. no authentication */ + nauths = 0; + if(!data->set.socks5_auth_only) { + ++nauths; + req[1 + nauths] = 0; /* no authentication */ + } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) if(auth & CURLAUTH_GSSAPI) { ++nauths; @@ -599,6 +606,10 @@ static CURLproxycode socks5_req0_init(struct Curl_cfilter *cf, ++nauths; req[1 + nauths] = 2; /* username/password */ } + if(!nauths) { + failf(data, "SOCKS5: no acceptable authentication method is available."); + return CURLPX_NO_AUTH; + } req[1] = nauths; req_len = 2 + nauths; @@ -631,11 +642,17 @@ static CURLproxycode socks5_check_resp0(struct socks_ctx *sx, switch(auth_mode) { case 0: + if(data->set.socks5_auth_only) { + failf(data, "SOCKS5 proxy selected no authentication."); + return CURLPX_NO_AUTH; + } /* DONE! No authentication needed. Send request. */ sxstate(sx, cf, data, SOCKS5_ST_REQ1_INIT); return CURLPX_OK; case 1: if(data->set.socks5auth & CURLAUTH_GSSAPI) { + if(cf->sockindex == FIRSTSOCKET) + cf->conn->bits.socks5_authenticated = TRUE; sxstate(sx, cf, data, SOCKS5_ST_GSSAPI_INIT); return CURLPX_OK; } @@ -644,6 +661,8 @@ static CURLproxycode socks5_check_resp0(struct socks_ctx *sx, case 2: /* regular name + password authentication */ if(data->set.socks5auth & CURLAUTH_BASIC) { + if(cf->sockindex == FIRSTSOCKET) + cf->conn->bits.socks5_authenticated = TRUE; sxstate(sx, cf, data, SOCKS5_ST_AUTH_INIT); return CURLPX_OK; } diff --git a/lib/splay.c b/lib/splay.c index 788da215ee54..df3734d53c5b 100644 --- a/lib/splay.c +++ b/lib/splay.c @@ -26,12 +26,24 @@ #include "urldata.h" #include "splay.h" +static struct Curl_tree *splay(timediff_t key, + struct Curl_tree *root); + +#ifdef UNITTESTS +uint32_t Curl_splayget(struct Curl_tree *node) +{ + DEBUGASSERT(node); + return node->id; +} + +#endif void Curl_timeouts_init(struct Curl_timeouts *timeouts, const struct curltime *ptime_base) { + struct curltime time_base = ptime_base ? *ptime_base : curlx_now(); timeouts->tree = NULL; - timeouts->time_base = ptime_base ? *ptime_base : curlx_now(); + timeouts->time_base_sec = time_base.tv_sec; } bool Curl_timeouts_has(struct Curl_easy *data) @@ -43,7 +55,10 @@ bool Curl_timeouts_has(struct Curl_easy *data) timediff_t Curl_timeouts_offset_us(struct Curl_timeouts *timeouts, const struct curltime *pts) { - return curlx_ptimediff_us(pts, &timeouts->time_base); + struct curltime time_base; + time_base.tv_sec = timeouts->time_base_sec; + time_base.tv_usec = 0; + return curlx_ptimediff_us(pts, &time_base); } int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, @@ -52,7 +67,7 @@ int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, uint32_t *pmid) { if(timeouts->tree) { /* splay the lowest key to the root */ - timeouts->tree = Curl_splay(TIMEDIFF_T_MIN, timeouts->tree); + timeouts->tree = splay(TIMEDIFF_T_MIN, timeouts->tree); } if(timeouts->tree) { @@ -76,50 +91,6 @@ int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts, return -1; } -bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts, - const struct curltime *ts, - uint32_t *pmid) -{ - if(timeouts->tree) { - struct Curl_tree *t = NULL; - timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, ts); - timeouts->tree = Curl_splaygetbest(elapsed_us, timeouts->tree, &t); - if(t) { - *pmid = t->id; - return TRUE; - } - } - *pmid = UINT32_MAX; - return FALSE; -} - -void Curl_timeouts_add(struct Curl_timeouts *timeouts, - struct Curl_easy *data, - timediff_t offset_us) -{ - struct Curl_tree *node = &data->state.timeouts.splaynode; - DEBUGASSERT(!node->registered); - timeouts->tree = Curl_splayinsert(offset_us, timeouts->tree, - node, data->mid); -} - -bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, - struct Curl_easy *data) -{ - struct Curl_tree *node = &data->state.timeouts.splaynode; - if(node->registered) { - int rc = Curl_splayremove(timeouts->tree, node, &timeouts->tree); -#ifdef DEBUGBUILD - if(rc) - curl_mfprintf(stderr, "Internal error removing splay node = %d\n", rc); -#else - (void)rc; -#endif - return TRUE; - } - return FALSE; -} - /* * Splay using the key i (which may or may not be in the tree). * This rotates the tree, so: @@ -128,7 +99,7 @@ bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, * - root->key may equal `key` or not * */ -struct Curl_tree *Curl_splay(timediff_t key, +struct Curl_tree *splay(timediff_t key, struct Curl_tree *root) { struct Curl_tree N, *l, *r, *y; @@ -199,12 +170,16 @@ struct Curl_tree *Curl_splay(timediff_t key, /* Insert key i into the tree t. Return a pointer to the resulting tree or * NULL if something went wrong. * - * @unittest: 1309 + * @unittest 1309 */ -struct Curl_tree *Curl_splayinsert(timediff_t key, - struct Curl_tree *root, - struct Curl_tree *node, - uint32_t id) +UNITTEST struct Curl_tree *splayinsert(timediff_t key, + struct Curl_tree *root, + struct Curl_tree *node, + uint32_t id); +UNITTEST struct Curl_tree *splayinsert(timediff_t key, + struct Curl_tree *root, + struct Curl_tree *node, + uint32_t id) { DEBUGASSERT(node); @@ -213,7 +188,7 @@ struct Curl_tree *Curl_splayinsert(timediff_t key, node->same = NULL; node->registered = TRUE; if(root) { - root = Curl_splay(key, root); + root = splay(key, root); DEBUGASSERT(root); if(key == root->key) { /* There already exists a node in the tree with the same key. @@ -244,12 +219,28 @@ struct Curl_tree *Curl_splayinsert(timediff_t key, return node; } +void Curl_timeouts_add(struct Curl_timeouts *timeouts, + struct Curl_easy *data, + timediff_t offset_us) +{ + struct Curl_tree *node = &data->state.timeouts.splaynode; + DEBUGASSERT(!node->registered); + timeouts->tree = splayinsert(offset_us, timeouts->tree, + node, data->mid); +} + /* Finds and deletes the best-fit node from the tree. Return a pointer to the resulting tree. best-fit means the smallest node if it is not larger than - the key */ -struct Curl_tree *Curl_splaygetbest(timediff_t key, - struct Curl_tree *root, - struct Curl_tree **removed) + the key + + @unittest 1309 +*/ +UNITTEST struct Curl_tree *splaygetbest(timediff_t key, + struct Curl_tree *root, + struct Curl_tree **removed); +UNITTEST struct Curl_tree *splaygetbest(timediff_t key, + struct Curl_tree *root, + struct Curl_tree **removed) { struct Curl_tree *x; @@ -259,7 +250,7 @@ struct Curl_tree *Curl_splaygetbest(timediff_t key, } /* find smallest */ - root = Curl_splay(TIMEDIFF_T_MIN, root); + root = splay(TIMEDIFF_T_MIN, root); DEBUGASSERT(root); if(key < root->key) { /* even the smallest is too big */ @@ -288,6 +279,23 @@ struct Curl_tree *Curl_splaygetbest(timediff_t key, return x; } +bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts, + const struct curltime *ts, + uint32_t *pmid) +{ + if(timeouts->tree) { + struct Curl_tree *t = NULL; + timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, ts); + timeouts->tree = splaygetbest(elapsed_us, timeouts->tree, &t); + if(t) { + *pmid = t->id; + return TRUE; + } + } + *pmid = UINT32_MAX; + return FALSE; +} + /* Deletes the node we point out from the tree if it is there. Stores a * pointer to the new resulting tree in 'newroot'. * @@ -297,11 +305,14 @@ struct Curl_tree *Curl_splaygetbest(timediff_t key, * NOTE: when the last node of the tree is removed, there is no tree left so * 'newroot' will be made to point to NULL. * - * @unittest: 1309 + * @unittest 1309 */ -int Curl_splayremove(struct Curl_tree *root, - struct Curl_tree *removenode, - struct Curl_tree **newroot) +UNITTEST int splayremove(struct Curl_tree *root, + struct Curl_tree *removenode, + struct Curl_tree **newroot); +UNITTEST int splayremove(struct Curl_tree *root, + struct Curl_tree *removenode, + struct Curl_tree **newroot) { struct Curl_tree *x; @@ -312,7 +323,7 @@ int Curl_splayremove(struct Curl_tree *root, if(!removenode->registered) return 2; - root = Curl_splay(removenode->key, root); + root = splay(removenode->key, root); DEBUGASSERT(root); /* First make sure that we got the same root key as the one we want @@ -353,7 +364,7 @@ int Curl_splayremove(struct Curl_tree *root, if(!root->smaller) x = root->larger; else { - x = Curl_splay(removenode->key, root->smaller); + x = splay(removenode->key, root->smaller); DEBUGASSERT(x); x->larger = root->larger; } @@ -363,15 +374,19 @@ int Curl_splayremove(struct Curl_tree *root, return 0; } -/* set and get the custom payload for this tree node */ -void Curl_splayset(struct Curl_tree *node, uint32_t id) -{ - DEBUGASSERT(node); - node->id = id; -} - -uint32_t Curl_splayget(struct Curl_tree *node) +bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, + struct Curl_easy *data) { - DEBUGASSERT(node); - return node->id; + struct Curl_tree *node = &data->state.timeouts.splaynode; + if(node->registered) { + int rc = splayremove(timeouts->tree, node, &timeouts->tree); +#ifdef DEBUGBUILD + if(rc) + curl_mfprintf(stderr, "Internal error removing splay node = %d\n", rc); +#else + (void)rc; +#endif + return TRUE; + } + return FALSE; } diff --git a/lib/splay.h b/lib/splay.h index 3f9e66297cd2..5731d810cce4 100644 --- a/lib/splay.h +++ b/lib/splay.h @@ -41,7 +41,7 @@ struct Curl_tree { struct Curl_timeouts { struct Curl_tree *tree; - struct curltime time_base; + time_t time_base_sec; }; void Curl_timeouts_init(struct Curl_timeouts *timeouts, @@ -69,24 +69,8 @@ void Curl_timeouts_add(struct Curl_timeouts *timeouts, bool Curl_timeouts_remove(struct Curl_timeouts *timeouts, struct Curl_easy *data); -struct Curl_tree *Curl_splay(timediff_t key, - struct Curl_tree *root); - -struct Curl_tree *Curl_splayinsert(timediff_t key, - struct Curl_tree *root, - struct Curl_tree *node, - uint32_t id); - -struct Curl_tree *Curl_splaygetbest(timediff_t key, - struct Curl_tree *root, - struct Curl_tree **removed); - -int Curl_splayremove(struct Curl_tree *root, - struct Curl_tree *removenode, - struct Curl_tree **newroot); - -/* set and get the custom payload for this tree node */ -void Curl_splayset(struct Curl_tree *node, uint32_t id); +#ifdef UNITTESTS uint32_t Curl_splayget(struct Curl_tree *node); +#endif #endif /* HEADER_CURL_SPLAY_H */ diff --git a/lib/system_win32.c b/lib/system_win32.c deleted file mode 100644 index 4cbc2c25e9bf..000000000000 --- a/lib/system_win32.c +++ /dev/null @@ -1,76 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Steve Holme, . - * - * 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 _WIN32 - -#include "system_win32.h" -#include "curl_sspi.h" -#include "curlx/timeval.h" -#include "curlx/version_win32.h" /* for curlx_verify_windows_init() */ - -/* Curl_win32_init() performs Win32 global initialization */ -CURLcode Curl_win32_init(long flags) -{ - /* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which - is for Winsock at the moment. Any required Win32 initialization - should take place after this block. */ - if(flags & CURL_GLOBAL_WIN32) { -#ifdef USE_WINSOCK - WSADATA wsa; - if(WSAStartup(MAKEWORD(2, 2), &wsa)) - return CURLE_FAILED_INIT; -#elif defined(USE_LWIPSOCK) - lwip_init(); -#endif - } /* CURL_GLOBAL_WIN32 */ - -#ifdef USE_WINDOWS_SSPI - { - CURLcode result = Curl_sspi_global_init(); - if(result) - return result; - } -#endif - - curlx_verify_windows_init(); - curlx_now_init(); - return CURLE_OK; -} - -/* Curl_win32_cleanup() is the opposite of Curl_win32_init() */ -void Curl_win32_cleanup(long init_flags) -{ -#ifdef USE_WINDOWS_SSPI - Curl_sspi_global_cleanup(); -#endif - - if(init_flags & CURL_GLOBAL_WIN32) { -#ifdef USE_WINSOCK - WSACleanup(); -#endif - } -} - -#endif /* _WIN32 */ diff --git a/lib/telnet.c b/lib/telnet.c index 735fa66156fd..07b2513b6edd 100644 --- a/lib/telnet.c +++ b/lib/telnet.c @@ -167,7 +167,7 @@ static void printoption(struct Curl_easy *data, } #endif /* !CURLVERBOSE */ -static void telnet_easy_dtor(void *key, size_t klen, void *entry) +static void telnet_easy_dtor(const void *key, size_t klen, void *entry) { struct TELNET *tn = entry; (void)key; @@ -225,16 +225,16 @@ static CURLcode init_telnet(struct Curl_easy *data) static void send_negotiation(struct Curl_easy *data, int cmd, int option) { unsigned char buf[3]; - ssize_t bytes_written; - struct connectdata *conn = data->conn; + size_t nwritten; + CURLcode result; buf[0] = CURL_IAC; buf[1] = (unsigned char)cmd; buf[2] = (unsigned char)option; - bytes_written = swrite(conn->sock[FIRSTSOCKET], buf, 3); - if(bytes_written < 0) - failf(data, "Sending data failed (%d)", SOCKERRNO); + result = Curl_xfer_send(data, buf, 3, FALSE, &nwritten); + if(result) + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); printoption(data, "SENT", cmd, option); } @@ -671,10 +671,10 @@ static CURLcode send_telnet_data(struct Curl_easy *data, static void sendsuboption(struct Curl_easy *data, struct TELNET *tn, int option) { - ssize_t bytes_written; + size_t nwritten; unsigned short x, y; const unsigned char *uc1, *uc2; - struct connectdata *conn = data->conn; + CURLcode result; switch(option) { case CURL_TELOPT_NAWS: @@ -703,16 +703,16 @@ static void sendsuboption(struct Curl_easy *data, CURL_SB_LEN(tn) - 2); /* we send the header of the suboption... */ - bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer, 3); - if(bytes_written < 0) - failf(data, "Sending data failed (%d)", SOCKERRNO); + result = Curl_xfer_send(data, tn->subbuffer, 3, FALSE, &nwritten); + if(result) + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); /* ... then the window size with the send_telnet_data() function to deal with 0xFF cases ... */ send_telnet_data(data, tn, (const char *)tn->subbuffer + 3, 4); /* ... and the footer */ - bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer + 7, 2); - if(bytes_written < 0) - failf(data, "Sending data failed (%d)", SOCKERRNO); + result = Curl_xfer_send(data, tn->subbuffer + 7, 2, FALSE, &nwritten); + if(result) + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); break; } } @@ -972,9 +972,9 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) { struct curl_slist *v; unsigned char temp[2048]; - ssize_t bytes_written; + size_t nwritten; size_t len; - struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; if(!CURL_SB_LEN(tn)) /* ignore empty suboption */ return CURLE_OK; @@ -993,11 +993,10 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE, CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE); - bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - - if(bytes_written < 0) { - failf(data, "Sending data failed (%d)", SOCKERRNO); - return CURLE_SEND_ERROR; + result = Curl_xfer_send(data, temp, len, FALSE, &nwritten); + if(result) { + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); + return result; } printsub(data, '>', &temp[2], len-2); break; @@ -1012,10 +1011,10 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC, CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE); - bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - if(bytes_written < 0) { - failf(data, "Sending data failed (%d)", SOCKERRNO); - return CURLE_SEND_ERROR; + result = Curl_xfer_send(data, temp, len, FALSE, &nwritten); + if(result) { + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); + return result; } printsub(data, '>', &temp[2], len - 2); break; @@ -1044,9 +1043,11 @@ static CURLcode suboption(struct Curl_easy *data, struct TELNET *tn) curl_msnprintf((char *)&temp[len], sizeof(temp) - len, "%c%c", CURL_IAC, CURL_SE); len += 2; - bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - if(bytes_written < 0) - failf(data, "Sending data failed (%d)", SOCKERRNO); + result = Curl_xfer_send(data, temp, len, FALSE, &nwritten); + if(result) { + failf(data, "Sending data failed: %s", curl_easy_strerror(result)); + return result; + } printsub(data, '>', &temp[2], len - 2); break; } @@ -1414,13 +1415,10 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) } } /* switch */ - if(data->set.timeout) { - if(curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) >= - data->set.timeout) { - failf(data, "Time-out"); - result = CURLE_OPERATION_TIMEDOUT; - keepon = FALSE; - } + if(Curl_timeleft_ms(data) < 0) { + failf(data, "Time-out"); + result = CURLE_OPERATION_TIMEDOUT; + keepon = FALSE; } } @@ -1533,13 +1531,10 @@ static CURLcode telnet_do(struct Curl_easy *data, bool *done) break; } /* poll switch statement */ - if(data->set.timeout) { - if(curlx_ptimediff_ms(Curl_pgrs_now(data), &conn->created) >= - data->set.timeout) { - failf(data, "Time-out"); - result = CURLE_OPERATION_TIMEDOUT; - keepon = FALSE; - } + if(Curl_timeleft_ms(data) < 0) { + failf(data, "Time-out"); + result = CURLE_OPERATION_TIMEDOUT; + keepon = FALSE; } if(!result) { diff --git a/lib/tftp.c b/lib/tftp.c index d95da69dc373..edc26739b3ef 100644 --- a/lib/tftp.c +++ b/lib/tftp.c @@ -366,6 +366,9 @@ static CURLcode tftp_tx(struct tftp_conn *state, tftp_event_t event) char *bufptr; bool eos; + if(!data->state.upload) + return CURLE_TFTP_ILLEGAL; + switch(event) { case TFTP_EVENT_ACK: @@ -773,10 +776,14 @@ static CURLcode tftp_send_first(struct tftp_conn *state, break; case TFTP_EVENT_ACK: /* Connected for transmit */ + if(!data->state.upload) + return CURLE_TFTP_ILLEGAL; result = tftp_connect_for_tx(state, event); break; case TFTP_EVENT_DATA: /* Connected for receive */ + if(data->state.upload) + return CURLE_TFTP_ILLEGAL; result = tftp_connect_for_rx(state, event); break; @@ -883,7 +890,7 @@ static CURLcode tftp_state_machine(struct tftp_conn *state, return result; } -static void tftp_conn_dtor(void *key, size_t klen, void *entry) +static void tftp_conn_dtor(const void *key, size_t klen, void *entry) { struct tftp_conn *state = entry; (void)key; @@ -1010,7 +1017,7 @@ static CURLcode tftp_done(struct Curl_easy *data, CURLcode status, (void)status; (void)premature; - if(Curl_pgrsDone(data)) + if(Curl_pgrsDone(data, Curl_pgrs_now(data))) return CURLE_ABORTED_BY_CALLBACK; /* If we have encountered an error */ @@ -1077,6 +1084,8 @@ static CURLcode tftp_receive_packet(struct Curl_easy *data, switch(state->event) { case TFTP_EVENT_DATA: + if(data->state.upload) + return CURLE_TFTP_ILLEGAL; /* Do not pass to the client empty or retransmitted packets */ if(state->rbytes > 4 && (NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) { diff --git a/lib/thrdqueue.h b/lib/thrdqueue.h index 0d84f6f6f144..ebd0c0785553 100644 --- a/lib/thrdqueue.h +++ b/lib/thrdqueue.h @@ -120,6 +120,13 @@ CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, #ifdef CURLVERBOSE void Curl_thrdq_trace(struct curl_thrdq *tqueue, struct Curl_easy *data); +#define CURL_THRDQ_TRACE(t, d) \ + do { \ + if(Curl_trc_ft_is_verbose(d, &Curl_trc_feat_threads)) \ + Curl_thrdq_trace(t, d); \ + } while(0) +#else +#define CURL_THRDQ_TRACE(t, d) Curl_nop_stmt #endif #endif /* USE_THREADS */ diff --git a/lib/transfer.c b/lib/transfer.c index 3b6c5c03dd36..3f0f2157ae7b 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -147,21 +147,21 @@ static CURLcode xfer_recv_shutdown(struct Curl_easy *data, bool *done) { if(!data || !data->conn) return CURLE_FAILED_INIT; - return Curl_conn_shutdown(data, data->conn->recv_idx, done); + return Curl_cshutdn_try_once_idx(data, data->conn->recv_idx, done); } static bool xfer_recv_shutdown_started(struct Curl_easy *data) { if(!data || !data->conn) return FALSE; - return Curl_shutdown_started(data->conn, data->conn->recv_idx); + return CURL_CONN_IN_SHUTDOWN(data->conn, data->conn->recv_idx); } CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done) { if(!data || !data->conn) return CURLE_FAILED_INIT; - return Curl_conn_shutdown(data, data->conn->send_idx, done); + return Curl_cshutdn_try_once_idx(data, data->conn->send_idx, done); } /** @@ -503,6 +503,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) data->state.authhost.want = data->set.httpauth; data->state.authproxy.want = data->set.proxyauth; curlx_safefree(data->info.wouldredirect); + curlx_safefree(data->info.effective_url); Curl_data_priority_clear_state(data); if(data->set.http_auto_referer) Curl_bufref_free(&data->state.referer); @@ -564,6 +565,10 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) #ifndef CURL_DISABLE_FTP data->state.wildcardmatch = data->set.wildcard_enabled; +#ifdef DEBUGBUILD + if(getenv("CURL_DBG_FTP_WILDCARD")) + data->state.wildcardmatch = TRUE; +#endif if(data->state.wildcardmatch) { struct WildcardData *wc; if(!data->wildcard) { diff --git a/lib/u32_ptrset.c b/lib/u32_ptrset.c new file mode 100644 index 000000000000..bc0f74c41f59 --- /dev/null +++ b/lib/u32_ptrset.c @@ -0,0 +1,279 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "u32_ptrset.h" + +/* random patterns for API verification */ +#ifdef DEBUGBUILD +#define CURL_U32_PTRSET_MAGIC 0x75703272 +#endif + +#define CURL_U32_PTRSET_DEBUG 0 + +#ifndef CURL_SWAP +#define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) +#endif + +#define CURL_U32_SLOT_CNT(bits) ((bits) ? (1U << (bits)) : 0) +#define CURL_U32_PTRSET_SLOT_CNT(s) CURL_U32_SLOT_CNT((s)->slotbits) +#define CURL_U32_PTRSET_SLOT_IDX(s, i) (uint32_t)((i) & (s)->slotmask) + +/* A hashset for tuples (id, ptr) using Robin Hood Hashing. + * + * The basic idea here to handle collisions by robbing "rich" entries and + * giving to the "poor": + * - We have an array: (id, ptr) are ideally placed at index "id % size". + * - If slot at index is already occupied, we have a collision. + * - A simple collision strategy would look at the next index, and the + * next until finding an empty slot. + * - The drawback is that this may lead to many checks on lookups, as it + * will need to also look at subsequent slots until it finds the match. + * The amount of lookups is the "probe sequence length" (psl) and this + * may vary greatly between entries. + * - Robin Hood Hashing balances the 'psl's of all entries more evenly: + * - psl == 0 means an entry is in exactly the right slot + * - psl == 1 means it is in the slot right after. psl == 2 is the slot + * after that, etc. + * - when inserting a new entry, track its psl. Finding a slot where + * the existing entry has a lower psl makes a swap. Put the new entry + * and its psl there, take the previous entry and its psl and find + * the next best slot for the previous entry. */ +void Curl_u32_ptrset_init(struct u32_ptrset *set, + Curl_u32_ptrset_dtor *dtor) +{ + memset(set, 0, sizeof(*set)); + set->dtor = dtor; +#ifdef DEBUGBUILD + set->init = CURL_U32_PTRSET_MAGIC; +#endif +} + +void Curl_u32_ptrset_clear(struct u32_ptrset *set) +{ + uint32_t i; + DEBUGASSERT(set->init == CURL_U32_PTRSET_MAGIC); + if(set->dtor) + for(i = 0; i < CURL_U32_PTRSET_SLOT_CNT(set); ++i) + if(set->data[i]) + set->dtor(set->ids[i], set->data[i]); + + curlx_safefree(set->data); + Curl_u32_ptrset_init(set, set->dtor); +} + +static void u32_ptrset_add(struct u32_ptrset *set, uint32_t id, void *ptr) +{ + uint32_t i = CURL_U32_PTRSET_SLOT_IDX(set, id); + uint32_t psl = 0; + while(set->data[i]) { + if(psl > set->psl[i]) { /* SWAP */ + void *tmpdata; +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_add %u=%p to idx=%u\n", id, ptr, i); +#endif + tmpdata = set->data[i]; + set->data[i] = ptr; + ptr = tmpdata; + CURL_SWAP(set->psl[i], psl); + CURL_SWAP(set->ids[i], id); + } + i = CURL_U32_PTRSET_SLOT_IDX(set, i + 1); + ++psl; + } + set->ids[i] = id; + set->data[i] = ptr; + set->psl[i] = psl; + ++set->count; +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_add %u=%p to idx=%u\n", id, ptr, i); +#endif +} + +static bool u32_ptrset_grow(struct u32_ptrset *set) +{ + uint32_t i, *prev_ids; + uint8_t nslotbits, pslotbits; + void **prev_data; + size_t nslots; + const size_t slot_size = (sizeof(void *) + (2 * sizeof(uint32_t))); + void *d; + + if(set->slotbits >= 31) + return FALSE; + nslotbits = set->slotbits ? (uint8_t)(set->slotbits + 1) : 5; + nslots = CURL_U32_SLOT_CNT(nslotbits); + if(nslots > (SIZE_MAX / slot_size)) /* 32-bit arch may trigger here */ + return FALSE; +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_grow from bits=%d to %d, slots=%zu\n", + set->slotbits, nslotbits, nslots); +#endif + d = curlx_calloc(1, nslots * slot_size); + if(!d) + return FALSE; + + prev_data = set->data; + prev_ids = set->ids; + pslotbits = set->slotbits; +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wanalyzer-allocation-size" +#endif + set->data = d; + set->ids = (uint32_t *)(set->data + nslots); + set->psl = (uint32_t *)(set->ids + nslots); +#if defined(__GNUC__) && __GNUC__ >= 13 +#pragma GCC diagnostic pop +#endif + + set->slotbits = nslotbits; + set->slotmask = (uint32_t)((1U << set->slotbits) - 1); + set->count = 0; + /* re-add previous entries */ + for(i = 0; i < CURL_U32_SLOT_CNT(pslotbits); ++i) { + if(prev_data[i]) + u32_ptrset_add(set, prev_ids[i], prev_data[i]); + } + if(prev_data) + curlx_free(prev_data); + return TRUE; +} + +static bool u32_ptrset_get_index(struct u32_ptrset *set, + uint32_t id, uint32_t *pindex) +{ + uint32_t i = CURL_U32_PTRSET_SLOT_IDX(set, id); + uint32_t psl = 0; + if(!set->data) + return FALSE; + while(set->data[i] && (psl <= set->psl[i])) { + if(set->ids[i] == id) { + *pindex = i; +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_index %u=%p at idx=%u\n", + id, set->data[i], i); +#endif + return TRUE; + } + i = CURL_U32_PTRSET_SLOT_IDX(set, i + 1); + ++psl; + } +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_index %u not found\n", id); +#endif + *pindex = 0; + return FALSE; +} + +uint32_t Curl_u32_ptrset_count(struct u32_ptrset *set) +{ + return set->count; +} + +void *Curl_u32_ptrset_get(struct u32_ptrset *set, uint32_t id) +{ + uint32_t i; + DEBUGASSERT(set->init == CURL_U32_PTRSET_MAGIC); + if(u32_ptrset_get_index(set, id, &i)) + return set->data[i]; + return NULL; +} + +CURLcode Curl_u32_ptrset_set(struct u32_ptrset *set, + uint32_t id, void *ptr) +{ + uint32_t i; + + DEBUGASSERT(set->init == CURL_U32_PTRSET_MAGIC); + if(!ptr) { + Curl_u32_ptrset_unset(set, id); + return CURLE_OK; + } + + if(u32_ptrset_get_index(set, id, &i)) { + /* `id` is in set, replace value */ + if(set->dtor && (set->data[i] != ptr)) + set->dtor(set->ids[i], set->data[i]); +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_set replace %u=%p at idx=%u\n", + id, ptr, i); +#endif + set->data[i] = ptr; + return CURLE_OK; + } + /* `id` not in set yet, grow if full */ + if((set->count >= CURL_U32_PTRSET_SLOT_CNT(set)) && + !u32_ptrset_grow(set)) { + return CURLE_OUT_OF_MEMORY; + } + + u32_ptrset_add(set, id, ptr); + return CURLE_OK; +} + +static void u32_ptrset_unset(struct u32_ptrset *set, uint32_t id) +{ + uint32_t i, j; + + DEBUGASSERT(set->init == CURL_U32_PTRSET_MAGIC); + if(u32_ptrset_get_index(set, id, &i)) { + /* `id` is in set */ +#if CURL_U32_PTRSET_DEBUG + curl_mfprintf(stderr, "u32_ptrset_unset %u at idx=%u\n", id, i); +#endif + if(set->dtor) + set->dtor(set->ids[i], set->data[i]); + set->data[i] = NULL; + set->ids[i] = set->psl[i] = 0; + --set->count; + j = CURL_U32_PTRSET_SLOT_IDX(set, i + 1); + /* shift all entries with positive psl "down" */ + while(set->data[j] && set->psl[j]) { + set->data[i] = set->data[j]; + set->ids[i] = set->ids[j]; + set->psl[i] = set->psl[j] - 1; + set->data[j] = NULL; + set->ids[j] = set->psl[j] = 0; + i = j; + j = CURL_U32_PTRSET_SLOT_IDX(set, i + 1); + } + } +} + +void Curl_u32_ptrset_unset(struct u32_ptrset *set, uint32_t id) +{ + u32_ptrset_unset(set, id); +} + +void Curl_u32_ptrset_visit(struct u32_ptrset *set, + Curl_u32_ptrset_visit_cb *cb, + void *user_data) +{ + uint32_t i; + for(i = 0; i < CURL_U32_PTRSET_SLOT_CNT(set); ++i) { + if(set->data[i] && !cb(set->ids[i], set->data[i], user_data)) + break; + } +} diff --git a/lib/u32_ptrset.h b/lib/u32_ptrset.h new file mode 100644 index 000000000000..6ce9a1190963 --- /dev/null +++ b/lib/u32_ptrset.h @@ -0,0 +1,70 @@ +#ifndef HEADER_CURL_U32_PTRSET_H +#define HEADER_CURL_U32_PTRSET_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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" + +typedef void Curl_u32_ptrset_dtor(uint32_t id, void *ptr); + +/* A set that can hold up to INT32_MAX+1 pointers identified by an `id'. + * Setting a pointer for an existing id replaces the previous one. + * Getting the pointer for an id not in the set returns NULL. + * Setting an id to NULL unsets the id. + */ +struct u32_ptrset { + void **data; /* #slots array of pointers */ + uint32_t *ids; /* #slots array of `id` values */ + uint32_t *psl; /* #slots array of "probe sequence length" values */ + uint32_t slotmask; + uint32_t count; + Curl_u32_ptrset_dtor *dtor; + uint8_t slotbits; +#ifdef DEBUGBUILD + int32_t init; +#endif +}; + +void Curl_u32_ptrset_init(struct u32_ptrset *set, + Curl_u32_ptrset_dtor *dtor); +void Curl_u32_ptrset_clear(struct u32_ptrset *set); + +uint32_t Curl_u32_ptrset_count(struct u32_ptrset *set); + +void *Curl_u32_ptrset_get(struct u32_ptrset *set, uint32_t id); + +/* Set pointer for id. */ +CURLcode Curl_u32_ptrset_set(struct u32_ptrset *set, + uint32_t id, void *ptr); + +void Curl_u32_ptrset_unset(struct u32_ptrset *set, uint32_t id); + +typedef bool Curl_u32_ptrset_visit_cb(uint32_t id, void *ptr, + void *user_data); + +/* Visit all entries in the set until the callback returns FALSE */ +void Curl_u32_ptrset_visit(struct u32_ptrset *set, + Curl_u32_ptrset_visit_cb *cb, + void *user_data); + +#endif /* HEADER_CURL_U32_PTRSET_H */ diff --git a/lib/uint-hashset.c b/lib/u8_strset.c similarity index 84% rename from lib/uint-hashset.c rename to lib/u8_strset.c index 84e9e5ec7bef..e2e368e01a1c 100644 --- a/lib/uint-hashset.c +++ b/lib/u8_strset.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "curl_setup.h" -#include "uint-hashset.h" +#include "u8_strset.h" #include "curlx/strdup.h" /* random patterns for API verification */ @@ -35,21 +35,10 @@ #define CURL_SWAP(a, b) (((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) -static const uint8_t u8_smask[] = { - 0x00U, - 0x01U, - 0x03U, - 0x07U, - 0x0FU, - 0x1FU, - 0x3FU, - 0x7FU, - 0xFFU, -}; - -#define CURL_U8_SET_SLOT_IDX(s, i) (uint8_t)((i) & u8_smask[(s)->slotbits]) -#define CURL_U8_SLOT_CNT(i) ((uint16_t)u8_smask[(i)] + 1) -#define CURL_U8_SET_SLOT_CNT(s) CURL_U8_SLOT_CNT((s)->slotbits) +#define CURL_U8_STRSET_SLOT_IDX(s, i) (uint8_t)((i) & (s)->slotmask) +#define CURL_U8_SLOT_CNT(i) (uint16_t)((i) ? (1U << (i)) : 0) +#define CURL_U8_SLOT_MASK(i) (uint8_t)((1U << (i)) - 1) +#define CURL_U8_STRSET_SLOT_CNT(s) CURL_U8_SLOT_CNT((s)->slotbits) /* A hashset for tuples (id, string) using Robin Hood Hashing. * @@ -85,6 +74,7 @@ void Curl_u8_strset_init(struct u8_strset *set) set->ids = set->sids; set->psl = set->spsl; set->slotbits = CURL_U8_STRSET_START_BITS; + set->slotmask = CURL_U8_SLOT_MASK(set->slotbits); set->count = 0; #ifdef DEBUGBUILD set->init = CURL_U8_STRSET_MAGIC; @@ -95,7 +85,7 @@ void Curl_u8_strset_clear(struct u8_strset *set) { uint16_t i; DEBUGASSERT(set->init == CURL_U8_STRSET_MAGIC); - for(i = 0; i < CURL_U8_SET_SLOT_CNT(set); ++i) + for(i = 0; i < CURL_U8_STRSET_SLOT_CNT(set); ++i) curlx_safefree(set->data[i]); if(set->data != set->sdata) @@ -105,7 +95,7 @@ void Curl_u8_strset_clear(struct u8_strset *set) static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) { - uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); + uint8_t i = CURL_U8_STRSET_SLOT_IDX(set, id); uint8_t psl = 0; while(set->data[i]) { if(psl > set->psl[i]) { /* SWAP */ @@ -116,7 +106,7 @@ static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) CURL_SWAP(set->psl[i], psl); CURL_SWAP(set->ids[i], id); } - i = CURL_U8_SET_SLOT_IDX(set, i + 1); + i = CURL_U8_STRSET_SLOT_IDX(set, i + 1); ++psl; } set->ids[i] = id; @@ -127,7 +117,7 @@ static void u8_strset_addn(struct u8_strset *set, uint8_t id, char *val) static bool u8_strset_grow(struct u8_strset *set) { - uint8_t i, *prev_ids, nslotbits; + uint8_t *prev_ids, nslotbits; uint16_t prev_slots; char **prev_data; size_t nslots; @@ -159,11 +149,15 @@ static bool u8_strset_grow(struct u8_strset *set) set->ids = (uint8_t *)d + (nslots * sizeof(char *)); set->psl = set->ids + nslots; set->slotbits = nslotbits; + set->slotmask = CURL_U8_SLOT_MASK(set->slotbits); set->count = 0; /* re-add previous entries */ - for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { - if(prev_data[i]) - u8_strset_addn(set, prev_ids[i], prev_data[i]); + { + uint16_t i; + for(i = 0; i < CURL_U8_SLOT_CNT(prev_slots); ++i) { + if(prev_data[i]) + u8_strset_addn(set, prev_ids[i], prev_data[i]); + } } if(prev_data != set->sdata) curlx_free(prev_data); @@ -173,7 +167,7 @@ static bool u8_strset_grow(struct u8_strset *set) static bool u8_strset_get_index(struct u8_strset *set, uint8_t id, uint8_t *pindex) { - uint8_t i = CURL_U8_SET_SLOT_IDX(set, id); + uint8_t i = CURL_U8_STRSET_SLOT_IDX(set, id); uint8_t psl = 0; while(set->data[i] && (psl <= set->psl[i])) { if(set->ids[i] == id) { @@ -183,7 +177,7 @@ static bool u8_strset_get_index(struct u8_strset *set, #endif return TRUE; } - i = CURL_U8_SET_SLOT_IDX(set, i + 1); + i = CURL_U8_STRSET_SLOT_IDX(set, i + 1); ++psl; } #if CURL_U8_STRSET_DEBUG @@ -193,10 +187,13 @@ static bool u8_strset_get_index(struct u8_strset *set, return FALSE; } +#ifdef UNITTESTS +/* @unittest 3211 */ uint16_t Curl_u8_strset_count(struct u8_strset *set) { return set->count; } +#endif const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id) { @@ -228,7 +225,7 @@ CURLcode Curl_u8_strset_setn(struct u8_strset *set, return CURLE_OK; } /* `id` not in set yet, grow if full */ - if((set->count >= CURL_U8_SET_SLOT_CNT(set)) && !u8_strset_grow(set)) { + if((set->count >= CURL_U8_STRSET_SLOT_CNT(set)) && !u8_strset_grow(set)) { curlx_free(str); return CURLE_OUT_OF_MEMORY; } @@ -254,8 +251,11 @@ CURLcode Curl_u8_strset_setx(struct u8_strset *set, return Curl_u8_strset_setn(set, id, val); } -CURLcode Curl_u8_strset_set(struct u8_strset *set, - uint8_t id, const char *str) +/* @unittest 3211 */ +UNITTEST CURLcode u8_strset_set(struct u8_strset *set, + uint8_t id, const char *str); +UNITTEST CURLcode u8_strset_set(struct u8_strset *set, + uint8_t id, const char *str) { return Curl_u8_strset_setx(set, id, str, str ? strlen(str) : 0); } @@ -272,7 +272,7 @@ static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero) curlx_safefree(set->data[i]); set->ids[i] = set->psl[i] = 0; --set->count; - j = CURL_U8_SET_SLOT_IDX(set, i + 1); + j = CURL_U8_STRSET_SLOT_IDX(set, i + 1); /* shift all entries with positive psl "down" */ while(set->data[j] && set->psl[j]) { set->data[i] = set->data[j]; @@ -281,7 +281,7 @@ static void u8_strset_unset(struct u8_strset *set, uint8_t id, bool zero) set->data[j] = NULL; set->ids[j] = set->psl[j] = 0; i = j; - j = CURL_U8_SET_SLOT_IDX(set, i + 1); + j = CURL_U8_STRSET_SLOT_IDX(set, i + 1); } } } @@ -303,9 +303,9 @@ CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src) DEBUGASSERT(src->init == CURL_U8_STRSET_MAGIC); Curl_u8_strset_clear(dest); - for(i = 0; !result && (i < CURL_U8_SET_SLOT_CNT(src)); ++i) { + for(i = 0; !result && (i < CURL_U8_STRSET_SLOT_CNT(src)); ++i) { if(src->data[i]) - result = Curl_u8_strset_set(dest, src->ids[i], src->data[i]); + result = u8_strset_set(dest, src->ids[i], src->data[i]); } return result; } diff --git a/lib/uint-hashset.h b/lib/u8_strset.h similarity index 93% rename from lib/uint-hashset.h rename to lib/u8_strset.h index e5a1e02f00a8..bf23cd8521f8 100644 --- a/lib/uint-hashset.h +++ b/lib/u8_strset.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_UINT_HASHSET_H -#define HEADER_CURL_UINT_HASHSET_H +#ifndef HEADER_CURL_U8_STRSET_H +#define HEADER_CURL_U8_STRSET_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -53,6 +53,7 @@ struct u8_strset { uint8_t spsl[CURL_U8_STRSET_START_DIM]; uint16_t count; uint8_t slotbits; + uint8_t slotmask; #ifdef DEBUGBUILD int32_t init; #endif @@ -61,12 +62,13 @@ struct u8_strset { void Curl_u8_strset_init(struct u8_strset *set); void Curl_u8_strset_clear(struct u8_strset *set); +#ifdef UNITTESTS uint16_t Curl_u8_strset_count(struct u8_strset *set); +#endif + const char *Curl_u8_strset_get(struct u8_strset *set, uint8_t id); /* Set string for id, makes a copy. */ -CURLcode Curl_u8_strset_set(struct u8_strset *set, - uint8_t id, const char *str); CURLcode Curl_u8_strset_setx(struct u8_strset *set, uint8_t id, const char *str, size_t slen); /* Set string for id, takes ownership of `str` even on failure. */ @@ -79,4 +81,4 @@ void Curl_u8_strset_unset0(struct u8_strset *set, uint8_t id); CURLcode Curl_u8_strset_copy(struct u8_strset *dest, struct u8_strset *src); -#endif /* HEADER_CURL_UINT_HASHSET_H */ +#endif /* HEADER_CURL_U8_STRSET_H */ diff --git a/lib/uint-bset.c b/lib/uint-bset.c index 55aedb234b05..82b84e3b5af8 100644 --- a/lib/uint-bset.c +++ b/lib/uint-bset.c @@ -80,7 +80,7 @@ uint32_t Curl_uint32_bset_count(struct uint32_bset *bset) { uint32_t i; uint32_t n = 0; - for(i = 0; i < bset->nslots; ++i) { + for(i = bset->first_slot_used; i < bset->nslots; ++i) { if(bset->slots[i]) n += CURL_POPCOUNT64(bset->slots[i]); } diff --git a/lib/uint-hash.c b/lib/uint-hash.c deleted file mode 100644 index c37448fd1130..000000000000 --- a/lib/uint-hash.c +++ /dev/null @@ -1,235 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , 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 "uint-hash.h" - -/* random patterns for API verification */ -#ifdef DEBUGBUILD -#define CURL_UINT32_HASHINIT 0x7117e779 -#endif - -static uint32_t uint32_hash_hash(uint32_t id, uint32_t slots) -{ - return (id % slots); -} - -struct uint_hash_entry { - struct uint_hash_entry *next; - void *value; - uint32_t id; -}; - -void Curl_uint32_hash_init(struct uint_hash *h, - uint32_t slots, - Curl_uint32_hash_dtor *dtor) -{ - DEBUGASSERT(h); - DEBUGASSERT(slots); - - h->table = NULL; - h->dtor = dtor; - h->size = 0; - h->slots = slots; -#ifdef DEBUGBUILD - h->init = CURL_UINT32_HASHINIT; -#endif -} - -static struct uint_hash_entry *uint32_hash_mk_entry(uint32_t id, void *value) -{ - struct uint_hash_entry *e; - - /* allocate the struct for the hash entry */ - e = curlx_malloc(sizeof(*e)); - if(e) { - e->id = id; - e->next = NULL; - e->value = value; - } - return e; -} - -static void uint32_hash_entry_clear(struct uint_hash *h, - struct uint_hash_entry *e) -{ - DEBUGASSERT(h); - DEBUGASSERT(e); - if(e->value) { - if(h->dtor) - h->dtor(e->id, e->value); - e->value = NULL; - } -} - -static void uint32_hash_entry_destroy(struct uint_hash *h, - struct uint_hash_entry *e) -{ - uint32_hash_entry_clear(h, e); - curlx_free(e); -} - -static void uint32_hash_entry_unlink(struct uint_hash *h, - struct uint_hash_entry **he_anchor, - struct uint_hash_entry *he) -{ - *he_anchor = he->next; - --h->size; -} - -static void uint32_hash_elem_link(struct uint_hash *h, - struct uint_hash_entry **he_anchor, - struct uint_hash_entry *he) -{ - he->next = *he_anchor; - *he_anchor = he; - ++h->size; -} - -#define CURL_UINT32_HASH_SLOT(h, id) h->table[uint32_hash_hash(id, (h)->slots)] -#define CURL_UINT32_HASH_SLOT_ADDR(h, id) &CURL_UINT32_HASH_SLOT(h, id) - -bool Curl_uint32_hash_set(struct uint_hash *h, uint32_t id, void *value) -{ - struct uint_hash_entry *he, **slot; - - DEBUGASSERT(h); - DEBUGASSERT(h->slots); - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - if(!h->table) { - h->table = curlx_calloc(h->slots, sizeof(*he)); - if(!h->table) - return FALSE; /* OOM */ - } - - slot = CURL_UINT32_HASH_SLOT_ADDR(h, id); - for(he = *slot; he; he = he->next) { - if(he->id == id) { - /* existing key entry, overwrite by clearing old pointer */ - uint32_hash_entry_clear(h, he); - he->value = value; - return TRUE; - } - } - - he = uint32_hash_mk_entry(id, value); - if(!he) - return FALSE; /* OOM */ - - uint32_hash_elem_link(h, slot, he); - return TRUE; -} - -bool Curl_uint32_hash_remove(struct uint_hash *h, uint32_t id) -{ - DEBUGASSERT(h); - DEBUGASSERT(h->slots); - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - if(h->table) { - struct uint_hash_entry *he, **he_anchor; - - he_anchor = CURL_UINT32_HASH_SLOT_ADDR(h, id); - while(*he_anchor) { - he = *he_anchor; - if(id == he->id) { - uint32_hash_entry_unlink(h, he_anchor, he); - uint32_hash_entry_destroy(h, he); - return TRUE; - } - he_anchor = &he->next; - } - } - return FALSE; -} - -void *Curl_uint32_hash_get(struct uint_hash *h, uint32_t id) -{ - DEBUGASSERT(h); - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - if(h->table) { - struct uint_hash_entry *he; - DEBUGASSERT(h->slots); - he = CURL_UINT32_HASH_SLOT(h, id); - while(he) { - if(id == he->id) { - return he->value; - } - he = he->next; - } - } - return NULL; -} - -/* @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; - size_t i; - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - for(i = 0; i < h->slots; ++i) { - he_anchor = &h->table[i]; - while(*he_anchor) { - he = *he_anchor; - uint32_hash_entry_unlink(h, he_anchor, he); - uint32_hash_entry_destroy(h, he); - } - } - } -} - -void Curl_uint32_hash_destroy(struct uint_hash *h) -{ - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - if(h->table) { - uint_hash_clear(h); - curlx_safefree(h->table); - } - DEBUGASSERT(h->size == 0); - h->slots = 0; -} - -uint32_t Curl_uint32_hash_count(struct uint_hash *h) -{ - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - return h->size; -} - -void Curl_uint32_hash_visit(struct uint_hash *h, - Curl_uint32_hash_visit_cb *cb, - void *user_data) -{ - if(h && h->table && cb) { - struct uint_hash_entry *he; - size_t i; - DEBUGASSERT(h->init == CURL_UINT32_HASHINIT); - for(i = 0; i < h->slots; ++i) { - for(he = h->table[i]; he; he = he->next) { - if(!cb(he->id, he->value, user_data)) - return; - } - } - } -} diff --git a/lib/uint-hash.h b/lib/uint-hash.h deleted file mode 100644 index 3cbcf8f8f11f..000000000000 --- a/lib/uint-hash.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef HEADER_CURL_UINT_HASH_H -#define HEADER_CURL_UINT_HASH_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , 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" - -/* A version with uint32_t as key */ -typedef void Curl_uint32_hash_dtor(uint32_t id, void *value); -struct uint_hash_entry; - -/* Hash for `uint32_t` as key */ -struct uint_hash { - struct uint_hash_entry **table; - Curl_uint32_hash_dtor *dtor; - uint32_t slots; - uint32_t size; -#ifdef DEBUGBUILD - int init; -#endif -}; - -void Curl_uint32_hash_init(struct uint_hash *h, - uint32_t slots, - Curl_uint32_hash_dtor *dtor); -void Curl_uint32_hash_destroy(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); -uint32_t Curl_uint32_hash_count(struct uint_hash *h); - -typedef bool Curl_uint32_hash_visit_cb(uint32_t id, void *value, - void *user_data); - -void Curl_uint32_hash_visit(struct uint_hash *h, - Curl_uint32_hash_visit_cb *cb, - void *user_data); - -#endif /* HEADER_CURL_UINT_HASH_H */ diff --git a/lib/uint-table.c b/lib/uint-table.c index addd2c9cb2e1..71b52999830c 100644 --- a/lib/uint-table.c +++ b/lib/uint-table.c @@ -29,11 +29,9 @@ #define CURL_UINT32_TBL_MAGIC 0x62757473 #endif -void Curl_uint32_tbl_init(struct uint32_tbl *tbl, - Curl_uint32_tbl_entry_dtor *entry_dtor) +void Curl_uint32_tbl_init(struct uint32_tbl *tbl) { memset(tbl, 0, sizeof(*tbl)); - tbl->entry_dtor = entry_dtor; tbl->last_key_added = UINT32_MAX; #ifdef DEBUGBUILD tbl->init = CURL_UINT32_TBL_MAGIC; @@ -49,8 +47,6 @@ static void uint32_tbl_clear_rows(struct uint32_tbl *tbl, end = CURLMIN(upto_excluding, tbl->nrows); for(i = from; i < end; ++i) { if(tbl->rows[i]) { - if(tbl->entry_dtor) - tbl->entry_dtor(i, tbl->rows[i]); tbl->rows[i] = NULL; tbl->nentries--; } @@ -153,7 +149,8 @@ bool Curl_uint32_tbl_add(struct uint32_tbl *tbl, void *entry, uint32_t *pkey) void Curl_uint32_tbl_remove(struct uint32_tbl *tbl, uint32_t key) { - uint32_tbl_clear_rows(tbl, key, key + 1); + if(key < tbl->nrows) + uint32_tbl_clear_rows(tbl, key, key + 1); } bool Curl_uint32_tbl_contains(struct uint32_tbl *tbl, uint32_t key) diff --git a/lib/uint-table.h b/lib/uint-table.h index 398a26a64843..743d13b72ce4 100644 --- a/lib/uint-table.h +++ b/lib/uint-table.h @@ -25,12 +25,8 @@ ***************************************************************************/ #include "curl_setup.h" -/* Destructor for a single table entry */ -typedef void Curl_uint32_tbl_entry_dtor(uint32_t key, void *entry); - struct uint32_tbl { void **rows; /* array of void* holding entries */ - Curl_uint32_tbl_entry_dtor *entry_dtor; uint32_t nrows; /* length of `rows` array */ uint32_t nentries; /* entries in table */ uint32_t last_key_added; /* UINT_MAX or last key added */ @@ -39,17 +35,14 @@ struct uint32_tbl { #endif }; -/* Initialize the table with 0 capacity. - * The optional `entry_dtor` is called when a table entry is removed, - * Passing NULL means no action is taken on removal. */ -void Curl_uint32_tbl_init(struct uint32_tbl *tbl, - Curl_uint32_tbl_entry_dtor *entry_dtor); +/* Initialize the table with 0 capacity. */ +void Curl_uint32_tbl_init(struct uint32_tbl *tbl); /* Resize the table to change capacity `nmax`. When `nmax` is reduced, * all present entries with key equal or larger to `nmax` are removed. */ CURLcode Curl_uint32_tbl_resize(struct uint32_tbl *tbl, uint32_t nrows); -/* Destroy the table, freeing all entries. */ +/* Destroy the table, freeing its storage. */ void Curl_uint32_tbl_destroy(struct uint32_tbl *tbl); /* Get the table capacity. */ diff --git a/lib/url.c b/lib/url.c index 6eeae43d0d42..b189d52841b2 100644 --- a/lib/url.c +++ b/lib/url.c @@ -244,6 +244,9 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_ssl_close_all(data); Curl_peer_unlink(&data->state.origin); Curl_peer_unlink(&data->state.initial_origin); +#ifndef CURL_DISABLE_RTSP + Curl_peer_unlink(&data->state.rtsp_session_origin); +#endif Curl_ssl_free_certinfo(data); Curl_bufref_free(&data->state.referer); @@ -267,6 +270,7 @@ CURLcode Curl_close(struct Curl_easy **datap) curlx_safefree(data->state.most_recent_ftp_entrypath); curlx_safefree(data->info.contenttype); curlx_safefree(data->info.wouldredirect); + curlx_safefree(data->info.effective_url); /* No longer a dirty share, if it exists */ if(Curl_share_easy_unlink(data)) @@ -294,10 +298,6 @@ CURLcode Curl_close(struct Curl_easy **datap) Curl_netrc_cleanup(&data->state.netrc); #ifndef CURL_DISABLE_DIGEST_AUTH curlx_free(data->state.envproxy); -#endif - Curl_ssl_config_cleanup(&data->set.ssl.primary); -#ifndef CURL_DISABLE_PROXY - Curl_ssl_config_cleanup(&data->set.proxy_ssl.primary); #endif curlx_memzero(data, sizeof(*data)); curlx_free(data); @@ -352,13 +352,13 @@ void Curl_init_userdefined(struct Curl_easy *data) set->dns_cache_timeout_ms = 60000; /* Timeout every 60 seconds by default */ /* Timeout every 24 hours by default */ - set->general_ssl.ca_cache_timeout = 24 * 60 * 60; + set->ssl_ca_cache_timeout = 24 * 60 * 60; set->httpauth = CURLAUTH_BASIC; /* defaults to basic */ - Curl_ssl_config_init(&data->set.ssl.primary); + Curl_ssl_config_init(&data->set.ssl); #ifndef CURL_DISABLE_PROXY - Curl_ssl_config_init(&data->set.proxy_ssl.primary); + Curl_ssl_config_init(&data->set.proxy_ssl); set->proxyport = 0; set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */ set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */ @@ -463,15 +463,16 @@ CURLcode Curl_open(struct Curl_easy **curl) data->magic = CURLEASY_MAGIC_NUMBER; /* most recent connection is not yet defined */ - data->state.lastconnect_id = -1; + data->state.last_conn_id = -1; + data->state.last_cpid = UINT32_MAX; /* and not assigned an id yet */ data->id = -1; data->mid = UINT32_MAX; data->master_mid = UINT32_MAX; data->progress.hide = TRUE; - Curl_hash_init(&data->meta_hash, 23, - Curl_hash_str, curlx_str_key_compare, easy_meta_freeentry); + Curl_hash_init(&data->meta_hash, 23, CURL_HASH_TYPE_BYTES, + easy_meta_freeentry); DEBUGASSERT(STRING_LAST <= UINT8_MAX); Curl_u8_strset_init(&data->set.strings); curlx_dyn_init(&data->state.headerb, CURL_MAX_HTTP_HEADER); @@ -512,7 +513,7 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) Curl_creds_unlink(&conn->creds); Curl_peer_unlink(&conn->creds_origin); curlx_safefree(conn->options); - curlx_safefree(conn->localdev); + curlx_safefree(conn->interface_opts); Curl_ssl_conn_config_cleanup(conn); curlx_safefree(conn->destination); @@ -582,6 +583,10 @@ struct url_conn_match { struct Curl_easy *data; struct connectdata *needle; struct curltime now; + struct ssl_filter_config ssl_config; +#ifndef CURL_DISABLE_PROXY + struct ssl_filter_config proxy_ssl_config; +#endif BIT(may_multiplex); BIT(want_ntlm_http); BIT(want_proxy_ntlm_http); @@ -593,7 +598,6 @@ struct url_conn_match { BIT(require_tls); /* Requires TLS use from a clear-text start, can only * reuse connections that have TLS. */ BIT(wait_pipe); - BIT(force_reuse); BIT(seen_pending_conn); BIT(seen_single_use_conn); BIT(seen_multiplex_conn); @@ -611,23 +615,29 @@ static bool url_match_connect_config(struct connectdata *conn, m->data->set.ipver != conn->ip_version) return FALSE; - if(m->needle->localdev || m->needle->localport) { + /* When needle has specific local bind settings, those have to match. + * If it does not, any other connection might do when it comes to ports. */ + if(m->needle->localport && + ((conn->localport != m->needle->localport) || + (conn->localportrange != m->needle->localportrange))) + return FALSE; + /* For interface related bind options, we require a full match + * as connection might go totally different ways (VPNs, for example). */ + if((m->needle->interface_opts != conn->interface_opts) && + (!m->needle->interface_opts || !conn->interface_opts || + strcmp(conn->interface_opts, m->needle->interface_opts))) + return FALSE; + + if((m->needle->interface_opts || m->needle->localport) && /* If we are bound to a specific local end (IP+port), we must not reuse a random other one, although if we did not ask for a particular one we - can reuse one that was bound. - - This comparison is a bit rough and too strict. Since the input - parameters can be specified in numerous ways and still end up the same - it would take a lot of processing to make it really accurate. Instead, - this matching will assume that reuses of bound connections will most - likely also reuse the exact same binding parameters and missing out a - few edge cases should not hurt anyone much. */ - if((conn->localport != m->needle->localport) || - (conn->localportrange != m->needle->localportrange) || - (m->needle->localdev && - (!conn->localdev || strcmp(conn->localdev, m->needle->localdev)))) - return FALSE; - } + can reuse one that was bound. */ + ((conn->localport != m->needle->localport) || + (conn->localportrange != m->needle->localportrange) || + (m->needle->interface_opts && + (!conn->interface_opts || + strcmp(conn->interface_opts, m->needle->interface_opts))))) + return FALSE; if(!m->needle->via_peer != !conn->via_peer) /* do not mix connections that use the "connect to host" feature and @@ -692,14 +702,16 @@ static bool url_match_multiplex_limits(struct connectdata *conn, /* If multiplexed, make sure we do not go over concurrency limit */ if(conn->attached_xfers >= Curl_multi_max_concurrent_streams(m->data->multi)) { - infof(m->data, "client side MAX_CONCURRENT_STREAMS reached" - ", skip (%u)", conn->attached_xfers); + infof(m->data, "connection #%" FMT_OFF_T " reached " + "MAX_CONCURRENT_STREAMS(%u), skip", + conn->connection_id, conn->attached_xfers); return FALSE; } if(conn->attached_xfers >= Curl_conn_get_max_concurrent(m->data, conn, FIRSTSOCKET)) { - infof(m->data, "MAX_CONCURRENT_STREAMS reached, skip (%u)", - conn->attached_xfers); + infof(m->data, "connection #%" FMT_OFF_T "reached " + "MAX_CONCURRENT_STREAMS(%u), skip", + conn->connection_id, conn->attached_xfers); return FALSE; } /* When not multiplexed, we have a match here! */ @@ -724,7 +736,7 @@ static bool url_match_ssl_use(struct connectdata *conn, return FALSE; /* We may reuse this as an auto-TLS upgrade, but only if the SSL * config parameters match. */ - if(!Curl_ssl_conn_config_match(m->data, conn, FALSE)) + if(!Curl_ssl_conn_config_match(m->data, &m->ssl_config, conn, FALSE)) return FALSE; } else if(m->require_tls) @@ -746,10 +758,21 @@ static bool url_match_proxy_use(struct connectdata *conn, if(!proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy)) return FALSE; + if(m->data->set.socks5_auth_only && !conn->bits.socks5_authenticated && + ((conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) || + (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5_HOSTNAME))) { + DEBUGF(infof(m->data, + "Connection #%" FMT_OFF_T + " was not SOCKS5 authenticated, cannot reuse", + conn->connection_id)); + return FALSE; + } + if(CURL_PROXY_IS_HTTPS(m->needle->http_proxy.proxytype)) { /* https proxies come in different types, http/1.1, h2, ... */ /* match SSL config to proxy */ - if(!Curl_ssl_conn_config_match(m->data, conn, TRUE)) { + if(!Curl_ssl_conn_config_match(m->data, &m->proxy_ssl_config, + conn, TRUE)) { DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T " has different SSL proxy parameters, cannot reuse", @@ -767,7 +790,8 @@ static bool url_match_proxy_use(struct connectdata *conn, #ifndef CURL_DISABLE_HTTP static bool url_match_http_multiplex(struct connectdata *conn, - struct url_conn_match *m) + struct url_conn_match *m, + bool *pwait_pipe) { if(m->may_multiplex && (m->data->state.http_neg.allowed & (CURL_HTTP_V2x | CURL_HTTP_V3x)) && @@ -776,7 +800,7 @@ static bool url_match_http_multiplex(struct connectdata *conn, if(m->data->set.pipewait) { infof(m->data, "Server upgrade does not support multiplex yet, wait"); m->found = NULL; - m->wait_pipe = TRUE; + *pwait_pipe = TRUE; return TRUE; /* stop searching, we want to wait */ } infof(m->data, "Server upgrade cannot be used"); @@ -818,8 +842,8 @@ static bool url_match_http_version(struct connectdata *conn, return TRUE; } #else -#define url_match_http_multiplex(c, m) ((void)(c), (void)(m), TRUE) -#define url_match_http_version(c, m) ((void)(c), (void)(m), TRUE) +#define url_match_http_multiplex(c, m, w) ((void)(c), (void)(m), TRUE) +#define url_match_http_version(c, m) ((void)(c), (void)(m), TRUE) #endif static bool url_match_proto_config(struct connectdata *conn, @@ -869,16 +893,15 @@ static bool url_match_destination(struct connectdata *conn, if(!Curl_peer_same_destination(m->needle->via_peer, conn->via_peer)) return FALSE; - if(m->needle->origin->scheme != conn->origin->scheme) { + if(m->needle->origin->scheme != conn->origin->scheme && /* `needle` and `conn` not having the same scheme. * This is allowed for the same family *if* conn is using TLS. * - IMAP+STARTTLS works for IMAPS. * - IMAPS works for IMAP. */ - if(get_protocol_family(conn->origin->scheme) != - m->needle->scheme->protocol) { - return FALSE; - } - } + get_protocol_family(conn->origin->scheme) != + m->needle->scheme->protocol) + return FALSE; + /* Scheme mismatch is acceptable, compare hostname/port */ return Curl_peer_same_destination(m->needle->origin, conn->origin); } @@ -888,7 +911,7 @@ static bool url_match_ssl_config(struct connectdata *conn, { /* If talking/upgrading to TLS, conn needs to use the same SSL options. */ if(((m->needle->scheme->flags & PROTOPT_SSL) || m->may_tls) && - !Curl_ssl_conn_config_match(m->data, conn, FALSE)) { + !Curl_ssl_conn_config_match(m->data, &m->ssl_config, conn, FALSE)) { DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T " has different SSL parameters, cannot reuse", conn->connection_id)); @@ -911,8 +934,9 @@ static bool url_match_auth_ntlm(struct connectdata *conn, } else if(m->want_ntlm_http) { /* Transfer wants NTLM, connection is not using it. - * Do not reuse when connection has credentials and they differ. */ - if(conn->creds && + * Do not reuse when connection credentials state is bound to an origin + * and it or creds differ. */ + if(conn->creds_origin && (!Curl_creds_same(conn->creds, m->data->state.creds) || !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) return FALSE; @@ -931,24 +955,6 @@ static bool url_match_auth_ntlm(struct connectdata *conn, return FALSE; } #endif - if(m->want_ntlm_http || m->want_proxy_ntlm_http) { - /* Credentials are already checked, we may use this connection. - * With NTLM being weird as it is, we MUST use a - * connection where it has already been fully negotiated. - * If it has not, we keep on looking for a better one. */ - m->found = conn; - - if((m->want_ntlm_http && - (conn->http_ntlm_state != NTLMSTATE_NONE)) || - (m->want_proxy_ntlm_http && - (conn->proxy_ntlm_state != NTLMSTATE_NONE))) { - /* We must use this connection, no other */ - m->force_reuse = TRUE; - return TRUE; - } - /* Continue look up for a better connection */ - return FALSE; - } return TRUE; } #else @@ -959,7 +965,7 @@ static bool url_match_auth_ntlm(struct connectdata *conn, static bool url_match_auth_nego(struct connectdata *conn, struct url_conn_match *m) { - if(conn->http_negotiate_state != GSS_AUTHNONE) { + if(conn->http_negotiate_state != CURL_NEGO_AUTH_NONE) { /* Connection is using Negotiate. We cannot reuse if transfer * has different Auth input parameters. */ if(!m->want_nego_http || @@ -969,8 +975,9 @@ static bool url_match_auth_nego(struct connectdata *conn, } else if(m->want_nego_http) { /* Transfer wants Negotiate, connection is not using it. - * Do not reuse when connection has credentials and they differ. */ - if(conn->creds && + * Do not reuse when connection credentials state is bound to an origin + * and it or creds differ. */ + if(conn->creds_origin && (!Curl_creds_same(conn->creds, m->data->state.creds) || !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) return FALSE; @@ -978,7 +985,7 @@ static bool url_match_auth_nego(struct connectdata *conn, #ifndef CURL_DISABLE_PROXY /* Same for Proxy Negotiate authentication */ - if(conn->proxy_negotiate_state != GSS_AUTHNONE) { + if(conn->proxy_negotiate_state != CURL_NEGO_AUTH_NONE) { if(!m->want_proxy_nego_http || !Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds)) return FALSE; @@ -989,86 +996,72 @@ static bool url_match_auth_nego(struct connectdata *conn, return FALSE; } #endif - if(m->want_nego_http || m->want_proxy_nego_http) { - /* Credentials are already checked, we may use this connection. We MUST - * use a connection where it has already been fully negotiated. If it has - * not, we keep on looking for a better one. */ - m->found = conn; - if((m->want_nego_http && - (conn->http_negotiate_state != GSS_AUTHNONE)) || - (m->want_proxy_nego_http && - (conn->proxy_negotiate_state != GSS_AUTHNONE))) { - /* We must use this connection, no other */ - m->force_reuse = TRUE; - return TRUE; - } - return FALSE; /* get another */ - } return TRUE; } #else #define url_match_auth_nego(c, m) ((void)(c), (void)(m), TRUE) #endif -static bool url_match_conn(struct connectdata *conn, void *userdata) +static cpool_match_result url_match_conn(struct connectdata *conn, + void *userdata) { struct url_conn_match *m = userdata; - /* Check if `conn` can be used for transfer `m->data` */ + bool wait_pipe = FALSE; /* general connect config setting match? */ if(!url_match_connect_config(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; /* match for destination and protocol? */ if(!url_match_destination(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_fully_connected(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_multiplex_needs(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_ssl_use(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_proxy_use(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_ssl_config(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; - if(!url_match_http_multiplex(conn, m)) - return FALSE; - else if(m->wait_pipe) - /* wait on multiplexing */ - return TRUE; + if(!url_match_http_multiplex(conn, m, &wait_pipe)) + return CPOOL_MATCH_CONT; if(!url_match_auth(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_proto_config(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; if(!url_match_auth_ntlm(conn, m)) - return FALSE; - else if(m->force_reuse) - return TRUE; + return CPOOL_MATCH_CONT; if(!url_match_auth_nego(conn, m)) - return FALSE; - else if(m->force_reuse) - return TRUE; + return CPOOL_MATCH_CONT; if(!url_match_multiplex_limits(conn, m)) - return FALSE; + return CPOOL_MATCH_CONT; + + /* The connection matches all conditions, but do we want to use it? */ + if(wait_pipe) { + /* The connection fits, but it's multiplex state has not been determined + * yet. Put the transfer into PENDING and wait for conn state change. */ + DEBUGASSERT(!m->found); + m->wait_pipe = TRUE; + return CPOOL_MATCH_FOUND; + } if(m->data->set.conn_max_age_ms > 0) { - timediff_t age_ms = curlx_ptimediff_ms(&m->now, &conn->created); + timediff_t age_ms = Curl_cpool_conn_age_ms(m->data, conn, &m->now); if(age_ms > m->data->set.conn_max_age_ms) { /* Transfer is looking for a younger connection. */ - if(!CONN_INUSE(conn)) - Curl_conn_close(m->data, conn, FALSE); - return FALSE; + return CPOOL_MATCH_TOO_OLD; } } @@ -1078,13 +1071,12 @@ static bool url_match_conn(struct connectdata *conn, void *userdata) !Curl_cpool_conn_seems_healthy(conn, m->data, &m->now)) { infof(m->data, "Connection %" FMT_OFF_T " seems to be dead, terminating", conn->connection_id); - Curl_conn_close(m->data, conn, FALSE); - return FALSE; + return CPOOL_MATCH_CLOSE; } /* conn matches our needs. */ m->found = conn; - return TRUE; + return CPOOL_MATCH_FOUND; } static bool url_match_result(void *userdata) @@ -1107,7 +1099,6 @@ static bool url_match_result(void *userdata) "Found pending candidate for reuse and CURLOPT_PIPEWAIT is set"); match->wait_pipe = TRUE; } - match->force_reuse = FALSE; return FALSE; } @@ -1122,57 +1113,13 @@ static bool url_match_result(void *userdata) */ static bool url_attach_existing(struct Curl_easy *data, struct connectdata *needle, - bool *waitpipe) + struct url_conn_match *m, + const struct curltime *pnow) { - struct cpool *cpool = Curl_cpool_get_instance(data); - struct url_conn_match match; - bool success; - - DEBUGASSERT(!data->conn); - - memset(&match, 0, sizeof(match)); - match.data = data; - match.needle = needle; - match.now = *Curl_pgrs_now(data); - match.may_multiplex = xfer_may_multiplex(data, needle); - - Curl_cpool_prune_dead(cpool, data); - -#ifdef USE_NTLM - match.want_ntlm_http = - (data->state.authhost.want & CURLAUTH_NTLM) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP); -#ifndef CURL_DISABLE_PROXY - match.want_proxy_ntlm_http = - needle->http_proxy.creds && - (data->state.authproxy.want & CURLAUTH_NTLM) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP); -#endif -#endif - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO) - match.want_nego_http = - (data->state.authhost.want & CURLAUTH_NEGOTIATE) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP); -#ifndef CURL_DISABLE_PROXY - match.want_proxy_nego_http = - needle->http_proxy.creds && - (data->state.authproxy.want & CURLAUTH_NEGOTIATE) && - (needle->scheme->protocol & PROTO_FAMILY_HTTP); -#endif -#endif - match.require_tls = data->set.use_ssl >= CURLUSESSL_CONTROL; - match.may_tls = data->set.use_ssl > CURLUSESSL_NONE; - /* Find a connection in the pool that matches what "data + needle" * requires. If a suitable candidate is found, it is attached to "data". */ - 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 success; + return Curl_cpool_find(data, needle->destination, TRUE, pnow, + url_match_conn, url_match_result, m); } /* @@ -1185,16 +1132,15 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) return NULL; /* and we setup a few fields in case we end up actually using this struct */ - conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->recv_idx = 0; /* default for receiving transfer data */ conn->send_idx = 0; /* default for sending transfer data */ conn->connection_id = -1; /* no ID */ + conn->cpid = UINT32_MAX; /* not in connection pool yet */ conn->attached_xfers = 0; - - /* Remember time this connection started */ - conn->lastused = conn->lastupkeep = conn->created = *Curl_pgrs_now(data); + conn->shutdown.start_ms[FIRSTSOCKET] = + conn->shutdown.start_ms[SECONDARYSOCKET] = -1; #ifndef CURL_DISABLE_FTP conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; @@ -1205,9 +1151,10 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->transport_wanted = TRNSPRT_TCP; /* most of them are TCP streams */ /* Store the local bind parameters that will be used for this connection */ - if(CURL_EASY_STR(data, STRING_DEVICE)) { - conn->localdev = curlx_strdup(CURL_EASY_STR(data, STRING_DEVICE)); - if(!conn->localdev) + if(CURL_EASY_STR(data, STRING_INTERFACE_OPT)) { + conn->interface_opts = + curlx_strdup(CURL_EASY_STR(data, STRING_INTERFACE_OPT)); + if(!conn->interface_opts) goto error; } #ifndef CURL_DISABLE_BINDLOCAL @@ -1225,23 +1172,23 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) return conn; error: - curlx_free(conn->localdev); + curlx_free(conn->interface_opts); curlx_free(conn); return NULL; } static CURLcode url_set_conn_scheme(struct Curl_easy *data, - struct connectdata *conn, - const struct Curl_scheme *scheme) + struct connectdata *conn) { /* URL scheme is usable for connection when it is * - allowed * - not from a redirect or an allowed redirect protocol */ + const struct Curl_scheme *scheme = conn->origin->scheme; if(scheme->run && (data->set.allowed_protocols & scheme->protocol) && (!data->state.this_is_a_follow || (data->set.redir_protocols & scheme->protocol))) { - conn->scheme = conn->given = scheme; + conn->scheme = scheme; return CURLE_OK; } if(scheme->flags & PROTOPT_NO_TRANSFER) @@ -1382,15 +1329,14 @@ static CURLcode url_set_data_creds_netrc(struct Curl_easy *data, goto out; } else if(ncreds_out) { - if(!(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL)) { - /* if the protocol cannot handle control codes in credentials, make - sure there are none */ - if(str_has_ctrl(ncreds_out->user) || - str_has_ctrl(ncreds_out->passwd)) { - failf(data, "control code detected in .netrc credentials"); - result = CURLE_READ_ERROR; - goto out; - } + if(!(data->state.origin->scheme->flags & PROTOPT_USERPWDCTRL) && + /* if the protocol cannot handle control codes in credentials, make + sure there are none */ + (str_has_ctrl(ncreds_out->user) || + str_has_ctrl(ncreds_out->passwd))) { + failf(data, "control code detected in .netrc credentials"); + result = CURLE_READ_ERROR; + goto out; } CURL_TRC_M(data, "netrc: using credentials for %s as %s", data->state.origin->hostname, ncreds_out->user); @@ -1519,7 +1465,7 @@ static CURLcode url_set_conn_origin_etc(struct Curl_easy *data, Curl_peer_link(&conn->origin, data->state.origin); /* set the connection scheme */ - result = url_set_conn_scheme(data, conn, conn->origin->scheme); + result = url_set_conn_scheme(data, conn); if(result) goto out; @@ -1587,10 +1533,9 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, struct connectdata *conn) { struct Curl_peer *peer = NULL; - CURLcode result; if(conn->scheme->run->setup_connection) { - result = conn->scheme->run->setup_connection(data, conn); + CURLcode result = conn->scheme->run->setup_connection(data, conn); if(result) return result; } @@ -1621,10 +1566,10 @@ static CURLcode setup_connection_internals(struct Curl_easy *data, if(data->set.scope_id) conn->scope_id = data->set.scope_id; else { - struct Curl_peer *first = Curl_conn_get_first_peer(conn, FIRSTSOCKET); - if(!first) + peer = Curl_conn_get_first_peer(conn, FIRSTSOCKET); + if(!peer) return CURLE_FAILED_INIT; - conn->scope_id = first->scopeid; + conn->scope_id = peer->scopeid; } #endif @@ -2017,8 +1962,8 @@ static CURLcode url_create_needle(struct Curl_easy *data, } /* Do the unfailable inits first, before checks that may early return */ - Curl_hash_init(&needle->meta_hash, 23, - Curl_hash_str, curlx_str_key_compare, conn_meta_freeentry); + Curl_hash_init(&needle->meta_hash, 23, CURL_HASH_TYPE_BYTES, + conn_meta_freeentry); /************************************************************* * Determine `conn->origin` and populate `data->state.up` and @@ -2124,7 +2069,7 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) { CURLcode result = CURLE_OK; CURLU *uh; - CURLUcode uc; + CURLUcode uc = CURLUE_OK; bool use_set_uh = (data->set.uh && !data->state.this_is_a_follow); uint16_t port_override = data->state.allow_port ? data->set.use_port : 0; uint32_t scope_id = 0; @@ -2149,41 +2094,78 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) goto out; } - /* Calculate the *real* URL this transfer uses, applying defaults - * where information is missing. */ - if(CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL) && - !Curl_is_absolute_url(Curl_bufref_ptr(&data->state.url), NULL, 0, TRUE)) { - char *url = curl_maprintf("%s://%s", - CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL), - Curl_bufref_ptr(&data->state.url)); - if(!url) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - Curl_bufref_set(&data->state.url, url, 0, curl_free); - } - if(!use_set_uh) { - char *newurl; - uc = curl_url_set(uh, CURLUPART_URL, Curl_bufref_ptr(&data->state.url), - (unsigned int)(CURLU_GUESS_SCHEME | - CURLU_NON_SUPPORT_SCHEME | - (data->set.disallow_username_in_url ? - CURLU_DISALLOW_USER : 0) | - (data->set.path_as_is ? CURLU_PATH_AS_IS : 0))); - if(uc) { - failf(data, "URL rejected: %s", curl_url_strerror(uc)); - result = Curl_uc_to_curlcode(uc); - goto out; + bool prepend_sheme = FALSE; + /* special-case when 'file' is default as a normal path is not a valid URL + without the 'file' scheme present */ + if(curl_strequal("file", CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL)) && + !Curl_is_absolute_url(Curl_bufref_ptr(&data->state.url), NULL, 0, TRUE)) + prepend_sheme = TRUE; + else { + uc = curl_url_set(uh, CURLUPART_URL, Curl_bufref_ptr(&data->state.url), + (unsigned int) + CURLU_GUESS_SCHEME | + CURLU_NON_SUPPORT_SCHEME | + (data->set.disallow_username_in_url ? + CURLU_DISALLOW_USER : 0) | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + if(uc) { + failf(data, "URL rejected: %s", curl_url_strerror(uc)); + result = Curl_uc_to_curlcode(uc); + goto out; + } + + if(CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL)) { + /* check if we there was a scheme provided */ + char *scheme; + uc = curl_url_get(uh, CURLUPART_SCHEME, &scheme, + CURLU_NO_GUESS_SCHEME); + if(uc == CURLUE_NO_SCHEME) + prepend_sheme = TRUE; + curl_free(scheme); + if(uc && uc != CURLUE_NO_SCHEME) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + } } - /* after it was parsed, get the generated normalized version */ - uc = curl_url_get(uh, CURLUPART_URL, &newurl, CURLU_GET_EMPTY); - if(uc) { - result = Curl_uc_to_curlcode(uc); - goto out; + if(prepend_sheme) { + char *url = curl_maprintf("%s://%s", + CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL), + Curl_bufref_ptr(&data->state.url)); + if(!url) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + uc = curl_url_set(uh, CURLUPART_URL, url, + (unsigned int) + CURLU_GUESS_SCHEME | + CURLU_NON_SUPPORT_SCHEME | + (data->set.disallow_username_in_url ? + CURLU_DISALLOW_USER : 0) | + (data->set.path_as_is ? CURLU_PATH_AS_IS : 0)); + curl_free(url); + if(uc) { + failf(data, "URL rejected: %s", curl_url_strerror(uc)); + result = Curl_uc_to_curlcode(uc); + goto out; + } + } + { + char *newurl; + /* after it was parsed, and possibly given a new scheme, get the + normalized version */ + uc = curl_url_get(uh, CURLUPART_URL, &newurl, CURLU_GET_EMPTY); + if(uc) { + result = Curl_uc_to_curlcode(uc); + goto out; + } + if(prepend_sheme) + infof(data, "Applied default protocol '%s'", + CURL_EASY_STR(data, STRING_DEFAULT_PROTOCOL)); + Curl_bufref_set(&data->state.url, newurl, 0, curl_free); } - Curl_bufref_set(&data->state.url, newurl, 0, curl_free); } #ifdef USE_IPV6 @@ -2232,6 +2214,62 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) return result; } +static CURLcode url_match_init(struct Curl_easy *data, + struct connectdata *needle, + struct url_conn_match *m) +{ + memset(m, 0, sizeof(*m)); + m->data = data; + m->needle = needle; + m->now = *Curl_pgrs_now(data); + m->may_multiplex = xfer_may_multiplex(data, needle); + +#ifdef USE_NTLM + m->want_ntlm_http = + (data->state.authhost.want & CURLAUTH_NTLM) && + (needle->scheme->protocol & PROTO_FAMILY_HTTP) && + Curl_auth_allowed_to_host(data); +#ifndef CURL_DISABLE_PROXY + m->want_proxy_ntlm_http = + needle->http_proxy.creds && + (data->state.authproxy.want & CURLAUTH_NTLM) && + (needle->scheme->protocol & PROTO_FAMILY_HTTP); +#endif +#endif + +#if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO) + m->want_nego_http = + (data->state.authhost.want & CURLAUTH_NEGOTIATE) && + (needle->scheme->protocol & PROTO_FAMILY_HTTP) && + Curl_auth_allowed_to_host(data); +#ifndef CURL_DISABLE_PROXY + m->want_proxy_nego_http = + needle->http_proxy.creds && + (data->state.authproxy.want & CURLAUTH_NEGOTIATE) && + (needle->scheme->protocol & PROTO_FAMILY_HTTP); +#endif +#endif + m->require_tls = data->set.use_ssl >= CURLUSESSL_CONTROL; + m->may_tls = data->set.use_ssl > CURLUSESSL_NONE; + + /* Get a shallow setup of filter configs for connection cache matching */ + return Curl_ssl_filter_config_tmp_init(data, needle->origin, + &m->ssl_config, +#ifndef CURL_DISABLE_PROXY + &m->proxy_ssl_config); +#else + NULL); +#endif +} + +static void url_match_destroy(struct url_conn_match *m) +{ + Curl_ssl_config_cleanup(&m->ssl_config); +#ifndef CURL_DISABLE_PROXY + Curl_ssl_config_cleanup(&m->proxy_ssl_config); +#endif +} + /** * Find an existing connection for the transfer or create a new one. * Returns @@ -2240,10 +2278,12 @@ static CURLcode url_set_data_origin_and_creds(struct Curl_easy *data) * a suitable connection has not determined its multiplex capability. * - a fatal error */ -static CURLcode url_find_or_create_conn(struct Curl_easy *data) +static CURLcode url_find_or_create_conn(struct Curl_easy *data, + const struct curltime *pnow) { struct connectdata *needle = NULL; - bool waitpipe = FALSE; + struct url_conn_match match; + bool match_initialized = FALSE; CURLcode result; /* create the template connection for transfer data. Use this needle to @@ -2254,6 +2294,11 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) goto out; DEBUGASSERT(needle); + result = url_match_init(data, needle, &match); + if(result) + goto out; + match_initialized = TRUE; + /*********************************************************************** * file: is a special case in that it does not need a network connection ***********************************************************************/ @@ -2271,7 +2316,7 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) goto out; /* Setup a "faked" transfer that will do nothing */ - result = Curl_cpool_add(data, needle); + result = Curl_cpool_add(data, needle, 0, 0, pnow); Curl_attach_connection(data, needle, TRUE); needle = NULL; if(!result) { @@ -2292,11 +2337,6 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) } #endif - /* Complete the easy's SSL configuration for connection cache matching */ - result = Curl_ssl_easy_config_complete(data, needle->origin); - if(result) - goto out; - /************************************************************* * Reuse of existing connection is not allowed when * - connect_only is set or @@ -2306,83 +2346,76 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) if((!data->set.reuse_fresh || data->state.followlocation) && !data->set.connect_only) { /* Ok, try to find and attach an existing one */ - url_attach_existing(data, needle, &waitpipe); - } - - if(data->conn) { - /* We attached an existing connection for this transfer. Copy - * over transfer specific properties over from needle. */ - struct connectdata *conn = data->conn; - VERBOSE(bool tls_upgraded = (!(needle->given->flags & PROTOPT_SSL) && - Curl_conn_is_ssl(conn, FIRSTSOCKET))); - - conn->bits.reuse = TRUE; - url_conn_reuse_adjust(data, needle); + if(url_attach_existing(data, needle, &match, pnow)) { + /* We attached an existing connection for this transfer. Copy + * over transfer specific properties over from needle. */ + struct connectdata *conn = data->conn; + VERBOSE(bool tls_upgraded = + (!(needle->origin->scheme->flags & PROTOPT_SSL) && + Curl_conn_is_ssl(conn, FIRSTSOCKET))); + + DEBUGASSERT(conn); + conn->bits.reuse = TRUE; + url_conn_reuse_adjust(data, needle); #ifndef CURL_DISABLE_PROXY - infof(data, "Reusing existing %s: connection%s with %s %s", - conn->given->name, - tls_upgraded ? " (upgraded to SSL)" : "", - (conn->socks_proxy.peer || conn->http_proxy.peer) ? "proxy" : "host", - conn->socks_proxy.peer ? conn->socks_proxy.peer->user_hostname : - conn->http_proxy.peer ? conn->http_proxy.peer->user_hostname : - conn->origin->hostname); + infof(data, "Reusing existing %s: connection%s with %s %s", + conn->origin->scheme->name, + tls_upgraded ? " (upgraded to SSL)" : "", + (conn->socks_proxy.peer || conn->http_proxy.peer) ? + "proxy" : "host", + conn->socks_proxy.peer ? conn->socks_proxy.peer->user_hostname : + conn->http_proxy.peer ? conn->http_proxy.peer->user_hostname : + conn->origin->hostname); #else - infof(data, "Reusing existing %s: connection%s with host %s", - conn->given->name, - tls_upgraded ? " (upgraded to SSL)" : "", - conn->origin->hostname); + infof(data, "Reusing existing %s: connection%s with host %s", + conn->origin->scheme->name, + tls_upgraded ? " (upgraded to SSL)" : "", + conn->origin->hostname); #endif + } } - else { - /* We have decided that we want a new connection. We may not be able to do - that if we have reached the limit of how many connections we are - allowed to open. */ - if(waitpipe) { + if(!data->conn) { + /* We have not attached an existing connection. `needle` may become + * the connection to use, if + * - we are not waiting on an existing connection result + * - the total/host limits on active connections allow it + */ + if(match.wait_pipe) { /* There is a connection that *might* become usable for multiplexing "soon", and we wait for that */ infof(data, "Waiting on connection to negotiate possible multiplexing."); result = CURLE_NO_CONNECTION_AVAILABLE; goto out; } - else { - switch(Curl_cpool_check_limits(data, needle, &needle->created)) { - case CPOOL_LIMIT_DEST: - infof(data, "No more connections allowed to host"); - result = CURLE_NO_CONNECTION_AVAILABLE; - goto out; - case CPOOL_LIMIT_TOTAL: - if(data->master_mid != UINT32_MAX) - CURL_TRC_M(data, "Allowing sub-requests (like DoH) to override " - "max connection limit"); - else { - infof(data, "No connections available, total of %zu reached.", - data->multi->max_total_connections); - result = CURLE_NO_CONNECTION_AVAILABLE; - goto out; - } - break; - default: - break; - } - } - /* Convert needle into a full connection by filling in all the - * remaining parts like the cloned SSL configuration. */ - result = Curl_ssl_conn_config_init(data, needle); - if(result) { - DEBUGF(curl_mfprintf(stderr, "Error: init connection SSL config\n")); + /* Add needle to conn pool, observing the limits. + * Initializes connection_id. + * May fail due to limits with CURLE_NO_CONNECTION_AVAILABLE. */ + result = Curl_cpool_add(data, needle, + data->multi->max_total_connections, + data->multi->max_host_connections, + pnow); + if(result) goto out; - } - /* Add needle to conn pool, which assigns the connection id. - * Attach regardless of result, for correct handling. */ - result = Curl_cpool_add(data, needle); + /* All fine, attach it and forget needle */ Curl_attach_connection(data, needle, TRUE); needle = NULL; - if(result) + + /* Clone the SSL filter configs into the new connection. */ + result = Curl_ssl_conn_config_clone(&match.ssl_config, +#ifndef CURL_DISABLE_PROXY + &match.proxy_ssl_config, +#else + NULL, +#endif + data->conn); + if(result) { + DEBUGF(infof(data, "Error: clone connection SSL config")); goto out; + } #ifdef USE_NTLM /* If NTLM is requested in a part of this connection, make sure we do not @@ -2404,6 +2437,8 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) #endif } + /* COMMON setup code for reused and new connections from here on. */ + /* Setup and init stuff before DO starts, in preparing for the transfer. */ result = Curl_init_transfer(data, data->conn); if(result) @@ -2430,6 +2465,8 @@ static CURLcode url_find_or_create_conn(struct Curl_easy *data) result = Curl_conn_ev_data_setup(data); out: + if(match_initialized) + url_match_destroy(&match); if(needle) Curl_conn_free(data, needle); DEBUGASSERT(result || data->conn); @@ -2440,7 +2477,7 @@ CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) { CURLcode result; struct connectdata *conn = NULL; - + const struct curltime *pnow = NULL; *pconnected = FALSE; /* Set the request to virgin state based on transfer settings */ @@ -2456,7 +2493,9 @@ CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) } /* Get or create a connection for the transfer. */ - result = url_find_or_create_conn(data); + pnow = Curl_pgrs_now(data); + Curl_pgrsTimeWas(data, TIMER_POSTQUEUE, *pnow); + result = url_find_or_create_conn(data, pnow); conn = data->conn; if(result) goto out; @@ -2466,7 +2505,6 @@ CURLcode Curl_connect(struct Curl_easy *data, bool *pconnected) goto out; } - Curl_pgrsTime(data, TIMER_POSTQUEUE); if(conn->bits.reuse) { if(conn->attached_xfers > 1) /* multiplexed */ @@ -2545,9 +2583,9 @@ void Curl_data_priority_clear_state(struct Curl_easy *data) CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key, void *meta_data, Curl_meta_dtor *meta_dtor) { - if(!Curl_hash_add2(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1, + if(!Curl_hash_add2(&conn->meta_hash, key, strlen(key) + 1, meta_data, meta_dtor)) { - meta_dtor(CURL_UNCONST(key), strlen(key) + 1, meta_data); + meta_dtor(key, strlen(key) + 1, meta_data); return CURLE_OUT_OF_MEMORY; } return CURLE_OK; @@ -2555,12 +2593,12 @@ CURLcode Curl_conn_meta_set(struct connectdata *conn, const char *key, void Curl_conn_meta_remove(struct connectdata *conn, const char *key) { - Curl_hash_delete(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1); + Curl_hash_delete(&conn->meta_hash, key, strlen(key) + 1); } void *Curl_conn_meta_get(struct connectdata *conn, const char *key) { - return Curl_hash_pick(&conn->meta_hash, CURL_UNCONST(key), strlen(key) + 1); + return Curl_hash_pick(&conn->meta_hash, key, strlen(key) + 1); } struct Curl_easy *Curl_get_admin(struct Curl_easy *data) diff --git a/lib/url.h b/lib/url.h index 43ee63cfc597..cbb68795c934 100644 --- a/lib/url.h +++ b/lib/url.h @@ -48,7 +48,7 @@ CURLcode Curl_parse_login_details(const char *login, const size_t len, /* Attach/Clear/Get meta data for an easy handle. Needs to provide * a destructor, will be automatically called when the easy handle * is reset or closed. */ -typedef void Curl_meta_dtor(void *key, size_t key_len, void *meta_data); +typedef void Curl_meta_dtor(const void *key, size_t key_len, void *meta_data); /* Set the transfer meta data for the key. Any existing entry for that * key will be destroyed. diff --git a/lib/urlapi.c b/lib/urlapi.c index 734efbc35e69..4f7aacdbf451 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -55,9 +55,6 @@ #define MAX_SCHEME_LEN 40 #define MAX_ZONEID_LEN 16 -/* characters not allowed in hostnames */ -#define HOSTNAME_INVALID_CHARS " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|" - /* * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make * sure we have _some_ value for AF_INET6 without polluting our fake value @@ -148,17 +145,21 @@ UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url, len -= n; } - for(iptr = host_sep; len && !result; iptr++, len--) { + for(iptr = host_sep; len && !result;) { if(*iptr == ' ') { if(query != QUERY_YES) result = curlx_dyn_addn(o, "%20", 3); else result = curlx_dyn_addn(o, "+", 1); + iptr++; + len--; } else if((*iptr < ' ') || (*iptr >= 0x7f)) { unsigned char out[3] = { '%' }; Curl_hexbyte(&out[1], *iptr); result = curlx_dyn_addn(o, out, 3); + iptr++; + len--; } else if(*iptr == '%' && (len >= 3) && ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) && @@ -169,13 +170,30 @@ UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url, unsigned char out[3] = { '%' }; Curl_hexbyte(&out[1], hex); result = curlx_dyn_addn(o, out, 3); - iptr += 2; - len -= 2; + iptr += 3; + len -= 3; } else { - result = curlx_dyn_addn(o, iptr, 1); - if(*iptr == '?' && (query == QUERY_NOT_YET)) - query = QUERY_YES; + const unsigned char *start = iptr; + while(len) { + if(*iptr == ' ' || *iptr < ' ' || *iptr >= 0x7f) + break; + if(*iptr == '%' && (len >= 3) && + ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) && + (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) + break; + if(*iptr == '?') { + if(query == QUERY_NOT_YET) { + iptr++; + len--; + query = QUERY_YES; + break; + } + } + iptr++; + len--; + } + result = curlx_dyn_addn(o, (const char *)start, (size_t)(iptr - start)); } } @@ -204,18 +222,20 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, if(guess_scheme && STARTS_WITH_DRIVE_PREFIX(url)) return 0; #endif - if(ISALPHA(url[0])) + if(ISALPHA(url[0])) { + if(buf) + buf[0] = Curl_raw_tolower(url[0]); for(i = 1; i < MAX_SCHEME_LEN; ++i) { char s = url[i]; if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) { - /* RFC 3986 3.1 explains: - scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - */ + if(buf) + buf[i] = Curl_raw_tolower(s); } else { break; } } + } if(i && (url[i] == ':') && ((url[i + 1] == '/') || !guess_scheme)) { /* If this does not guess scheme, the scheme always ends with the colon so that this also detects data: URLs etc. In guessing mode, data: could @@ -223,32 +243,26 @@ size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen, /* the length of the scheme is the name part only */ size_t len = i; - if(buf) { - Curl_strntolower(buf, url, i); + if(buf) buf[i] = 0; - } return len; } + if(buf) + buf[0] = 0; return 0; } -/* scan for byte values <= 31, 127 and sometimes space */ -CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace) +/* scan for byte values <= 31, 127 and maybe space */ +static bool badoctets(const char *input, size_t n, int flags) { - size_t n = strlen(url); - size_t i; - unsigned char control; - const unsigned char *p = (const unsigned char *)url; - if(n > CURL_MAX_INPUT_LENGTH) - return CURLUE_MALFORMED_INPUT; - - control = allowspace ? 0x1f : 0x20; - for(i = 0; i < n; i++) { - if(p[i] <= control || p[i] == 127) - return CURLUE_MALFORMED_INPUT; + const uint8_t *p = (const unsigned char *)input; + const uint8_t control = flags & CURLU_ALLOW_SPACE ? 0x1f : 0x20; + while(n--) { + if(*p <= control || *p == 127) + return TRUE; + p++; } - *urllen = n; - return CURLUE_OK; + return FALSE; } /* @@ -336,9 +350,20 @@ UNITTEST CURLUcode parse_hostname_login(struct Curl_URL *u, u->options = optionsp; } - /* the hostname starts at this offset */ - *hostname_offset = ptr - login; - return CURLUE_OK; + if(userp && badoctets(userp, strlen(userp), flags)) + ures = CURLUE_BAD_USER; + else if(passwdp && badoctets(passwdp, strlen(passwdp), flags)) + ures = CURLUE_BAD_PASSWORD; + else if(optionsp && badoctets(optionsp, strlen(optionsp), flags)) + ures = CURLUE_MALFORMED_INPUT; + + userp = passwdp = optionsp = NULL; + + if(!ures) { + /* the hostname starts at this offset */ + *hostname_offset = ptr - login; + return CURLUE_OK; + } out: @@ -368,7 +393,7 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, u->portnum = 0; u->port_present = FALSE; if(hostname[0] == '[') { - portptr = strchr(hostname, ']'); + portptr = memchr(hostname + 1, ']', curlx_dyn_len(host) - 1); if(!portptr) return CURLUE_BAD_IPV6; portptr++; @@ -381,7 +406,7 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, portptr = NULL; } else - portptr = strchr(hostname, ':'); + portptr = memchr(hostname, ':', curlx_dyn_len(host)); if(portptr) { curl_off_t port; @@ -415,6 +440,20 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host, return CURLUE_OK; } +/* characters not allowed in hostnames: + " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|" */ + +static const bool invalid_host_char[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00-0x0F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10-0x1F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, /* 0x20-0x2F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 0x30-0x3F */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40-0x4F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* 0x50-0x5F */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60-0x6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1 /* 0x70-0x7F */ +}; + /* This function assumes 'hostname' now starts with [. It trims 'hostname' in * place and it sets u->zoneid if present. * @@ -445,7 +484,8 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, /* pass '25' if present and is a URL encoded percent sign */ if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']')) h += 2; - while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1))) + while(!invalid_host_char[(unsigned char)*h] && (*h != ']') && + (i < (MAX_ZONEID_LEN - 1))) zoneid[i++] = *h++; if(!i || (']' != *h)) return CURLUE_BAD_IPV6; @@ -476,31 +516,36 @@ UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname, return CURLUE_OK; } -static CURLUcode hostname_check(struct Curl_URL *u, char *hostname, +/* the input is a confirmed hostname, never an IPv6 address */ +static CURLUcode hostname_check(char *hostname, size_t hlen) +{ + size_t i; + for(i = 0; i < hlen; i++) { + if(invalid_host_char[(unsigned char)hostname[i]]) + return CURLUE_BAD_HOSTNAME; + } + if((hlen >= 2) && + (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.')) + /* more than one trailing dot is not allowed */ + return CURLUE_BAD_HOSTNAME; + else if((hlen == 1) && (hostname[0] == '.')) + /* a single dot alone is not allowed */ + return CURLUE_BAD_HOSTNAME; + return CURLUE_OK; +} + +/* the input is a hostname or perhaps an IPv6 address */ +static CURLUcode hostname_check6(struct Curl_URL *u, char *hostname, size_t hlen) /* length of hostname */ { - size_t len; DEBUGASSERT(hostname); if(!hlen) return CURLUE_NO_HOST; else if(hostname[0] == '[') return ipv6_parse(u, hostname, hlen); - else { - /* letters from the second string are not ok */ - len = strcspn(hostname, HOSTNAME_INVALID_CHARS); - if(hlen != len) - /* hostname with bad content */ - return CURLUE_BAD_HOSTNAME; - else if((hlen >= 2) && - (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.')) - /* more than one trailing dot is not allowed */ - return CURLUE_BAD_HOSTNAME; - else if((hlen == 1) && (hostname[0] == '.')) - /* a single dot alone is not allowed */ - return CURLUE_BAD_HOSTNAME; - } - return CURLUE_OK; + + return hostname_check(hostname, hlen); } /* @@ -529,14 +574,14 @@ UNITTEST int ipv4_normalize(struct dynbuf *host) unsigned int parts[4] = { 0, 0, 0, 0 }; CURLcode result = CURLE_OK; - if(*c == '[') - return HOST_IPV6; + if(!ISDIGIT(*c)) + return HOST_NAME; while(!done) { int rc; curl_off_t l; if(*c == '0') { - if(Curl_raw_tolower(c[1]) == 'x') { + if((c[1] | 0x20) == 'x') { c += 2; /* skip the prefix */ rc = curlx_str_hex(&c, &l, UINT_MAX); if(rc) @@ -631,7 +676,7 @@ static CURLUcode urldecode_host(struct dynbuf *host) { const char *per; const char *hostname = curlx_dyn_ptr(host); - per = strchr(hostname, '%'); + per = memchr(hostname, '%', curlx_dyn_len(host)); if(!per) /* nothing to decode */ return CURLUE_OK; @@ -676,6 +721,7 @@ static CURLUcode parse_authority(struct Curl_URL *u, return uc; } + /* parse_port() also sets the hostname length correctly */ uc = parse_port(u, host, has_scheme); if(!curlx_dyn_len(host)) @@ -684,23 +730,17 @@ static CURLUcode parse_authority(struct Curl_URL *u, if(!uc) uc = urldecode_host(host); if(uc) - return uc; - - switch(ipv4_normalize(host)) { - case HOST_IPV4: - break; - case HOST_IPV6: + ; + else if(auth[offset] == '[') uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); - break; - case HOST_NAME: - uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); - break; - case HOST_ERROR: - uc = CURLUE_OUT_OF_MEMORY; - break; - default: - uc = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ - break; + else { + /* ipv4_normalize() returns *NAME, *IPV4 or *ERROR */ + int type = ipv4_normalize(host); + + if(type == HOST_NAME) + uc = hostname_check(curlx_dyn_ptr(host), curlx_dyn_len(host)); + else if(type == HOST_ERROR) + uc = CURLUE_OUT_OF_MEMORY; } return uc; @@ -756,6 +796,8 @@ static bool needs_dedotdot(const char *p, size_t pn) /* a single byte path cannot be cleaned up */ if(pn < 2) return FALSE; + if(!memchr(p, '.', pn) && !memchr(p, '%', pn)) + return FALSE; while(pn) { if(is_dot(&p, &pn)) { /* "./" or dot before end of string */ @@ -999,7 +1041,8 @@ static CURLUcode parse_scheme(const char *url, CURLU *u, char *schemebuf, if(schemelen) { int num_slashes = 0; const char *p = &url[schemelen + 1]; - if(!Curl_get_scheme(schemebuf) && !(flags & CURLU_NON_SUPPORT_SCHEME)) + if(!Curl_getn_scheme(schemebuf, schemelen) && + !(flags & CURLU_NON_SUPPORT_SCHEME)) return CURLUE_UNSUPPORTED_SCHEME; if(!ISSLASH(*p)) @@ -1090,6 +1133,8 @@ static CURLUcode handle_fragment(CURLU *u, const char *fragment, u->fragment = curlx_dyn_ptr(&enc); } else { + if(badoctets(fragment, fraglen, flags)) + return CURLUE_BAD_FRAGMENT; u->fragment = curlx_memdup0(fragment + 1, fraglen - 1); if(!u->fragment) return CURLUE_OUT_OF_MEMORY; @@ -1114,6 +1159,9 @@ static CURLUcode handle_query(CURLU *u, const char *query, u->query = curlx_dyn_ptr(&enc); } else { + if(badoctets(query, qlen, flags)) + return CURLUE_BAD_QUERY; + u->query = curlx_memdup0(query + 1, qlen - 1); if(!u->query) return CURLUE_OUT_OF_MEMORY; @@ -1144,6 +1192,9 @@ static CURLUcode handle_path(CURLU *u, const char *path, } if(pathlen >= (size_t)(1 + !is_file)) { + if(badoctets(path, pathlen, flags)) + return CURLUE_BAD_PATH; + /* paths for file:// scheme can be one byte, others need to be two */ if(!u->path) { u->path = curlx_memdup0(path, pathlen); @@ -1183,31 +1234,35 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) DEBUGASSERT(url); - curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH); + urllen = strlen(url); + if(urllen > CURL_MAX_INPUT_LENGTH) + return CURLUE_MALFORMED_INPUT; - ures = Curl_junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE)); - if(ures) - goto fail; + curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH); schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf), flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)); /* handle the file: scheme */ - if(schemelen && !strcmp(schemebuf, "file")) { + if(schemelen == 4 && !memcmp(schemebuf, "file", 4)) { is_file = TRUE; ures = parse_file(url, urllen, u, &path, &pathlen); } else { const char *hostp = NULL; + const char *p; size_t hostlen; ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp); if(ures) goto fail; /* find the end of the hostname + port number */ - hostlen = strcspn(hostp, "/?#"); - path = &hostp[hostlen]; + p = hostp; + while(*p && *p != '/' && *p != '?' && *p != '#') + p++; + hostlen = p - hostp; + path = p; /* this pathlen also contains the query and the fragment */ pathlen = urllen - (path - url); @@ -1227,7 +1282,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags) if(!ures) { /* The path might at this point contain a fragment and/or a query to handle */ - const char *fragment = strchr(path, '#'); + const char *fragment = memchr(path, '#', pathlen); if(fragment) { size_t fraglen = pathlen - (fragment - path); ures = handle_fragment(u, fragment, fraglen, flags); @@ -1378,7 +1433,12 @@ void curl_url_cleanup(CURLU *u) CURLU *curl_url_dup(const CURLU *in) { - struct Curl_URL *u = curlx_calloc(1, sizeof(struct Curl_URL)); + struct Curl_URL *u; + + if(!in) + return NULL; + + u = curlx_calloc(1, sizeof(struct Curl_URL)); if(u) { DUP(u, in, scheme); DUP(u, in, user); @@ -1393,6 +1453,7 @@ CURLU *curl_url_dup(const CURLU *in) u->port_present = in->port_present; u->fragment_present = in->fragment_present; u->query_present = in->query_present; + u->guessed_scheme = in->guessed_scheme; } return u; fail: @@ -1479,15 +1540,13 @@ static CURLUcode urlget_format(const CURLU *u, CURLUPart what, part = punyversion; } } - else if(depunyfy) { - if(Curl_is_ASCII_name(u->host)) { - char *unpunified = NULL; - uc = host_encode(part, &unpunified); - curlx_free(part); - if(uc) - return uc; - part = unpunified; - } + else if(depunyfy && Curl_is_ASCII_name(u->host)) { + char *unpunified = NULL; + uc = host_encode(part, &unpunified); + curlx_free(part); + if(uc) + return uc; + part = unpunified; } *partp = part; return CURLUE_OK; @@ -2001,11 +2060,25 @@ static CURLUcode url_sethost(CURLU *u, struct dynbuf *encp, size_t dlen; char *decoded = NULL; CURLcode result = Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL); - if(result || hostname_check(u, decoded, dlen)) + if(result || hostname_check6(u, decoded, dlen)) bad = TRUE; + else { + struct dynbuf dbuf; + curlx_dyn_init(&dbuf, CURL_MAX_INPUT_LENGTH); + if(curlx_dyn_addn(&dbuf, decoded, dlen)) + bad = TRUE; + else if(ipv4_normalize(&dbuf) == HOST_IPV4) { + curlx_dyn_reset(encp); + if(curlx_dyn_addn(encp, curlx_dyn_ptr(&dbuf), curlx_dyn_len(&dbuf))) + bad = TRUE; + } + curlx_dyn_free(&dbuf); + } curlx_free(decoded); } - else if(hostname_check(u, newp, n)) + else if(hostname_check6(u, newp, n)) + bad = TRUE; + else if(ipv4_normalize(encp) == HOST_ERROR) bad = TRUE; if(bad) { curlx_dyn_free(encp); @@ -2090,7 +2163,9 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, const char *newp = NULL; struct dynbuf enc; CURLUcode status; - curlx_dyn_init(&enc, (nalloc * 3) + 1 + leadingslash); + curlx_dyn_init(&enc, (what == CURLUPART_HOST) ? + CURLMAX((nalloc * 3) + 1 + leadingslash, 16) : + (nalloc * 3) + 1 + leadingslash); if(leadingslash && (part[0] != '/')) { CURLcode result = curlx_dyn_addn(&enc, "/", 1); diff --git a/lib/urldata.h b/lib/urldata.h index 1c0101847b94..249331d30bbc 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -69,7 +69,7 @@ #include "request.h" #include "ratelimit.h" #include "netrc.h" -#include "uint-hashset.h" +#include "u8_strset.h" #include "vdns/asyn.h" #include "vdns/hostip.h" #include "vtls/vtls_config.h" @@ -93,7 +93,6 @@ typedef CURLcode (Curl_recv)(struct Curl_easy *data, /* transfer */ #include "protocol.h" #include "ftp.h" #include "http.h" -#include "smb.h" #include "mqtt.h" #include "ftplistparser.h" #include "multihandle.h" @@ -170,13 +169,11 @@ typedef enum { NTLMSTATE_LAST } curlntlm; -typedef enum { - GSS_AUTHNONE, - GSS_AUTHRECV, - GSS_AUTHSENT, - GSS_AUTHDONE, - GSS_AUTHSUCC -} curlnegotiate; +#define CURL_NEGO_AUTH_NONE 0U +#define CURL_NEGO_AUTH_RECV 1U +#define CURL_NEGO_AUTH_SENT 2U +#define CURL_NEGO_AUTH_DONE 3U +#define CURL_NEGO_AUTH_SUCC 4U /* * Boolean values that concerns this connection. @@ -185,6 +182,7 @@ struct ConnectBits { BIT(connect_only); #ifndef CURL_DISABLE_PROXY BIT(origin_is_proxy); /* if set, the connection's origin is a proxy */ + BIT(socks5_authenticated); /* SOCKS5 authenticated on FIRSTSOCKET */ #endif /* always modify bits.close with the connclose() and connkeep() macros! */ BIT(close); /* if set, we close the connection after this request */ @@ -220,7 +218,6 @@ struct ConnectBits { BIT(no_reuse); /* connection should not be reused */ 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 */ }; @@ -279,7 +276,6 @@ struct connectdata { struct Curl_creds *creds; /* When connection itself is tied to credentials */ struct Curl_peer *creds_origin; /* origin tied credentials are for */ const struct Curl_scheme *scheme; /* Connection's real protocol handler */ - const struct Curl_scheme *given; /* The protocol first given */ /* `meta_hash` is a general key-value store for implementations * with the lifetime of the connection. @@ -287,40 +283,39 @@ struct connectdata { * the connection is cleaned up (see Curl_hash_add2()).*/ struct Curl_hash meta_hash; - struct Curl_llist_node cpool_node; /* conncache lists */ - struct Curl_llist_node cshutdn_node; /* cshutdn list */ char *destination; /* hostname+port, used in conncache */ struct curltime created; /* creation time */ - struct curltime lastused; /* when returned to the connection pool as idle */ - struct curltime lastchecked; /* when last checked alive status */ - struct curltime lastupkeep; /* when last done conn_upkeep */ + /* timediff_t are deltas from `created`*/ + timediff_t lastused_ms; /* when returned to the connection pool as idle */ + timediff_t lastchecked_ms; /* when last checked alive status */ + timediff_t lastupkeep_ms; /* when last done conn_upkeep */ #ifndef CURL_DISABLE_PROXY struct proxy_info socks_proxy; struct proxy_info http_proxy; #endif + /* A connection can have one or two sockets and connection filters. + * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ struct Curl_cfilter *cfilter[2]; /* connection filters */ Curl_recv *recv[2]; Curl_send *send[2]; - /* A connection can have one or two sockets and connection filters. - * The protocol using the 2nd one is FTP for CONTROL+DATA sockets */ curl_socket_t sock[2]; #define CONN_SOCK_IDX_VALID(i) (((i) >= 0) && ((i) < 2)) struct { - struct curltime start[2]; /* when filter shutdown started */ + timediff_t start_ms[2]; /* when filter shutdown started */ timediff_t timeout_ms; /* 0 means no timeout */ } shutdown; curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ void *closesocket_client; - struct ssl_primary_config ssl_config; + struct ssl_filter_config ssl_config; #ifndef CURL_DISABLE_PROXY - struct ssl_primary_config proxy_ssl_config; + struct ssl_filter_config proxy_ssl_config; #endif char *options; /* options string, allocated */ @@ -334,17 +329,12 @@ struct connectdata { curlntlm proxy_ntlm_state; #endif -#ifdef USE_SPNEGO - curlnegotiate http_negotiate_state; - curlnegotiate proxy_negotiate_state; -#endif - /* When this connection is created, store the conditions for the local end bind. This is stored before the actual bind and before any connection is made and will serve the purpose of being used for comparison reasons so that subsequent bound-requested connections are not accidentally reusing wrong connections. */ - char *localdev; + char *interface_opts; struct ConnectBits bits; /* various state-flags for this connection */ #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) int socks5_gssapi_enctype; @@ -357,6 +347,8 @@ struct connectdata { #define CONN_INUSE(c) (!!(c)->attached_xfers) uint32_t attached_xfers; /* # of attached easy handles */ + uint32_t cpid; /* connection pool id */ + #ifdef USE_IPV6 uint32_t scope_id; /* Scope id for IPv6 */ #endif @@ -373,7 +365,10 @@ struct connectdata { * 0 at start, then one of 09, 10, 11, etc. */ uint8_t httpversion_seen; uint8_t gssapi_delegation; /* inherited from set.gssapi_delegation */ - +#ifdef USE_SPNEGO + uint8_t http_negotiate_state; + uint8_t proxy_negotiate_state; +#endif }; #ifndef CURL_DISABLE_PROXY @@ -409,6 +404,7 @@ struct PureInfo { curl_off_t numconnects; /* how many new connections libcurl created */ char *contenttype; /* the content type of the object */ char *wouldredirect; /* URL this would have been redirected to if asked to */ + char *effective_url; /* when needed */ curl_off_t retry_after; /* info from Retry-After: header */ const char *conn_scheme; int httpcode; /* Recent HTTP, FTP, RTSP or SMTP response code */ @@ -507,6 +503,7 @@ typedef enum { EXPIRE_FTP_ACCEPT, EXPIRE_ALPN_EYEBALLS, EXPIRE_SHUTDOWN, + EXPIRE_SERVER_RESPONSE, EXPIRE_LAST /* not an actual timer, used as a marker only */ } expire_id; @@ -526,7 +523,6 @@ struct urlpieces { }; struct UrlState { - curl_off_t lastconnect_id; /* The last assigned connection or -1 */ /* Origin of the initial (e.g. not followed) request of a transfer. Credentials from CURLOPT_* are only valid for this origin. Always set once a transfer starts searching for connections. */ @@ -594,6 +590,10 @@ struct UrlState { #endif struct Curl_llist httphdrs; /* received headers */ struct curl_header headerout[2]; /* for external purposes */ + /* curl_easy_nextheader() cache parameters */ + unsigned int nh_origin; + int nh_request; + size_t nh_count; /* httphdrs count when the cache was filled */ #endif #ifndef CURL_DISABLE_COOKIES struct curl_slist *cookielist; /* list of cookie files set by @@ -617,12 +617,18 @@ struct UrlState { #endif #ifndef CURL_DISABLE_RTSP /* This RTSP state information survives requests and connections */ + struct Curl_peer *rtsp_session_origin; /* origin that issued the session id, + NULL when set by the application */ uint8_t rtp_channel_mask[32]; /* for the correctness checking of the interleaved data */ uint32_t rtsp_next_client_CSeq; /* the session's next client CSeq */ uint32_t rtsp_next_server_CSeq; /* the session's next server CSeq */ uint32_t rtsp_CSeq_recv; /* most recent CSeq received */ #endif + + curl_off_t last_conn_id; /* The last assigned connection or -1 */ + uint32_t last_cpid; /* last connection pool id used */ + #if defined(USE_HTTP2) || defined(USE_HTTP3) int weight; /* shallow copy of data->set */ #endif @@ -721,9 +727,7 @@ enum dupstring { #endif STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */ STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL does not specify */ - STRING_DEVICE, /* local network interface/address to use */ - STRING_INTERFACE, /* local network interface to use */ - STRING_BINDHOST, /* local address to use */ + STRING_INTERFACE_OPT, /* CURLOPT_INTERFACE config */ STRING_ENCODING, /* Accept-Encoding string */ #ifndef CURL_DISABLE_FTP STRING_FTP_ACCOUNT, /* ftp account data */ @@ -836,7 +840,7 @@ struct UserDefined { curl_seek_callback seek_func; /* function that seeks the input */ curl_off_t postfieldsize; /* if POST, this might have a size to use instead of strlen(), and then the data *may* be binary - (contain zero bytes) */ + (contain NUL bytes) */ curl_write_callback fwrite_func; /* function that stores the output */ curl_write_callback fwrite_header; /* function that stores headers */ curl_write_callback fwrite_rtp; /* function that stores interleaved RTP */ @@ -889,9 +893,11 @@ struct UserDefined { struct curl_slist *connect_to; /* list of host:port mappings to override the hostname and port to connect to */ time_t timevalue; /* what time to compare with */ - struct ssl_config_data ssl; /* user defined SSL stuff */ + struct ssl_easy_config ssl; /* user defined SSL stuff */ + curl_ssl_ctx_callback ssl_fsslctx; /* function to initialize SSL ctx */ + void *ssl_fsslctxp; /* parameter for callback */ #ifndef CURL_DISABLE_PROXY - struct ssl_config_data proxy_ssl; /* user defined SSL stuff for proxy */ + struct ssl_easy_config proxy_ssl; /* user defined SSL stuff for proxy */ struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */ uint16_t proxyport; /* If non-zero, use this port number by default. If the proxy string features a @@ -899,7 +905,6 @@ struct UserDefined { uint8_t proxytype; /* what kind of proxy */ uint8_t socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */ #endif - struct ssl_general_config general_ssl; /* general user defined SSL stuff */ timediff_t dns_cache_timeout_ms; /* DNS cache timeout (milliseconds) */ uint32_t buffer_size; /* size of receive buffer to use */ uint32_t upload_buffer_size; /* size of upload buffer to use, keep it >= @@ -967,6 +972,8 @@ struct UserDefined { #ifndef CURL_DISABLE_SMTP struct curl_slist *mail_rcpt; /* linked list of mail recipients */ #endif + int ssl_ca_cache_timeout; /* Certificate store cache timeout (seconds) */ + int tcp_keepidle; /* seconds in idle before sending keepalive probe */ int tcp_keepintvl; /* seconds between TCP keepalive probes */ int tcp_keepcnt; /* maximum number of keepalive probes */ @@ -994,9 +1001,6 @@ struct UserDefined { #ifndef CURL_DISABLE_RTSP uint8_t rtspreq; /* RTSP request type */ #endif -#ifdef USE_ECH - uint8_t tls_ech; /* TLS ECH configuration */ -#endif #ifndef CURL_DISABLE_NETRC uint8_t use_netrc; /* enum CURL_NETRC_OPTION values */ #endif @@ -1099,6 +1103,7 @@ struct UserDefined { #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) BIT(socks5_gssapi_nec); /* Flag to support NEC SOCKS5 server */ #endif + BIT(socks5_auth_only); /* SOCKS5 must authenticate */ BIT(sasl_ir); /* Enable/disable SASL initial response */ BIT(tcp_keepalive); /* use TCP keepalives */ BIT(tcp_fastopen); /* use TCP Fast Open */ @@ -1178,7 +1183,7 @@ struct Curl_easy { struct Curl_multi *multi_easy; /* if non-NULL, points to the multi handle struct to which this "belongs" when used by the easy interface */ - struct Curl_message msg; /* A single posted message. */ + struct CURLMsg msg; /* A single posted message. */ /* once an easy handle is tied to a connection pool a non-negative number to distinguish this transfer from other using the same pool. For easier @@ -1227,9 +1232,9 @@ struct Curl_easy { #define CURL_EASY_STR(d, id) \ Curl_u8_strset_get(&(d)->set.strings, (uint8_t)(id)) #define CURL_EASY_STR_SET(d, id, s, slen) \ - Curl_u8_strset_setx(&(d)->set.strings, (uint8_t)(id), (s), (slen)) + Curl_u8_strset_setx(&(d)->set.strings, (uint8_t)(id), s, slen) #define CURL_EASY_STR_SETN(d, id, s) \ - Curl_u8_strset_setn(&(d)->set.strings, (uint8_t)(id), (s)) + Curl_u8_strset_setn(&(d)->set.strings, (uint8_t)(id), s) #define CURL_EASY_STR_CLEAR(d, id) \ Curl_u8_strset_unset(&(d)->set.strings, (uint8_t)(id)) #define CURL_EASY_STR_CLEAR0(d, id) \ diff --git a/lib/vauth/digest.c b/lib/vauth/digest.c index 748c0d834726..3642dc714eb5 100644 --- a/lib/vauth/digest.c +++ b/lib/vauth/digest.c @@ -57,18 +57,16 @@ #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf" #endif -bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, - const char **endptr) +bool Curl_auth_digest_get_pair(const char *str, struct Curl_str *value, + char *content, const char **endptr) { int c; + size_t content_length = 0; bool starts_with_quote = FALSE; bool escape = FALSE; - for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);) - *value++ = *str++; - *value = 0; - - if('=' != *str++) + if(curlx_str_until(&str, value, DIGEST_MAX_VALUE_LENGTH, '=') || + curlx_str_single(&str, '=')) /* eek, no match */ return FALSE; @@ -78,7 +76,7 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, starts_with_quote = TRUE; } - for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) { + for(c = DIGEST_MAX_CONTENT_LENGTH; *str && c--; str++) { if(!escape) { switch(*str) { case '\\': @@ -117,9 +115,16 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, } } + if(content_length >= DIGEST_MAX_CONTENT_LENGTH - 1) { + /* no room for this byte plus the terminator */ + return FALSE; + } + escape = FALSE; *content++ = *str; + content_length++; } + if(escape) return FALSE; /* No character after backslash */ @@ -366,6 +371,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, char *qrealm; char *qnonce; char *quserp; + char *qspn; /* Decode the challenge message */ CURLcode result = auth_decode_digest_md5_message(chlg, @@ -479,23 +485,25 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, 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 username, realm and nonce as - necessary */ + /* escape double quotes and backslashes in the username, realm, nonce and + spn as necessary */ qrealm = auth_digest_string_quoted(realm); qnonce = auth_digest_string_quoted(nonce); quserp = auth_digest_string_quoted(userp); - if(qrealm && qnonce && quserp) + qspn = auth_digest_string_quoted(spn); + if(qrealm && qnonce && quserp && qspn) /* Generate the response */ response = curl_maprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\"," "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\"," "response=%s,qop=%s", quserp, qrealm, qnonce, - cnonce, nonceCount, spn, resp_hash_hex, qop); + cnonce, nonceCount, qspn, resp_hash_hex, qop); curlx_free(qrealm); curlx_free(qnonce); curlx_free(quserp); curlx_free(spn); + curlx_free(qspn); if(!response) return CURLE_OUT_OF_MEMORY; @@ -530,47 +538,46 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, Curl_auth_digest_cleanup(digest); for(;;) { - char value[DIGEST_MAX_VALUE_LENGTH]; + struct Curl_str value; char content[DIGEST_MAX_CONTENT_LENGTH]; /* Pass all additional spaces here */ - while(*chlg && ISBLANK(*chlg)) - chlg++; + curlx_str_passblanks(&chlg); /* Extract a value=content pair */ - if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { - if(curl_strequal(value, "nonce")) { + if(Curl_auth_digest_get_pair(chlg, &value, content, &chlg)) { + if(curlx_str_casecompare(&value, "nonce")) { curlx_free(digest->nonce); digest->nonce = curlx_strdup(content); if(!digest->nonce) return CURLE_OUT_OF_MEMORY; } - else if(curl_strequal(value, "stale")) { + else if(curlx_str_casecompare(&value, "stale")) { if(curl_strequal(content, "true")) { digest->stale = TRUE; digest->nc = 1; /* we make a new nonce now */ } } - else if(curl_strequal(value, "realm")) { + else if(curlx_str_casecompare(&value, "realm")) { curlx_free(digest->realm); digest->realm = curlx_strdup(content); if(!digest->realm) return CURLE_OUT_OF_MEMORY; } - else if(curl_strequal(value, "opaque")) { + else if(curlx_str_casecompare(&value, "opaque")) { curlx_free(digest->opaque); digest->opaque = curlx_strdup(content); if(!digest->opaque) return CURLE_OUT_OF_MEMORY; } - else if(curl_strequal(value, "qop")) { + else if(curlx_str_casecompare(&value, "qop")) { const char *token = content; struct Curl_str out; bool foundAuth = FALSE; bool foundAuthInt = FALSE; /* Pass leading spaces */ - while(*token && ISBLANK(*token)) - token++; + curlx_str_passblanks(&token); + while(!curlx_str_until(&token, &out, 32, ',')) { if(curlx_str_casecompare(&out, DIGEST_QOP_VALUE_STRING_AUTH)) foundAuth = TRUE; @@ -579,8 +586,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, foundAuthInt = TRUE; if(curlx_str_single(&token, ',')) break; - while(*token && ISBLANK(*token)) - token++; + curlx_str_passblanks(&token); } /* Select only auth or auth-int. Otherwise, ignore */ @@ -597,7 +603,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, return CURLE_OUT_OF_MEMORY; } } - else if(curl_strequal(value, "algorithm")) { + else if(curlx_str_casecompare(&value, "algorithm")) { curlx_free(digest->algorithm); digest->algorithm = curlx_strdup(content); if(!digest->algorithm) @@ -628,7 +634,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, else return CURLE_BAD_CONTENT_ENCODING; } - else if(curl_strequal(value, "userhash")) { + else if(curlx_str_casecompare(&value, "userhash")) { if(curl_strequal(content, "true")) { digest->userhash = TRUE; } @@ -641,12 +647,10 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, break; /* We are done here */ /* Pass all additional spaces here */ - while(*chlg && ISBLANK(*chlg)) - chlg++; + curlx_str_passblanks(&chlg); /* Allow the list to be comma-separated */ - if(',' == *chlg) - chlg++; + (void)curlx_str_single(&chlg, ','); } /* We had a nonce since before, and we got another one now without diff --git a/lib/vauth/digest.h b/lib/vauth/digest.h index 5b8f3ae7675f..63d8a6122c25 100644 --- a/lib/vauth/digest.h +++ b/lib/vauth/digest.h @@ -30,9 +30,11 @@ #define DIGEST_MAX_VALUE_LENGTH 256 #define DIGEST_MAX_CONTENT_LENGTH 1024 +struct Curl_str; + /* This is used to extract the realm from a challenge message */ -bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, - const char **endptr); +bool Curl_auth_digest_get_pair(const char *str, struct Curl_str *value, + char *content, const char **endptr); #endif diff --git a/lib/vauth/digest_sspi.c b/lib/vauth/digest_sspi.c index e3648e37b857..1a8dc91c627b 100644 --- a/lib/vauth/digest_sspi.c +++ b/lib/vauth/digest_sspi.c @@ -34,6 +34,7 @@ #include "curlx/multibyte.h" #include "curl_trc.h" #include "curlx/strdup.h" +#include "curlx/strparse.h" #include "strcase.h" #include "strerror.h" @@ -250,16 +251,15 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, /* If domain is blank or unset, check challenge message for realm */ if(!identity->Domain || !identity->DomainLength) { for(;;) { - char value[DIGEST_MAX_VALUE_LENGTH]; + struct Curl_str value; char content[DIGEST_MAX_CONTENT_LENGTH]; /* Pass all additional spaces here */ - while(*chlg && ISBLANK(*chlg)) - chlg++; + curlx_str_passblanks(&chlg); /* Extract a value=content pair */ - if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { - if(curl_strequal(value, "realm")) { + if(Curl_auth_digest_get_pair(chlg, &value, content, &chlg)) { + if(curlx_str_casecompare(&value, "realm")) { /* Setup identity's domain and length */ domain.tchar_ptr = curlx_convert_UTF8_to_tchar(content); @@ -287,12 +287,10 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, break; /* We are done here */ /* Pass all additional spaces here */ - while(*chlg && ISBLANK(*chlg)) - chlg++; + curlx_str_passblanks(&chlg); /* Allow the list to be comma-separated */ - if(',' == *chlg) - chlg++; + (void)curlx_str_single(&chlg, ','); } } @@ -325,16 +323,16 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, /* Check for the 'stale' directive */ for(;;) { - char value[DIGEST_MAX_VALUE_LENGTH]; + struct Curl_str value; char content[DIGEST_MAX_CONTENT_LENGTH]; while(*p && ISBLANK(*p)) p++; - if(!Curl_auth_digest_get_pair(p, value, content, &p)) + if(!Curl_auth_digest_get_pair(p, &value, content, &p)) break; - if(curl_strequal(value, "stale") && + if(curlx_str_casecompare(&value, "stale") && curl_strequal(content, "true")) { stale = TRUE; break; diff --git a/lib/vauth/ntlm.c b/lib/vauth/ntlm.c index 3f1ffb50ce7a..929531ca1740 100644 --- a/lib/vauth/ntlm.c +++ b/lib/vauth/ntlm.c @@ -474,7 +474,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(struct Curl_easy *data, "%c%c" /* 2 zeroes */ "%s" /* hostname */ "%s", /* domain string */ - 0, /* trailing zero */ + 0, /* null-terminator */ 0, 0, 0, /* part of type-1 long */ LONGQUARTET(NTLMFLAG_NEGOTIATE_OEM | diff --git a/lib/vauth/vauth.c b/lib/vauth/vauth.c index 3259556e5f78..d3849eb46c77 100644 --- a/lib/vauth/vauth.c +++ b/lib/vauth/vauth.c @@ -150,7 +150,7 @@ bool Curl_auth_allowed_to_origin(struct Curl_easy *data, } #ifdef USE_NTLM -static void ntlm_conn_dtor(void *key, size_t klen, void *entry) +static void ntlm_conn_dtor(const void *key, size_t klen, void *entry) { struct ntlmdata *ntlm = entry; (void)key; @@ -180,7 +180,7 @@ void Curl_auth_ntlm_remove(struct connectdata *conn, bool proxy) #endif /* USE_NTLM */ #ifdef USE_KERBEROS5 -static void krb5_conn_dtor(void *key, size_t klen, void *entry) +static void krb5_conn_dtor(const void *key, size_t klen, void *entry) { struct kerberos5data *krb5 = entry; (void)key; @@ -204,7 +204,7 @@ struct kerberos5data *Curl_auth_krb5_get(struct connectdata *conn) #endif /* USE_KERBEROS5 */ #ifdef USE_GSASL -static void gsasl_conn_dtor(void *key, size_t klen, void *entry) +static void gsasl_conn_dtor(const void *key, size_t klen, void *entry) { struct gsasldata *gsasl = entry; (void)key; @@ -228,7 +228,7 @@ struct gsasldata *Curl_auth_gsasl_get(struct connectdata *conn) #endif /* USE_GSASL */ #ifdef USE_SPNEGO -static void nego_conn_dtor(void *key, size_t klen, void *entry) +static void nego_conn_dtor(const void *key, size_t klen, void *entry) { struct negotiatedata *nego = entry; (void)key; diff --git a/lib/vdns/asyn-ares.c b/lib/vdns/asyn-ares.c index dfb087599527..e74e3523c7fb 100644 --- a/lib/vdns/asyn-ares.c +++ b/lib/vdns/asyn-ares.c @@ -401,6 +401,22 @@ CURLcode Curl_async_await(struct Curl_easy *data, uint32_t resolv_id, return result; } +#define CARES_MAX_ADDRS 256 + +static bool async_ares_addr_seen(const struct Curl_addrinfo *cafirst, + int family, size_t ss_size, + const void *addr) +{ + const struct Curl_addrinfo *ca; + for(ca = cafirst; ca; ca = ca->ai_next) { + if((ca->ai_family == family) && + ((size_t)ca->ai_addrlen == ss_size) && + !memcmp(ca->ai_addr, addr, ss_size)) + return TRUE; + } + return FALSE; +} + /* * async_ares_node2addr() converts an address list provided by c-ares * to an internal libcurl compatible list. @@ -413,8 +429,9 @@ static struct Curl_addrinfo *async_ares_node2addr( struct Curl_addrinfo *cafirst = NULL; struct Curl_addrinfo *calast = NULL; int error = 0; + unsigned int naddrs = 0; - for(ai = node; ai; ai = ai->ai_next) { + for(ai = node; ai && (naddrs < CARES_MAX_ADDRS); ai = ai->ai_next) { size_t ss_size; struct Curl_addrinfo *ca; /* ignore elements with unsupported address family, @@ -436,6 +453,10 @@ static struct Curl_addrinfo *async_ares_node2addr( if((size_t)ai->ai_addrlen < ss_size) continue; + /* ignore duplicate addresses already collected */ + if(async_ares_addr_seen(cafirst, ai->ai_family, ss_size, ai->ai_addr)) + continue; + ca = curlx_malloc(sizeof(struct Curl_addrinfo) + ss_size); if(!ca) { error = EAI_MEMORY; @@ -465,6 +486,7 @@ static struct Curl_addrinfo *async_ares_node2addr( if(calast) calast->ai_next = ca; calast = ca; + ++naddrs; } /* if we failed, destroy the Curl_addrinfo list */ diff --git a/lib/vdns/asyn-thrdd.c b/lib/vdns/asyn-thrdd.c index 1ca217a79e60..e5086c4ebb24 100644 --- a/lib/vdns/asyn-thrdd.c +++ b/lib/vdns/asyn-thrdd.c @@ -150,7 +150,7 @@ static struct async_thrdd_item *async_thrdd_item_create( if(!item) return NULL; - if(hostlen) /* NUL byte of name already in struct size */ + if(hostlen) /* null-terminator of name already in struct size */ memcpy(item->hostname, hostname, hostlen); item->mid = data->mid; item->resolv_id = resolv_id; @@ -212,39 +212,48 @@ static void async_thrdd_rr_done(void *user_data, ares_status_t status, async->result = Curl_httpsrr_from_ares(dnsrec, &async->httpsrr); } -static CURLcode async_rr_start(struct Curl_easy *data, - struct Curl_resolv_async *async) +/* Initialize the c-ares channel used for the HTTPS RR side-query, without + * submitting any query on it yet. */ +static CURLcode async_rr_init(struct Curl_easy *data, + struct Curl_resolv_async *async) { struct async_thrdd_ctx *thrdd = &async->thrdd; - char *https_name = NULL; int status; - CURLcode result = CURLE_OK; + (void)data; DEBUGASSERT(!thrdd->rr.channel); - if(async->peer->port != 443) { - https_name = curl_maprintf("_%u._https.%s", - async->peer->port, async->peer->hostname); - if(!https_name) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } status = ares_init_options(&thrdd->rr.channel, NULL, 0); if(status != ARES_SUCCESS) { thrdd->rr.channel = NULL; - result = CURLE_FAILED_INIT; - goto out; + return CURLE_FAILED_INIT; } #ifdef DEBUGBUILD if(getenv("CURL_DNS_SERVER")) { const char *servers = getenv("CURL_DNS_SERVER"); status = ares_set_servers_ports_csv(thrdd->rr.channel, servers); - if(status) { - result = CURLE_FAILED_INIT; + if(status) + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + +/* Submit the HTTPS RR query on the already initialized channel. */ +static CURLcode async_rr_start(struct Curl_easy *data, + struct Curl_resolv_async *async) +{ + struct async_thrdd_ctx *thrdd = &async->thrdd; + char *https_name = NULL; + CURLcode result = CURLE_OK; + + if(async->peer->port != 443) { + https_name = curl_maprintf("_%u._https.%s", + async->peer->port, async->peer->hostname); + if(!https_name) { + result = CURLE_OUT_OF_MEMORY; goto out; } } -#endif async->queries_ongoing++; ares_query_dnsrec(thrdd->rr.channel, @@ -504,7 +513,7 @@ 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); + CURL_THRDQ_TRACE(multi->resolv_thrdq, multi->admin); #endif Curl_thrdq_destroy(multi->resolv_thrdq, join); multi->resolv_thrdq = NULL; @@ -587,7 +596,7 @@ void Curl_async_thrdd_multi_process(struct Curl_multi *multi) } async_thrdd_item_free(item); } - VERBOSE(Curl_thrdq_trace(multi->resolv_thrdq, multi->admin)); + CURL_THRDQ_TRACE(multi->resolv_thrdq, multi->admin); } CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, @@ -639,7 +648,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, #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); + result = async_rr_init(data, async); if(result) goto out; resolver = async->thrdd.rr.channel; @@ -650,6 +659,14 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, if(result) return result; +#ifdef USE_HTTPSRR_ARES + if(resolver) { + result = async_rr_start(data, async); + if(result) + goto out; + } +#endif + #ifdef CURLRES_IPV6 /* Do not start an AAAA query for an IPv4 address when * we will start an A query for it. */ @@ -666,9 +683,7 @@ CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, goto out; } -#ifdef CURLVERBOSE - Curl_thrdq_trace(data->multi->resolv_thrdq, data); -#endif + CURL_THRDQ_TRACE(data->multi->resolv_thrdq, data); out: if(!async->queries_ongoing) @@ -911,9 +926,7 @@ CURLcode Curl_async_take_result(struct Curl_easy *data, *pdns = dns; dns = NULL; } -#ifdef CURLVERBOSE - Curl_thrdq_trace(data->multi->resolv_thrdq, data); -#endif + CURL_THRDQ_TRACE(data->multi->resolv_thrdq, data); out: Curl_dns_entry_unlink(data, &dns); diff --git a/lib/vdns/dnscache.c b/lib/vdns/dnscache.c index bfd935ed472e..303e11b18c05 100644 --- a/lib/vdns/dnscache.c +++ b/lib/vdns/dnscache.c @@ -56,18 +56,13 @@ #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 -struct dnsc_id { - struct Curl_str name; - uint16_t port; - char type; -}; - -static void dnsc_peer2id(struct dnsc_id *pid, char type, - struct Curl_peer *peer) +/* @unittest 3217 */ +UNITTEST void dnsc_peer2id(struct dnsc_id *pid, char type, + struct Curl_peer *peer); +UNITTEST void dnsc_peer2id(struct dnsc_id *pid, char type, + struct Curl_peer *peer) { curlx_str_assign(&pid->name, peer->hostname, strlen(peer->hostname)); pid->port = peer->port; @@ -82,26 +77,41 @@ static void dnsc_str2id(struct dnsc_id *pid, char type, pid->type = type; } -struct dnsc_key { - char data[MAX_HOSTCACHE_LEN]; - size_t len; -}; - /* * 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. + * + * @unittest 3217 */ -static void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) +UNITTEST void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id); +UNITTEST void dnsc_id2key(struct dnsc_key *key, struct dnsc_id *id) { size_t namelen = curlx_strlen(&id->name); - if(namelen > (sizeof(key->data) - 8)) - namelen = sizeof(key->data) - 8; + if(namelen > (MAX_HOSTCACHE_LEN - 3)) + namelen = MAX_HOSTCACHE_LEN - 3; /* store and lower case the name */ key->data[0] = id->type; - Curl_strntolower(key->data + 1, curlx_str(&id->name), namelen); - /* include the terminating 0 in key length */ - key->len = namelen + 2 + - curl_msnprintf(&key->data[namelen + 1], 7, ":%u", id->port); + key->data[1] = (uint8_t)((id->port >> 8) & 0xff); + key->data[2] = (uint8_t)(id->port & 0xff); + Curl_strntolower((char *)key->data + 3, curlx_str(&id->name), namelen); + key->len = namelen + 3; +} + +/* @unittest 3271 */ +UNITTEST bool dnsc_id_matches_entry(struct dnsc_id *id, + struct Curl_dns_entry *dns); +UNITTEST bool dnsc_id_matches_entry(struct dnsc_id *id, + struct Curl_dns_entry *dns) +{ + return dns && + /* Neither the requested nor the cached key was truncated. */ + (((dns->hostlen <= (MAX_HOSTCACHE_LEN - 3)) && + (id->name.len <= (MAX_HOSTCACHE_LEN - 3))) || + /* port and host string are the same, case insensitive */ + ((id->port == dns->port) && (id->name.len == dns->hostlen) && + /* for most lookups, case is the same, try that first */ + (!strcmp(id->name.str, dns->hostname) || + curl_strequal(id->name.str, dns->hostname)))); } static void dnscache_entry_free(struct Curl_dns_entry *dns) @@ -114,7 +124,7 @@ static void dnscache_entry_free(struct Curl_dns_entry *dns) } struct dnscache_prune_data { - struct curltime now; + const struct curltime *pnow; timediff_t oldest_ms; /* oldest time in cache not pruned. */ timediff_t max_age_ms; }; @@ -131,15 +141,27 @@ 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) { + if(!dns->permanent) { /* 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) + timediff_t age_ms = curlx_ptimediff_ms(prune->pnow, &dns->added); + switch(dns->type) { + case CURL_DNST_ADDR: + if(!dns->addr) + age_ms *= 2; /* negative entries age twice as fast */ + break; +#ifdef USE_HTTPSRR + case CURL_DNST_HTTPS: + if(!dns->hinfo) + age_ms *= 2; /* negative entries age twice as fast */ + break; +#endif + default: + break; + } + if(age_ms >= prune->max_age_ms) return TRUE; - if(age > prune->oldest_ms) - prune->oldest_ms = age; + if(age_ms > prune->oldest_ms) + prune->oldest_ms = age_ms; } return FALSE; } @@ -150,12 +172,12 @@ static int dnscache_entry_is_stale(void *datap, void *hc) */ static timediff_t dnscache_prune(struct Curl_hash *hostcache, timediff_t cache_timeout_ms, - struct curltime now) + const struct curltime *pnow) { struct dnscache_prune_data user; user.max_age_ms = cache_timeout_ms; - user.now = now; + user.pnow = pnow; user.oldest_ms = 0; Curl_hash_clean_with_criterium(hostcache, @@ -192,7 +214,7 @@ static void dnscache_unlock(struct Curl_easy *data, * Library-wide function for pruning the DNS cache. This function takes and * returns the appropriate locks. */ -void Curl_dnscache_prune(struct Curl_easy *data) +void Curl_dnscache_prune(struct Curl_easy *data, const struct curltime *pnow) { struct Curl_dnscache *dnscache = dnscache_get(data); /* the timeout may be set -1 (forever) */ @@ -207,7 +229,7 @@ void Curl_dnscache_prune(struct Curl_easy *data) do { /* Remove outdated and unused entries from the hostcache */ timediff_t oldest_ms = - dnscache_prune(&dnscache->entries, timeout_ms, *Curl_pgrs_now(data)); + dnscache_prune(&dnscache->entries, timeout_ms, pnow); if(Curl_hash_count(&dnscache->entries) > MAX_DNS_CACHE_SIZE) /* prune the ones over half this age */ @@ -233,11 +255,11 @@ void Curl_dnscache_clear(struct Curl_easy *data) } /* 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, - struct Curl_peer *peer, - struct Curl_dns_entry **pdns) +static CURLcode fetch_entry(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + uint8_t dns_queries, + struct Curl_peer *peer, + struct Curl_dns_entry **pdns) { struct Curl_dns_entry *dns = NULL; struct dnsc_id id; @@ -256,6 +278,11 @@ static CURLcode fetch_addr(struct Curl_easy *data, /* See if it is already in our dns cache */ dns = Curl_hash_pick(&dnscache->entries, key.data, key.len); + if(dns && !dnsc_id_matches_entry(&id, dns)) { + /* May happen on hostnames longer than MAX_HOSTCACHE_LEN */ + dns = NULL; + } + /* No entry found in cache, check if we might have a wildcard entry */ if(!dns && (type == CURL_DNST_ADDR) && data->state.wildcard_resolve) { struct Curl_str wildname; @@ -272,7 +299,7 @@ static CURLcode fetch_addr(struct Curl_easy *data, /* See whether the returned entry is stale. Done before we release lock */ struct dnscache_prune_data user; - user.now = *Curl_pgrs_now(data); + user.pnow = Curl_pgrs_now(data); user.max_age_ms = data->set.dns_cache_timeout_ms; user.oldest_ms = 0; @@ -331,7 +358,7 @@ CURLcode Curl_dnscache_get(struct Curl_easy *data, CURLcode result = CURLE_OK; dnscache_lock(data, dnscache); - result = fetch_addr(data, dnscache, dns_queries, peer, &dns); + result = fetch_entry(data, dnscache, dns_queries, peer, &dns); if(!result && dns) dns->refcount++; /* we pass out a reference */ else if(result) { @@ -438,31 +465,27 @@ static bool dnscache_ai_has_family(struct Curl_addrinfo *ai, int ai_family) return FALSE; } -static struct Curl_dns_entry *dnsc_entry_create(struct Curl_easy *data, - struct dnsc_id *pid, +static struct Curl_dns_entry *dnsc_entry_create(struct dnsc_id *pid, bool permanent) { struct Curl_dns_entry *dns = NULL; - /* Create a new cache entry, struct already has the hostname NUL */ + if(curlx_strlen(&pid->name) > UINT16_MAX) + goto out; + + /* Create a new cache entry, + struct already has the hostname null-terminator */ dns = curlx_calloc(1, sizeof(struct Curl_dns_entry) + curlx_strlen(&pid->name)); if(!dns) goto out; dns->refcount = 1; /* the cache has the first reference */ - dns->hostlen = curlx_strlen(&pid->name); dns->port = pid->port; + dns->hostlen = (uint16_t)curlx_strlen(&pid->name); if(dns->hostlen) memcpy(dns->hostname, curlx_str(&pid->name), dns->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); - } + dns->permanent = permanent; out: return dns; @@ -545,7 +568,7 @@ struct Curl_dns_entry *Curl_dnsc_mk_addr(struct Curl_easy *data, struct Curl_dns_entry *dns; dnsc_peer2id(&id, CURL_DNST_ADDR, peer); - dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_create(&id, FALSE); dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); return dns; } @@ -560,11 +583,20 @@ struct Curl_dns_entry *Curl_dnsc_mk_addr2(struct Curl_easy *data, struct Curl_dns_entry *dns; dnsc_peer2id(&id, CURL_DNST_ADDR, peer); - dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_create(&id, FALSE); dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr1, paddr2); return dns; } +static struct Curl_dns_entry *dnsc_add_entry(struct Curl_easy *data, + struct Curl_dnscache *dnscache, + struct dnsc_key *key, + struct Curl_dns_entry *entry) +{ + entry->added = *Curl_pgrs_now(data); + return Curl_hash_add(&dnscache->entries, key->data, key->len, entry); +} + #ifdef USE_HTTPSRR static struct Curl_dns_entry *dnsc_entry_assign_https( struct Curl_dns_entry *dns, @@ -601,8 +633,9 @@ struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, struct dnsc_id id; struct Curl_dns_entry *dns; + (void)data; dnsc_peer2id(&id, CURL_DNST_HTTPS, peer); - dns = dnsc_entry_create(data, &id, FALSE); + dns = dnsc_entry_create(&id, FALSE); dns = dnsc_entry_assign_https(dns, phinfo); return dns; } @@ -610,20 +643,19 @@ struct Curl_dns_entry *Curl_dnsc_mk_https(struct Curl_easy *data, static struct Curl_dns_entry *dnsc_add_https(struct Curl_easy *data, struct Curl_dnscache *dnscache, struct Curl_https_rrinfo **phinfo, - struct dnsc_id *id, - bool permanent) + struct dnsc_id *id) { struct Curl_dns_entry *dns, *dns2; struct dnsc_key key; - dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_create(id, FALSE); dns = dnsc_entry_assign_https(dns, phinfo); if(!dns) return NULL; /* Store the resolved data in our DNS cache. */ dnsc_id2key(&key, id); - dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns); + dns2 = dnsc_add_entry(data, dnscache, &key, dns); if(!dns2) { dnscache_entry_free(dns); return NULL; @@ -646,13 +678,13 @@ static struct Curl_dns_entry *dnsc_add_addr(struct Curl_easy *data, struct Curl_dns_entry *dns; struct Curl_dns_entry *dns2; - dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_create(id, permanent); dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); if(!dns) return NULL; /* Store the resolved data in our DNS cache. */ - dns2 = Curl_hash_add(&dnscache->entries, key->data, key->len, (void *)dns); + dns2 = dnsc_add_entry(data, dnscache, key, dns); if(!dns2) { dnscache_entry_free(dns); return NULL; @@ -675,14 +707,14 @@ static struct Curl_dns_entry *dnsc_add_peer_addr( struct Curl_dns_entry *dns2; struct dnsc_key key; - dns = dnsc_entry_create(data, id, permanent); + dns = dnsc_entry_create(id, permanent); dns = dnsc_entry_assign_addr(data, dns, dns_queries, paddr, NULL); if(!dns) return NULL; /* Store the resolved data in our DNS cache. */ dnsc_id2key(&key, id); - dns2 = Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)dns); + dns2 = dnsc_add_entry(data, dnscache, &key, dns); if(!dns2) { dnscache_entry_free(dns); return NULL; @@ -712,7 +744,7 @@ CURLcode Curl_dnscache_add(struct Curl_easy *data, /* Store the resolved data in our DNS cache and up ref count */ dnscache_lock(data, dnscache); - if(!Curl_hash_add(&dnscache->entries, key.data, key.len, (void *)entry)) { + if(!dnsc_add_entry(data, dnscache, &key, entry)) { dnscache_unlock(data, dnscache); return CURLE_OUT_OF_MEMORY; } @@ -749,7 +781,7 @@ CURLcode Curl_dnscache_add_negative(struct Curl_easy *data, #ifdef USE_HTTPSRR else if(dns_queries == CURL_DNSQ_HTTPS) { dnsc_peer2id(&id, CURL_DNST_HTTPS, peer); - dns = dnsc_add_https(data, dnscache, NULL, &id, FALSE); + dns = dnsc_add_https(data, dnscache, NULL, &id); if(!dns) result = CURLE_OUT_OF_MEMORY; } @@ -807,7 +839,7 @@ static void dnscache_entry_dtor(void *entry) */ void Curl_dnscache_init(struct Curl_dnscache *dns, size_t size) { - Curl_hash_init(&dns->entries, size, Curl_hash_str, curlx_str_key_compare, + Curl_hash_init(&dns->entries, size, CURL_HASH_TYPE_BYTES, dnscache_entry_dtor); } diff --git a/lib/vdns/dnscache.h b/lib/vdns/dnscache.h index 73dcff60d70f..0b47710b7f1d 100644 --- a/lib/vdns/dnscache.h +++ b/lib/vdns/dnscache.h @@ -27,6 +27,7 @@ #include "hash.h" #include "curlx/timeval.h" +#include "curlx/strparse.h" struct addrinfo; struct hostent; @@ -46,20 +47,31 @@ struct Curl_dns_entry { #ifdef USE_HTTPSRR struct Curl_https_rrinfo *hinfo; #endif - /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */ - struct curltime timestamp; - size_t hostlen; - /* reference counter, entry is freed on reaching 0 */ - uint32_t refcount; - /* hostname port number that resolved to addr. */ - uint16_t port; + struct curltime added; /* time this entry was added to the cache */ + uint32_t refcount; /* reference counter, entry is freed on reaching 0 */ + uint16_t hostlen; /* length of hostname */ + uint16_t port; /* host port */ char type; /* CURL_DNST_ADDR or CURL_DNST_HTTPS */ uint8_t dns_queries; /* CURL_DNSQ_* type of queries performed for this */ uint8_t dns_responses; /* CURL_DNSQ_* type this entry has responses for */ + BIT(permanent); /* entry is permanent, e.g. does not time out */ /* hostname that resolved to addr. may be NULL (Unix domain sockets). */ char hostname[1]; }; +struct dnsc_id { + struct Curl_str name; + uint16_t port; + char type; +}; + +#define MAX_HOSTCACHE_LEN (255 + 3) /* max FQDN + type + port */ + +struct dnsc_key { + uint8_t data[MAX_HOSTCACHE_LEN]; + size_t len; +}; + /* * Create a `Curl_dns_entry` with a reference count of 1. * Use `Curl_dns_entry_unlink()` to release your hold on it. @@ -101,7 +113,7 @@ 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); +void Curl_dnscache_prune(struct Curl_easy *data, const struct curltime *pnow); /* clear the DNS cache */ void Curl_dnscache_clear(struct Curl_easy *data); diff --git a/lib/vdns/doh.c b/lib/vdns/doh.c index 3b52ed92f362..f479f0d2beb3 100644 --- a/lib/vdns/doh.c +++ b/lib/vdns/doh.c @@ -123,7 +123,7 @@ UNITTEST DOHcode doh_req_encode(const char *host, * Each [ label, dot ] pair is encoded as [ length, label ], * preserving overall length. A final [ label ] without a dot is * also encoded as [ length, label ], increasing overall length - * by one. The encoding is completed by appending a zero byte, + * by one. The encoding is completed by appending a NUL byte, * representing the zero-length root label, again increasing * the overall length by one. */ @@ -210,7 +210,7 @@ static size_t doh_probe_write_cb(char *contents, size_t size, size_t nmemb, static void doh_probe_done(struct Curl_easy *doh, struct Curl_easy *master, CURLcode result); -static void doh_probe_dtor(void *key, size_t klen, void *e) +static void doh_probe_dtor(const void *key, size_t klen, void *e) { (void)key; (void)klen; @@ -353,17 +353,17 @@ static CURLcode doh_probe_run(struct Curl_easy *data, } if(data->set.ssl.certinfo) ERROR_CHECK_SETOPT(CURLOPT_CERTINFO, 1L); - if(data->set.ssl.fsslctx) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl.fsslctx); - if(data->set.ssl.fsslctxp) - ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl.fsslctxp); + if(data->set.ssl_fsslctx) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_FUNCTION, data->set.ssl_fsslctx); + if(data->set.ssl_fsslctxp) + ERROR_CHECK_SETOPT(CURLOPT_SSL_CTX_DATA, data->set.ssl_fsslctxp); if(CURL_EASY_STR(data, STRING_SSL_EC_CURVES)) { ERROR_CHECK_SETOPT(CURLOPT_SSL_EC_CURVES, CURL_EASY_STR(data, STRING_SSL_EC_CURVES)); } (void)curl_easy_setopt(doh, CURLOPT_SSL_OPTIONS, - ((long)data->set.ssl.primary.ssl_options & + ((long)data->set.ssl.ssl_options & ~CURLSSLOPT_AUTO_CLIENT_CERT)); } @@ -600,12 +600,10 @@ static DOHcode doh_rdata(const unsigned char *doh, doh_store_aaaa(doh, index, d); break; #ifdef USE_HTTPSRR - case CURL_DNS_TYPE_HTTPS: { - DOHcode rc = doh_store_https(doh, index, d, rdlength); - if(rc) - return rc; - break; - } + case CURL_DNS_TYPE_HTTPS: + if(rdlength < 3) + return DOH_DNS_RDATA_LEN; + return doh_store_https(doh, index, d, rdlength); #endif default: /* unsupported type, or type we do not store, skip it */ @@ -948,6 +946,18 @@ static CURLcode doh_decode_rdata_name(const unsigned char **buf, return CURLE_OK; } +/* scan for byte values <= 32 or 127 */ +static CURLcode junkscan(const char *url) +{ + const unsigned char *p = (const unsigned char *)url; + while(*p) { + if(*p <= 0x20 || *p == 127) + return CURLE_WEIRD_SERVER_REPLY; + p++; + } + return CURLE_OK; +} + /* @unittest 1658 */ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, const unsigned char *cp, size_t len, @@ -961,7 +971,6 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, struct Curl_https_rrinfo *lhrr = NULL; char *dnsname = NULL; CURLcode result = CURLE_OUT_OF_MEMORY; - size_t olen; (void)data; *hrr = NULL; @@ -976,9 +985,9 @@ UNITTEST CURLcode doh_resp_decode_httpsrr(struct Curl_easy *data, if(doh_decode_rdata_name(&cp, &len, &dnsname) != CURLE_OK) goto err; lhrr->target = dnsname; - if(Curl_junkscan(dnsname, &olen, FALSE)) { + result = junkscan(dnsname); + if(result) { /* unacceptable hostname content */ - result = CURLE_WEIRD_SERVER_REPLY; goto err; } while(len >= 4) { diff --git a/lib/vdns/hostip.c b/lib/vdns/hostip.c index 8464bd291773..222e86b58cb6 100644 --- a/lib/vdns/hostip.c +++ b/lib/vdns/hostip.c @@ -395,7 +395,7 @@ static struct Curl_resolv_async *hostip_async_new(struct Curl_easy *data, return NULL; } - /* struct size already includes the NUL for hostname */ + /* struct size already includes the null-terminator for hostname */ async = curlx_calloc(1, sizeof(*async)); if(!async) return NULL; @@ -950,8 +950,8 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, the time we spent until now! */ if(prev_alarm) { /* there was an alarm() set before us, now put it back */ - timediff_t elapsed_secs = curlx_ptimediff_ms(Curl_pgrs_now(data), - &data->conn->created) / 1000; + timediff_t elapsed_secs = + Curl_cpool_conn_age_ms(data, data->conn, Curl_pgrs_now(data)) / 1000; /* the alarm period is counted in even number of seconds */ unsigned long alarm_set = (unsigned long)(prev_alarm - elapsed_secs); diff --git a/lib/vdns/httpsrr.c b/lib/vdns/httpsrr.c index 98a3c894964c..12a829978bfe 100644 --- a/lib/vdns/httpsrr.c +++ b/lib/vdns/httpsrr.c @@ -239,11 +239,19 @@ void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo) bool Curl_httpsrr_applicable(struct Curl_easy *data, const struct Curl_https_rrinfo *rr) { - if(!data->conn || !rr) + if(!data->conn) + return FALSE; + return Curl_httpsrr_is_for_peer(data->conn->origin, rr); +} + +bool Curl_httpsrr_is_for_peer(struct Curl_peer *peer, + const struct Curl_https_rrinfo *rr) +{ + if(!peer || !rr) return FALSE; return (!rr->target || !rr->target[0] || (rr->target[0] == '.' && !rr->target[1])) && - (!rr->port_set || rr->port == data->conn->origin->port); + (!rr->port_set || rr->port == peer->port); } #ifdef USE_ARES diff --git a/lib/vdns/httpsrr.h b/lib/vdns/httpsrr.h index 8d6eed1c936c..1aa67b8d81b2 100644 --- a/lib/vdns/httpsrr.h +++ b/lib/vdns/httpsrr.h @@ -67,6 +67,10 @@ void Curl_httpsrr_destroy(struct Curl_https_rrinfo *rrinfo); bool Curl_httpsrr_applicable(struct Curl_easy *data, const struct Curl_https_rrinfo *rr); +/* TRUE if the record is applicable to the given peer. */ +bool Curl_httpsrr_is_for_peer(struct Curl_peer *peer, + const struct Curl_https_rrinfo *rr); + /* * Code points for DNS wire format SvcParams as per RFC 9460 */ diff --git a/lib/version.c b/lib/version.c index c4cdde6377bb..ec796e87baac 100644 --- a/lib/version.c +++ b/lib/version.c @@ -186,8 +186,9 @@ char *curl_version(void) int j; #ifdef DEBUGBUILD - /* Override version string when environment variable CURL_VERSION is set */ - const char *debugversion = getenv("CURL_VERSION"); + /* Override version string when environment variable CURL_DEBUG_VERSION is + set */ + const char *debugversion = getenv("CURL_DEBUG_VERSION"); if(debugversion) { curl_msnprintf(out, sizeof(out), "%s", debugversion); return out; @@ -345,12 +346,6 @@ static const char * const supported_protocols[] = { "scp", "sftp", #endif -#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE) - "smb", -# ifdef USE_SSL - "smbs", -# endif -#endif #ifndef CURL_DISABLE_SMTP "smtp", #endif diff --git a/lib/vquic/capsule.c b/lib/vquic/capsule.c index 4bbdae3ace44..e6456062c1d7 100644 --- a/lib/vquic/capsule.c +++ b/lib/vquic/capsule.c @@ -256,7 +256,7 @@ size_t Curl_capsule_process_udp_raw(struct Curl_cfilter *cf, *err = CURLE_RECV_ERROR; return 0; } - if(capsule_length - 1 >= (uint64_t)SIZE_MAX) { + if(capsule_length - 1 > (uint64_t)(SIZE_MAX - offset)) { infof(data, "Error! Capsule length too large: %" CURL_FORMAT_CURL_OFF_T, (curl_off_t)capsule_length); *err = CURLE_RECV_ERROR; @@ -264,11 +264,6 @@ size_t Curl_capsule_process_udp_raw(struct Curl_cfilter *cf, } payload_len = (size_t)(capsule_length - 1); - if(Curl_bufq_len(recvbufq) < offset + payload_len) { - *err = CURLE_AGAIN; - return 0; - } - if(payload_len > len) { infof(data, "UDP payload does not fit destination buffer: %zu > %zu", payload_len, len); @@ -277,6 +272,17 @@ size_t Curl_capsule_process_udp_raw(struct Curl_cfilter *cf, return 0; } + if(Curl_bufq_len(recvbufq) < offset + payload_len) { + if(Curl_bufq_is_full(recvbufq)) { + infof(data, "Error! Capsule does not fit the receive buffer: %zu", + payload_len); + *err = CURLE_RECV_ERROR; + return 0; + } + *err = CURLE_AGAIN; + return 0; + } + Curl_bufq_skip(recvbufq, offset); if(!payload_len) { *err = CURLE_OK; diff --git a/lib/vquic/cf-ngtcp2-cmn.c b/lib/vquic/cf-ngtcp2-cmn.c index b1cfb68cbdfa..12a4d2b06b19 100644 --- a/lib/vquic/cf-ngtcp2-cmn.c +++ b/lib/vquic/cf-ngtcp2-cmn.c @@ -49,7 +49,7 @@ #include "urldata.h" #include "url.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "curl_trc.h" #include "rand.h" #include "multiif.h" @@ -57,7 +57,7 @@ #include "cf-socket.h" #include "connect.h" #include "progress.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/dynbuf.h" #include "http1.h" #include "select.h" @@ -90,7 +90,7 @@ void Curl_cf_ngtcp2_h3_stream_ctx_free(struct h3_stream_ctx *stream) curlx_free(stream); } -static void h3_stream_hash_free(unsigned int id, void *stream) +static void h3_stream_set_free(uint32_t id, void *stream) { (void)id; DEBUGASSERT(stream); @@ -118,7 +118,7 @@ void Curl_cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, cf_ngtcp2_init_h3_conn *init_h3_conn_cb) { DEBUGASSERT(!ctx->initialized); @@ -129,7 +129,7 @@ CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, H3_STREAM_POOL_SPARES); curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); - Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free); + Curl_u32_ptrset_init(&ctx->streams, h3_stream_set_free); ctx->init_h3_conn_cb = init_h3_conn_cb; ctx->initialized = TRUE; return Curl_vquic_tls_peer_init(origin, peer, sslc, &ctx->ssl_peer); @@ -138,19 +138,57 @@ CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx) { if(ctx && ctx->initialized) { + if(ctx->qlogfd != -1) { + curlx_close(ctx->qlogfd); + ctx->qlogfd = -1; + } + /* Cleanup TLS instances first as ngtcp2 objects + * might be registered there. */ Curl_vquic_tls_cleanup(&ctx->tls); Curl_vquic_ctx_free(&ctx->q); + if(ctx->h3conn) { + nghttp3_conn_del(ctx->h3conn); + ctx->h3conn = NULL; + } + if(ctx->qconn) { + ngtcp2_conn_del(ctx->qconn); + ctx->qconn = NULL; + } +#ifdef OPENSSL_QUIC_API2 + if(ctx->ossl_ctx) { + ngtcp2_crypto_ossl_ctx_del(ctx->ossl_ctx); + ctx->ossl_ctx = NULL; + } +#endif Curl_bufcp_free(&ctx->stream_bufcp); curlx_dyn_free(&ctx->scratch); - Curl_uint32_hash_destroy(&ctx->streams); + Curl_u32_ptrset_clear(&ctx->streams); Curl_ssl_peer_cleanup(&ctx->ssl_peer); curlx_safefree(ctx->tunnel_inbuf); ctx->tunnel_inbuf_len = 0; - if(ctx->qlogfd != -1) { - curlx_close(ctx->qlogfd); - ctx->qlogfd = -1; + } +} + +struct Curl_easy *Curl_cf_ngtcp2_get_xfer(struct Curl_cfilter *cf, + void *stream_user_data, + struct h3_stream_ctx **pstream) +{ + struct h3_stream_ctx *stream = stream_user_data; + + if(pstream) + *pstream = stream; + if(stream && (stream->mid != UINT32_MAX)) { + struct Curl_easy *data, *call_data = CF_DATA_CURRENT(cf); + if(!call_data || !call_data->multi) { + DEBUGASSERT(0); + return NULL; } + data = Curl_multi_get_easy(call_data->multi, stream->mid); + if(data && (data->id == stream->xfer_id)) + return data; + DEBUGASSERT(0); } + return NULL; } static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) @@ -283,24 +321,26 @@ static int cb_ngtcp2_handshake_completed(ngtcp2_conn *tconn, void *user_data) ctx->earlydata_accepted = !ngtcp2_conn_get_tls_early_data_rejected2(ctx->qconn); #else /* older NGTCP2 */ + { #if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_EARLYDATA) - int ossl_early_status = SSL_get_early_data_status(ctx->tls.ossl.ssl); - if(ossl_early_status == SSL_EARLY_DATA_NOT_SENT) - CURL_TRC_CF(data, cf, "OpenSSL did not send early data"); - ctx->earlydata_accepted = (ossl_early_status == SSL_EARLY_DATA_ACCEPTED); + int ossl_early_status = SSL_get_early_data_status(ctx->tls.ossl.ssl); + if(ossl_early_status == SSL_EARLY_DATA_NOT_SENT) + CURL_TRC_CF(data, cf, "OpenSSL did not send early data"); + ctx->earlydata_accepted = (ossl_early_status == SSL_EARLY_DATA_ACCEPTED); #elif defined(USE_GNUTLS) - int flags = gnutls_session_get_flags(ctx->tls.gtls.session); - ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); + int flags = gnutls_session_get_flags(ctx->tls.gtls.session); + ctx->earlydata_accepted = !!(flags & GNUTLS_SFLAGS_EARLY_DATA); #elif defined(USE_WOLFSSL) #ifdef WOLFSSL_EARLY_DATA - ctx->earlydata_accepted = - (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != - WOLFSSL_EARLY_DATA_REJECTED); + ctx->earlydata_accepted = + (wolfSSL_get_early_data_status(ctx->tls.wssl.ssl) != + WOLFSSL_EARLY_DATA_REJECTED); #else - DEBUGASSERT(0); /* should not come here if ED is disabled. */ - ctx->earlydata_accepted = FALSE; + DEBUGASSERT(0); /* should not come here if ED is disabled. */ + ctx->earlydata_accepted = FALSE; #endif /* WOLFSSL_EARLY_DATA */ #endif /* OPENSSL or GNUTLS or WOLFSSL */ + } #endif /* older NGTCP2 */ CURL_TRC_CF(data, cf, "server did%s accept %zu bytes of early data", ctx->earlydata_accepted ? "" : " not", ctx->earlydata_skip); @@ -321,10 +361,11 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, nghttp3_ssize rc; uint64_t nconsumed; int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); - (void)offset; + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); + (void)offset; rc = nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); if(rc < 0) { if(data && stream) { @@ -376,7 +417,7 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; + struct Curl_easy *data = Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, NULL); int rv; (void)tconn; @@ -410,7 +451,7 @@ static int cb_stream_close2(ngtcp2_conn *tconn, uint32_t flags, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; + struct Curl_easy *data = Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, NULL); uint64_t h3_app_error_code = NGHTTP3_H3_NO_ERROR; int rv; @@ -443,14 +484,15 @@ static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; + struct Curl_easy *data = Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, NULL); int rv; (void)tconn; (void)final_size; (void)app_error_code; rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + if(data) + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -499,8 +541,9 @@ static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *s_data = stream_user_data; struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); int rv; (void)tconn; (void)max_data; @@ -509,11 +552,11 @@ static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } - stream = H3_STREAM_CTX(ctx, s_data); if(stream && stream->quic_flow_blocked) { - CURL_TRC_CF(s_data, cf, "[%" PRId64 "] unblock quic flow", stream_id); + CURL_TRC_CF(data, cf, "[%" PRId64 "] unblock quic flow", stream_id); stream->quic_flow_blocked = FALSE; - Curl_multi_mark_dirty(s_data); + if(data) + Curl_multi_mark_dirty(data); } return 0; } @@ -664,18 +707,6 @@ static ngtcp2_callbacks ng_callbacks = { #pragma warning(pop) #endif -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 -} - #ifdef USE_OPENSSL /* The "new session" callback must return zero if the session can be removed * or non-zero if the session has been put into the session cache. @@ -1118,7 +1149,7 @@ CURLcode Curl_cf_ngtcp2_cmn_connect(struct Curl_cfilter *cf, *done = FALSE; - if(cf_ngtcp2_need_httpsrr(data) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, ctx->ssl_peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); return CURLE_OK; @@ -1399,21 +1430,14 @@ static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, struct cf_ngtcp2_sfind_ctx fctx; fctx.stream_id = stream_id; fctx.stream = NULL; - Curl_uint32_hash_visit(&ctx->streams, cf_ngtcp2_sfind, &fctx); + Curl_u32_ptrset_visit(&ctx->streams, cf_ngtcp2_sfind, &fctx); return fctx.stream; } #else static struct h3_stream_ctx *cf_ngtcp2_get_stream(struct cf_ngtcp2_ctx *ctx, int64_t stream_id) { - struct Curl_easy *data = - ngtcp2_conn_get_stream_user_data(ctx->qconn, stream_id); - - if(!data) { - return NULL; - } - - return H3_STREAM_CTX(ctx, data); + return ngtcp2_conn_get_stream_user_data(ctx->qconn, stream_id); } #endif @@ -1857,7 +1881,7 @@ static void cf_ngtcp2_setup_keep_alive(struct Curl_cfilter *cf, ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); CURL_TRC_CF(data, cf, "no peer idle timeout, unset keep-alive"); } - else if(!Curl_uint32_hash_count(&ctx->streams)) { + else if(!Curl_u32_ptrset_count(&ctx->streams)) { ngtcp2_conn_set_keep_alive_timeout(ctx->qconn, UINT64_MAX); CURL_TRC_CF(data, cf, "no active streams, unset keep-alive"); } @@ -1873,22 +1897,28 @@ static void cf_ngtcp2_setup_keep_alive(struct Curl_cfilter *cf, } CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + struct h3_stream_ctx **pstream) { struct cf_ngtcp2_ctx *ctx = cf->ctx; struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + *pstream = NULL; if(!data) return CURLE_FAILED_INIT; - if(stream) + if(stream) { + *pstream = stream; return CURLE_OK; + } stream = curlx_calloc(1, sizeof(*stream)); if(!stream) return CURLE_OUT_OF_MEMORY; stream->id = -1; + stream->xfer_id = data->id; + stream->mid = data->mid; stream->rx_offset = 0; stream->rx_offset_max = H3_STREAM_WINDOW_SIZE_INITIAL; stream->tx_in_flight_ideal = H3_STREAM_SEND_BUF_INITIAL; @@ -1897,17 +1927,17 @@ CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); stream->tx_in_flight_size = 0; - stream->window_size_max = H3_STREAM_WINDOW_SIZE_INITIAL; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { + if(Curl_u32_ptrset_set(&ctx->streams, data->mid, stream)) { Curl_cf_ngtcp2_h3_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } - if(Curl_uint32_hash_count(&ctx->streams) == 1) + if(Curl_u32_ptrset_count(&ctx->streams) == 1) cf_ngtcp2_setup_keep_alive(cf, data); + *pstream = stream; return CURLE_OK; } @@ -1942,8 +1972,8 @@ void Curl_cf_ngtcp2_h3_stream_done(struct Curl_cfilter *cf, if(stream) { CURL_TRC_CF(data, cf, "[%" PRId64 "] easy handle is done", stream->id); Curl_cf_ngtcp2_h3_stream_close(cf, data, stream); - Curl_uint32_hash_remove(&ctx->streams, data->mid); - if(!Curl_uint32_hash_count(&ctx->streams)) + Curl_u32_ptrset_unset(&ctx->streams, data->mid); + if(!Curl_u32_ptrset_count(&ctx->streams)) cf_ngtcp2_setup_keep_alive(cf, data); } } diff --git a/lib/vquic/cf-ngtcp2-cmn.h b/lib/vquic/cf-ngtcp2-cmn.h index e0c27a95f7c0..2ead50ea1526 100644 --- a/lib/vquic/cf-ngtcp2-cmn.h +++ b/lib/vquic/cf-ngtcp2-cmn.h @@ -58,7 +58,7 @@ #include #include "http1.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "vtls/vtls.h" #include "vquic/vquic_int.h" #include "vquic/vquic-tls.h" @@ -83,14 +83,13 @@ struct cf_quic_ctx; #if H3_STREAM_CHUNK_SIZE < NGTCP2_MAX_UDP_PAYLOAD_SIZE #error H3_STREAM_CHUNK_SIZE smaller than NGTCP2_MAX_UDP_PAYLOAD_SIZE #endif -/* The pool keeps spares around and half of a full stream window - * seems good. More does not seem to improve performance. +/* The pool keeps spares around for all streams. * The benefit of the pool is that stream buffers do not keep * spares. Memory consumption goes down when streams run empty, * have a large upload done, etc. */ -#define H3_STREAM_POOL_SPARES 2 +#define H3_STREAM_POOL_SPARES 5 /* The max amount of un-acked upload data we keep around per stream */ -#define H3_STREAM_SEND_BUFFER_MAX (10 * 1024 * 1024) +#define H3_STREAM_SEND_BUFFER_MAX (10 * H3_STREAM_CHUNK_SIZE) #define H3_STREAM_SEND_CHUNKS \ (H3_STREAM_SEND_BUFFER_MAX / H3_STREAM_CHUNK_SIZE) /* How much data we initially want to buffer un-acked */ @@ -129,7 +128,7 @@ struct cf_ngtcp2_ctx { struct curltime handshake_at; /* time connect handshake finished */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ struct dynbuf scratch; /* temp buffer for header construction */ - struct uint_hash streams; /* hash data->mid to h3_stream_ctx */ + struct u32_ptrset streams; /* set of data->mid + h3_stream_ctx */ uint64_t used_bidi_streams; /* bidi streams we have opened */ uint64_t max_bidi_streams; /* max bidi streams we can open */ size_t earlydata_max; /* max amount of early data supported by @@ -155,7 +154,7 @@ struct cf_ngtcp2_ctx { CURLcode Curl_cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, cf_ngtcp2_init_h3_conn *init_h3_conn_cb); void Curl_cf_ngtcp2_ctx_cleanup(struct cf_ngtcp2_ctx *ctx); void Curl_cf_ngtcp2_cmn_err_set(struct Curl_cfilter *cf, @@ -166,6 +165,7 @@ void Curl_cf_ngtcp2_cmn_err_set(struct Curl_cfilter *cf, */ struct h3_stream_ctx { int64_t id; /* HTTP/3 stream identifier */ + curl_off_t xfer_id; /* `id` of transfer owning this stream */ struct bufq sendbuf; /* h3 request body */ struct h1_req_parser h1; /* h1 request parsing */ uint64_t error3; /* HTTP/3 stream error code */ @@ -173,10 +173,10 @@ struct h3_stream_ctx { curl_off_t rx_total; /* total number of bytes received */ uint64_t rx_offset; /* current receive offset */ uint64_t rx_offset_max; /* allowed receive offset */ - uint64_t window_size_max; /* max flow control window set for stream */ size_t tx_in_flight_size; /* sendbuf data "in flight" */ size_t tx_in_flight_ideal; /* ideal amount of un-acked send data */ - int status_code; /* HTTP status code */ + uint32_t mid; /* `mid` of transfer owning this stream */ + int32_t status_code; /* HTTP status code */ CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ BIT(resp_hds_complete); /* we have a complete, final response */ BIT(closed); /* TRUE on stream close */ @@ -229,7 +229,8 @@ CURLcode Curl_cf_ngtcp2_cmn_set_expiry(struct Curl_cfilter *cf, struct cf_ngtcp2_io_ctx *pktx); CURLcode Curl_cf_ngtcp2_h3_stream_setup(struct Curl_cfilter *cf, - struct Curl_easy *data); + struct Curl_easy *data, + struct h3_stream_ctx **pstream); void Curl_cf_ngtcp2_h3_stream_close(struct Curl_cfilter *cf, struct Curl_easy *data, struct h3_stream_ctx *stream); @@ -248,6 +249,10 @@ CURLcode Curl_cf_ngtcp2_cmn_cntrl(struct Curl_cfilter *cf, struct Curl_easy *data, int event, int arg1, void *arg2); +struct Curl_easy *Curl_cf_ngtcp2_get_xfer(struct Curl_cfilter *cf, + void *stream_user_data, + struct h3_stream_ctx **pstream); + #endif /* !CURL_DISABLE_HTTP && USE_NGTCP2 && USE_NGHTTP3 */ #endif /* HEADER_CURL_VQUIC_CF_NGTCP2_CMN_H */ diff --git a/lib/vquic/cf-ngtcp2-proxy.c b/lib/vquic/cf-ngtcp2-proxy.c index 89bf19cc2782..043ee3c6a788 100644 --- a/lib/vquic/cf-ngtcp2-proxy.c +++ b/lib/vquic/cf-ngtcp2-proxy.c @@ -46,7 +46,9 @@ * each active transfer. We use HTTP/3 flow control and only ACK * when we take things out of the buffer. * Chunk size is large enough to take a full DATA frame */ -#define PROXY_H3_STREAM_RECV_CHUNKS ((512 * 1024) / H3_STREAM_CHUNK_SIZE) +#define PROXY_H3_TUNNEL_RX_WIN (512 * 1024) +#define PROXY_H3_TUNNEL_RECV_CHUNKS \ + (PROXY_H3_TUNNEL_RX_WIN / H3_STREAM_CHUNK_SIZE) typedef enum { H3_TUNNEL_INIT, /* init/default/no tunnel state */ @@ -57,7 +59,7 @@ typedef enum { } h3_tunnel_state; struct h3_tunnel_stream { - struct Curl_peer *peer; /* where the tunnel goes to */ + struct Curl_peer *dest; /* where the tunnel goes to */ struct http_resp *resp; struct bufq recvbuf; char *authority; @@ -69,19 +71,19 @@ struct h3_tunnel_stream { }; static CURLcode h3_tunnel_stream_init(struct h3_tunnel_stream *ts, - struct Curl_peer *peer, + struct Curl_peer *dest, bool udp) { ts->state = H3_TUNNEL_INIT; - Curl_peer_link(&ts->peer, peer); + Curl_peer_link(&ts->dest, dest); Curl_bufq_init2(&ts->recvbuf, H3_STREAM_CHUNK_SIZE, - PROXY_H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + PROXY_H3_TUNNEL_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); ts->udp = udp; /* host:port with IPv6 support */ - ts->authority = curl_maprintf("%s%s%s:%u", peer->ipv6 ? "[" : "", - peer->hostname, - peer->ipv6 ? "]" : "", - peer->port); + ts->authority = curl_maprintf("%s%s%s:%u", dest->ipv6 ? "[" : "", + dest->hostname, + dest->ipv6 ? "]" : "", + dest->port); if(!ts->authority) return CURLE_OUT_OF_MEMORY; @@ -101,7 +103,7 @@ static void h3_tunnel_stream_reset(struct h3_tunnel_stream *ts) static void h3_tunnel_stream_cleanup(struct h3_tunnel_stream *ts) { - Curl_peer_unlink(&ts->peer); + Curl_peer_unlink(&ts->dest); Curl_bufq_free(&ts->recvbuf); Curl_http_resp_free(ts->resp); curlx_safefree(ts->authority); @@ -175,7 +177,7 @@ static CURLcode cf_ngtcp2_proxy_h3_init(struct Curl_cfilter *cf, static CURLcode cf_h3_proxy_ctx_init(struct cf_h3_proxy_ctx *ctx, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, struct Curl_peer *tunnel_peer, uint8_t tunnel_transport) { @@ -271,47 +273,33 @@ static void cf_h3_proxy_upd_rx_win(struct Curl_cfilter *cf, { struct cf_h3_proxy_ctx *pctx = cf->ctx; struct cf_ngtcp2_ctx *ctx = &pctx->ngtcp2_ctx; - uint64_t cur_win, wanted_win = H3_STREAM_WINDOW_SIZE_MAX; - - /* how much does rate limiting allow us to acknowledge? */ - if(Curl_rlimit_active(&data->progress.dl.rlimit)) { - int64_t avail; - - /* start rate limit updates only after first bytes arrived */ - if(!stream->rx_offset) - return; - - avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); - if(avail <= 0) { - /* nothing available, do not extend the rx offset */ - CURL_TRC_CF(data, cf, "[%" PRId64 "] dl rate limit exhausted (%" PRId64 - " tokens)", stream->id, avail); - return; - } - wanted_win = CURLMIN((uint64_t)avail, H3_STREAM_WINDOW_SIZE_MAX); - } + uint64_t cur_win, wanted_win = 0; + size_t rx_buffered; + if(!stream) /* already closed */ + return; if(stream->rx_offset_max < stream->rx_offset) { DEBUGASSERT(0); return; } + + rx_buffered = Curl_bufq_len(&pctx->tunnel.recvbuf); + wanted_win = (rx_buffered < PROXY_H3_TUNNEL_RX_WIN) ? + (PROXY_H3_TUNNEL_RX_WIN - rx_buffered) : 0; cur_win = stream->rx_offset_max - stream->rx_offset; - if(cur_win < wanted_win) { - /* We have exhausted the credit we gave the QUIC peer for DATA. - * We extend it with the amount we can give (rate limit) */ - uint64_t ext = wanted_win - cur_win; - - ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, ext); - ngtcp2_conn_extend_max_offset(ctx->qconn, ext); - stream->rx_offset_max += ext; - if(stream->rx_offset_max > stream->window_size_max) { - stream->window_size_max = stream->rx_offset_max; - CURL_TRC_CF(data, cf, "[%" PRId64 "] max window now -> %" PRIu64, - stream->id, stream->window_size_max); + if(wanted_win > cur_win) { + uint64_t delta = wanted_win - cur_win; + + if(UINT64_MAX - delta < stream->rx_offset_max) + delta = UINT64_MAX - stream->rx_offset_max; + if(delta) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] rx window, extend %" PRIu64 + " (buffered %zu) by %" PRIu64 " bytes", + stream->id, cur_win, rx_buffered, delta); + stream->rx_offset_max += delta; + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, delta); + ngtcp2_conn_extend_max_offset(ctx->qconn, delta); } - CURL_TRC_CF(data, cf, "[%" PRId64 "] rx_offset_max -> %" PRIu64 - " (ext %" PRIu64 ", win %" PRIu64 ")", - stream->id, stream->rx_offset_max, ext, wanted_win); } } @@ -344,9 +332,7 @@ static int cb_h3_proxy_recv_data(nghttp3_conn *conn, int64_t stream3_id, return NGHTTP3_ERR_CALLBACK_FAILURE; } - /* DATA has been moved into our local recv buffer. Update stream offsets - * and give QUIC read credit back so long transfers over proxy tunnels - * do not stall on stream/connection flow-control limits. */ + /* DATA has been moved into our local recv buffer. Update stream offsets. */ stream->rx_offset += buflen; if(stream->rx_offset_max < stream->rx_offset) stream->rx_offset_max = stream->rx_offset; @@ -845,6 +831,7 @@ static CURLcode cf_h3_proxy_recv(struct Curl_cfilter *cf, result = Curl_bufq_cread(&pctx->tunnel.recvbuf, buf, len, pnread); if(result) goto out; + cf_h3_proxy_upd_rx_win(cf, data, pctx->tunnel.stream); } result = Curl_cf_ngtcp2_progress_ingress(cf, data, &pktx); @@ -859,6 +846,7 @@ static CURLcode cf_h3_proxy_recv(struct Curl_cfilter *cf, stream->id, len, *pnread, (int)result); goto out; } + cf_h3_proxy_upd_rx_win(cf, data, pctx->tunnel.stream); } if(*pnread) { @@ -915,15 +903,9 @@ static CURLcode cf_h3_proxy_submit(struct Curl_cfilter *cf, if(result) goto out; - result = Curl_cf_ngtcp2_h3_stream_setup(cf, data); + result = Curl_cf_ngtcp2_h3_stream_setup(cf, data, &stream); if(result) goto out; - stream = H3_STREAM_CTX(ctx, data); - DEBUGASSERT(stream); - if(!stream) { - result = CURLE_FAILED_INIT; - goto out; - } nheader = Curl_dynhds_count(&h2_headers); nva = curlx_malloc(sizeof(nghttp3_nv) * nheader); @@ -967,7 +949,7 @@ static CURLcode cf_h3_proxy_submit(struct Curl_cfilter *cf, preader = &reader; rc = nghttp3_conn_submit_request(ctx->h3conn, stream->id, - nva, nheader, preader, data); + nva, nheader, preader, stream); if(rc) { switch(rc) { @@ -1044,13 +1026,13 @@ static CURLcode cf_h3_proxy_submit_CONNECT(struct Curl_cfilter *cf, struct Curl_easy *data, struct h3_tunnel_stream *ts) { + struct cf_h3_proxy_ctx *ctx = cf->ctx; CURLcode result; struct httpreq *req = NULL; - result = Curl_http_proxy_create_tunnel_request(&req, cf, data, - ts->peer, - PROXY_HTTP_V3, - (bool)ts->udp); + result = Curl_http_proxy_create_tunnel_request( + &req, cf, data, ctx->ngtcp2_ctx.ssl_peer.peer, + ts->dest, PROXY_HTTP_V3, (bool)ts->udp); if(!result) result = Curl_creader_set_null(data); if(!result) @@ -1204,6 +1186,12 @@ static void cf_h3_proxy_destroy(struct Curl_cfilter *cf, (void)data; if(ctx) { CURL_TRC_CF(data, cf, "cf_h3_proxy_destroy()"); + if(ctx->ngtcp2_ctx.qconn) { + struct cf_call_data save; + CF_DATA_SAVE(save, cf, data); + Curl_cf_ngtcp2_cmn_conn_close(cf, data); + CF_DATA_RESTORE(cf, save); + } cf_h3_proxy_ctx_free(ctx); cf->ctx = NULL; } diff --git a/lib/vquic/cf-ngtcp2.c b/lib/vquic/cf-ngtcp2.c index e3bca8774e40..8cda37656da4 100644 --- a/lib/vquic/cf-ngtcp2.c +++ b/lib/vquic/cf-ngtcp2.c @@ -27,7 +27,7 @@ #include "urldata.h" #include "url.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "curl_trc.h" #include "rand.h" #include "multiif.h" @@ -35,7 +35,7 @@ #include "cf-socket.h" #include "connect.h" #include "progress.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/dynbuf.h" #include "http1.h" #include "select.h" @@ -95,14 +95,14 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); (void)conn; (void)stream_id; /* we might be called by nghttp3 after we already cleaned up */ - if(!stream) + if(!data || !stream) return 0; stream->closed = TRUE; @@ -212,13 +212,14 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); (void)conn; (void)stream3_id; - if(!stream) + if(!stream || !data) return NGHTTP3_ERR_CALLBACK_FAILURE; h3_xfer_write_resp(cf, data, stream, (const char *)buf, buflen, FALSE); @@ -240,14 +241,14 @@ static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = stream_user_data; (void)conn; /* nghttp3 has consumed bytes on the QUIC stream and we need to * tell the QUIC connection to increase its flow control */ ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream3_id, consumed); ngtcp2_conn_extend_max_offset(ctx->qconn, consumed); + if(stream) { stream->rx_offset += consumed; stream->rx_offset_max += consumed; @@ -259,15 +260,15 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, void *user_data, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); (void)conn; (void)stream_id; (void)fin; (void)cf; - if(!stream) + if(!stream || !data) return 0; /* add a CRLF only if we have received some headers */ h3_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), @@ -291,8 +292,9 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, struct cf_ngtcp2_ctx *ctx = cf->ctx; nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); CURLcode result = CURLE_OK; (void)conn; (void)stream_id; @@ -301,7 +303,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id, (void)cf; /* we might have cleaned up this transfer already */ - if(!stream) + if(!stream || !data) return 0; if(token == NGHTTP3_QPACK_TOKEN__STATUS) { @@ -373,13 +375,14 @@ static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; + struct Curl_easy *data = Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, NULL); int rv; (void)conn; rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, app_error_code); - CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); + if(data) + CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { return NGHTTP3_ERR_CALLBACK_FAILURE; } @@ -567,9 +570,7 @@ static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream = stream_user_data; size_t skiplen; (void)cf; @@ -601,9 +602,9 @@ static nghttp3_ssize cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, void *stream_user_data) { struct Curl_cfilter *cf = user_data; - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct Curl_easy *data = stream_user_data; - struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct h3_stream_ctx *stream; + struct Curl_easy *data = + Curl_cf_ngtcp2_get_xfer(cf, stream_user_data, &stream); size_t nwritten = 0; size_t nvecs = 0; (void)cf; @@ -612,7 +613,7 @@ static nghttp3_ssize cb_h3_read_req_body(nghttp3_conn *conn, int64_t stream_id, (void)user_data; (void)veccnt; - if(!stream) + if(!stream || !data) return NGHTTP3_ERR_CALLBACK_FAILURE; /* nghttp3 keeps references to the sendbuf data until it is ACKed * by the server (see `cb_h3_acked_req_body()` for updates). @@ -679,15 +680,9 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, *pnwritten = 0; Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); - result = Curl_cf_ngtcp2_h3_stream_setup(cf, data); + result = Curl_cf_ngtcp2_h3_stream_setup(cf, data, &stream); if(result) goto out; - stream = H3_STREAM_CTX(ctx, data); - DEBUGASSERT(stream); - if(!stream) { - result = CURLE_FAILED_INIT; - goto out; - } result = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, !data->state.http_ignorecustom ? @@ -724,7 +719,7 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, nva[i].flags = NGHTTP3_NV_FLAG_NONE; } - rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, data); + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &sid, stream); if(rc) { failf(data, "cannot open bidi streams"); result = CURLE_SEND_ERROR; @@ -758,7 +753,7 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, } rc = nghttp3_conn_submit_request(ctx->h3conn, stream->id, - nva, nheader, preader, data); + nva, nheader, preader, stream); if(rc) { switch(rc) { case NGHTTP3_ERR_CONN_CLOSING: @@ -971,36 +966,6 @@ static CURLcode cf_ngtcp2_cntrl(struct Curl_cfilter *cf, return result; } -static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx) -{ - struct cf_call_data save = ctx->call_data; - - if(!ctx->initialized) - return; - if(ctx->qlogfd != -1) { - curlx_close(ctx->qlogfd); - } - ctx->qlogfd = -1; - Curl_vquic_tls_cleanup(&ctx->tls); - Curl_ssl_peer_cleanup(&ctx->ssl_peer); - Curl_vquic_ctx_free(&ctx->q); - if(ctx->h3conn) { - nghttp3_conn_del(ctx->h3conn); - ctx->h3conn = NULL; - } - if(ctx->qconn) { - ngtcp2_conn_del(ctx->qconn); - ctx->qconn = NULL; - } -#ifdef OPENSSL_QUIC_API2 - if(ctx->ossl_ctx) { - ngtcp2_crypto_ossl_ctx_del(ctx->ossl_ctx); - ctx->ossl_ctx = NULL; - } -#endif - ctx->call_data = save; -} - static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; @@ -1011,7 +976,6 @@ static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) struct cf_call_data save; CF_DATA_SAVE(save, cf, data); Curl_cf_ngtcp2_cmn_conn_close(cf, data); - cf_ngtcp2_ctx_close(ctx); CF_DATA_RESTORE(cf, save); } Curl_cf_ngtcp2_ctx_cleanup(ctx); @@ -1053,8 +1017,10 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, max_streams += avail_bidi_streams; *pres1 = (max_streams > INT_MAX) ? INT_MAX : (int)max_streams; } - else /* transport params not arrived yet? take our default. */ - *pres1 = (int)Curl_multi_max_concurrent_streams(data->multi); + else { /* transport params not arrived yet? take our default. */ + uint32_t n = Curl_multi_max_concurrent_streams(data->multi); + *pres1 = (n < INT_MAX) ? (int)n : INT_MAX; + } CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: " "MAX_CONCURRENT -> %d (%u in use)", cf->conn->connection_id, *pres1, cf->conn->attached_xfers); @@ -1147,7 +1113,8 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, struct Curl_peer *origin, - struct Curl_peer *peer) + struct Curl_peer *peer, + struct Curl_cfilter **pcf) { struct cf_ngtcp2_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; @@ -1173,6 +1140,7 @@ CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, curlx_free(ctx); } } + *pcf = cf; return result; } diff --git a/lib/vquic/cf-ngtcp2.h b/lib/vquic/cf-ngtcp2.h index 601efc82245a..0ea7933590a2 100644 --- a/lib/vquic/cf-ngtcp2.h +++ b/lib/vquic/cf-ngtcp2.h @@ -56,8 +56,9 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, struct Curl_sockaddr_ex *addr); CURLcode Curl_cf_ngtcp2_insert_after(struct Curl_cfilter *cf_at, - struct Curl_peer *origin, - struct Curl_peer *peer); + struct Curl_peer *origin, + struct Curl_peer *peer, + struct Curl_cfilter **pcf); #endif #endif /* HEADER_CURL_VQUIC_CF_NGTCP2_H */ diff --git a/lib/vquic/cf-quiche.c b/lib/vquic/cf-quiche.c index 934a56cadaf1..c6456d17f885 100644 --- a/lib/vquic/cf-quiche.c +++ b/lib/vquic/cf-quiche.c @@ -29,7 +29,7 @@ #include #include "bufq.h" -#include "uint-hash.h" +#include "u32_ptrset.h" #include "urldata.h" #include "cfilters.h" #include "cf-socket.h" @@ -86,7 +86,7 @@ struct cf_quiche_ctx { uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ - struct uint_hash streams; /* hash `data->mid` to `stream_ctx` */ + struct u32_ptrset streams; /* set of `data->mid` + `stream_ctx` */ struct dynbuf h1hdr; /* temp buffer for header construction */ struct bufq writebuf; /* temp buffer for writing bodies */ curl_off_t data_recvd; @@ -107,12 +107,12 @@ static void quiche_debug_log(const char *line, void *argp) } #endif -static void h3_stream_hash_free(unsigned int id, void *stream); +static void h3_stream_set_free(uint32_t id, void *stream); static CURLcode cf_quiche_ctx_init(struct cf_quiche_ctx *ctx, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc) + struct ssl_filter_config *sslc) { DEBUGASSERT(!ctx->initialized); #ifdef DEBUG_QUICHE @@ -122,7 +122,7 @@ static CURLcode cf_quiche_ctx_init(struct cf_quiche_ctx *ctx, } #endif curlx_dyn_init(&ctx->h1hdr, CURL_MAX_HTTP_HEADER); - Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free); + Curl_u32_ptrset_init(&ctx->streams, h3_stream_set_free); Curl_bufq_init2(&ctx->writebuf, H3_STREAM_CHUNK_SIZE, H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); ctx->data_recvd = 0; @@ -138,7 +138,7 @@ static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) Curl_vquic_tls_cleanup(&ctx->tls); Curl_ssl_peer_cleanup(&ctx->ssl_peer); Curl_vquic_ctx_free(&ctx->q); - Curl_uint32_hash_destroy(&ctx->streams); + Curl_u32_ptrset_clear(&ctx->streams); curlx_dyn_free(&ctx->h1hdr); Curl_bufq_free(&ctx->writebuf); } @@ -176,7 +176,7 @@ struct h3_stream_ctx { uint64_t id; /* HTTP/3 protocol stream identifier */ struct h1_req_parser h1; /* h1 request parsing */ uint64_t error3; /* HTTP/3 stream error code */ - int status_code; /* HTTP status code */ + int32_t status_code; /* HTTP status code */ CURLcode xfer_result; /* result from cf_quiche_write_(hd/body) */ BIT(opened); /* TRUE after stream has been opened */ BIT(closed); /* TRUE on stream close */ @@ -193,7 +193,7 @@ static void h3_stream_ctx_free(struct h3_stream_ctx *stream) curlx_free(stream); } -static void h3_stream_hash_free(unsigned int id, void *stream) +static void h3_stream_set_free(uint32_t id, void *stream) { (void)id; DEBUGASSERT(stream); @@ -233,7 +233,7 @@ static void cf_quiche_for_all_streams(struct Curl_cfilter *cf, vctx.multi = multi; vctx.cb = do_cb; vctx.user_data = user_data; - Curl_uint32_hash_visit(&ctx->streams, cf_quiche_stream_do, &vctx); + Curl_u32_ptrset_visit(&ctx->streams, cf_quiche_stream_do, &vctx); } static bool cf_quiche_do_resume(struct Curl_cfilter *cf, @@ -278,7 +278,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, stream->id = -1; Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); - if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) { + if(Curl_u32_ptrset_set(&ctx->streams, data->mid, stream)) { h3_stream_ctx_free(stream); return CURLE_OUT_OF_MEMORY; } @@ -318,7 +318,7 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) if(stream) { CURL_TRC_CF(data, cf, "[%" PRIu64 "] easy handle is done", stream->id); cf_quiche_stream_close(cf, data, stream); - Curl_uint32_hash_remove(&ctx->streams, data->mid); + Curl_u32_ptrset_unset(&ctx->streams, data->mid); } } @@ -635,7 +635,7 @@ static CURLcode cf_poll_events(struct Curl_cfilter *cf, dctx.cf = cf; dctx.multi = data->multi; dctx.ev = ev; - Curl_uint32_hash_visit(&ctx->streams, cf_quiche_disp_event, &dctx); + Curl_u32_ptrset_visit(&ctx->streams, cf_quiche_disp_event, &dctx); quiche_h3_event_free(ev); } } @@ -1399,7 +1399,7 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf, } *done = FALSE; - if(Curl_ossl_need_httpsrr(data) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, ctx->ssl_peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); return CURLE_OK; @@ -1682,7 +1682,8 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, struct Curl_peer *origin, - struct Curl_peer *peer) + struct Curl_peer *peer, + struct Curl_cfilter **pcf) { struct cf_quiche_ctx *ctx = NULL; struct Curl_cfilter *cf = NULL; @@ -1706,7 +1707,7 @@ CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, if(ctx) cf_quiche_ctx_free(ctx); } - + *pcf = cf; return result; } diff --git a/lib/vquic/cf-quiche.h b/lib/vquic/cf-quiche.h index 88d9161dd768..80068c9058cd 100644 --- a/lib/vquic/cf-quiche.h +++ b/lib/vquic/cf-quiche.h @@ -43,7 +43,8 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf, struct Curl_sockaddr_ex *addr); CURLcode Curl_cf_quiche_insert_after(struct Curl_cfilter *cf_at, struct Curl_peer *origin, - struct Curl_peer *peer); + struct Curl_peer *peer, + struct Curl_cfilter **pcf); #endif diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c index 75e2f4e1e2c4..99c0553a283a 100644 --- a/lib/vquic/vquic-tls.c +++ b/lib/vquic/vquic-tls.c @@ -51,7 +51,7 @@ CURLcode Curl_vquic_tls_peer_init(struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, struct ssl_peer *ssl_peer) { char tls_id[80]; @@ -155,10 +155,10 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, struct Curl_easy *data, struct ssl_peer *peer) { - struct ssl_primary_config *conn_config; + struct ssl_filter_config *conn_config; CURLcode result = CURLE_OK; - conn_config = Curl_ssl_cf_get_primary_config(cf); + conn_config = Curl_ssl_cf_get_filter_config(cf); if(!conn_config) return CURLE_FAILED_INIT; @@ -169,8 +169,6 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, result = Curl_gtls_verifyserver( cf, data, ctx->gtls.session, conn_config, &data->set.ssl, peer, CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY)); - if(result) - return result; #elif defined(USE_WOLFSSL) (void)data; if(conn_config->verifyhost) { diff --git a/lib/vquic/vquic-tls.h b/lib/vquic/vquic-tls.h index e8d2418b0611..0e7f34dff760 100644 --- a/lib/vquic/vquic-tls.h +++ b/lib/vquic/vquic-tls.h @@ -68,7 +68,7 @@ typedef CURLcode Curl_vquic_session_reuse_cb(struct Curl_cfilter *cf, CURLcode Curl_vquic_tls_peer_init(struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, struct ssl_peer *ssl_peer); /** diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index bbc5777eac88..fbca4b84b8b7 100644 --- a/lib/vquic/vquic.c +++ b/lib/vquic/vquic.c @@ -50,7 +50,8 @@ #include "bufq.h" #include "curlx/dynbuf.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" +#include "connect.h" #include "cfilters.h" #include "vdns/cf-dns.h" #include "vquic/cf-ngtcp2.h" @@ -564,9 +565,12 @@ static size_t vquic_msghdr_get_udp_gro(struct msghdr *msg) } #endif /* (HAVE_SENDMMSG || HAVE_SENDMSG) && !HAVE_APPLE_MSG_X */ +/* IP_RECVTOS was added in the macOS 10.13 SDK */ #if (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) || \ defined(HAVE_APPLE_MSG_X)) && \ - (defined(IP_RECVTOS) || defined(IP_TOS)) && defined(IPTOS_ECN_MASK) + ((defined(__APPLE__) && defined(IP_RECVTOS)) || \ + (!defined(__APPLE__) && defined(IP_TOS))) && \ + defined(IPTOS_ECN_MASK) static uint8_t vquic_msghdr_get_ecn(struct msghdr *msg, int family) { struct cmsghdr *cmsg; @@ -627,7 +631,8 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - Curl_vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp, + size_t *pnread) { #if defined(__linux__) && defined(UDP_GRO) #define MMSG_NUM 16 @@ -729,6 +734,7 @@ static CURLcode recvmmsg_packets(struct Curl_cfilter *cf, "vquic_recvmmsg(len=%zu, packets=%zu, calls=%zu) -> %d", total_nread, pkts, calls, (int)result); Curl_multi_xfer_sockbuf_release(data, sockbuf); + *pnread = total_nread; return result; } @@ -739,7 +745,7 @@ static CURLcode recvmsg_x_packets(struct Curl_cfilter *cf, struct cf_quic_ctx *qctx, size_t max_pkts, Curl_vquic_recv_pkts_cb *recv_cb, - void *userp) + void *userp, size_t *pnread) { #define MSG_X_NUM 64 #define MSG_BUF_SIZE (2048) @@ -839,6 +845,7 @@ static CURLcode recvmsg_x_packets(struct Curl_cfilter *cf, "vquic_recvmsg_x(len=%zu, packets=%zu, calls=%zu) -> %d", total_nread, pkts, calls, (int)result); Curl_multi_xfer_sockbuf_release(data, sockbuf); + *pnread = total_nread; return result; } @@ -847,7 +854,8 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - Curl_vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp, + size_t *pnread) { #define CMSG_PER_MSG_SIZE CMSG_SPACE(sizeof(int)) struct iovec msg_iov; @@ -925,6 +933,7 @@ static CURLcode recvmsg_packets(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "vquic_recvmsg(len=%zu, packets=%zu, calls=%zu) -> %d", total_nread, pkts, calls, (int)result); + *pnread = total_nread; return result; } @@ -933,7 +942,8 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, struct Curl_easy *data, struct cf_quic_ctx *qctx, size_t max_pkts, - Curl_vquic_recv_pkts_cb *recv_cb, void *userp) + Curl_vquic_recv_pkts_cb *recv_cb, void *userp, + size_t *pnread) { uint8_t buf[64 * 1024]; int bufsize = (int)sizeof(buf); @@ -990,6 +1000,7 @@ static CURLcode recvfrom_packets(struct Curl_cfilter *cf, CURL_TRC_CF(data, cf, "vquic_recvfrom(len=%zu, packets=%zu, calls=%zu) -> %d", total_nread, pkts, calls, (int)result); + *pnread = total_nread; return result; } #endif /* !HAVE_SENDMMSG && !HAVE_SENDMSG */ @@ -1001,17 +1012,20 @@ CURLcode Curl_vquic_recv_packets(struct Curl_cfilter *cf, Curl_vquic_recv_pkts_cb *recv_cb, void *userp) { CURLcode result; + size_t nread = 0; #ifdef HAVE_SENDMMSG - result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp, &nread); #elif defined(HAVE_APPLE_MSG_X) - result = recvmsg_x_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmsg_x_packets(cf, data, qctx, max_pkts, recv_cb, userp, + &nread); #elif defined(HAVE_SENDMSG) - result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp, &nread); #else - result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp); + result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp, &nread); #endif if(!result) { - if(!qctx->got_first_byte) { + /* EAGAIN is no error, only count actual packets as a reply */ + if(!qctx->got_first_byte && nread) { qctx->got_first_byte = TRUE; qctx->first_byte_at = qctx->last_op; } @@ -1054,9 +1068,10 @@ CURLcode Curl_qlogdir(struct Curl_easy *data, if(!result) { int qlogfd = curlx_open(curlx_dyn_ptr(&fname), O_WRONLY | O_CREAT | CURL_O_BINARY, - data->set.new_file_perms #ifdef _WIN32 - & (_S_IREAD | _S_IWRITE) + data->set.new_file_perms & (_S_IREAD | _S_IWRITE) +#else + (mode_t)data->set.new_file_perms #endif ); if(qlogfd != -1) @@ -1076,14 +1091,16 @@ CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, struct Curl_peer *origin, struct Curl_peer *peer) { + struct Curl_cfilter *cf = NULL; CURLcode result; (void)data; /* not used in all cases and compilers are stupid */ #if defined(USE_NGTCP2) && defined(USE_NGHTTP3) - result = Curl_cf_ngtcp2_insert_after(cf_at, origin, peer); + result = Curl_cf_ngtcp2_insert_after(cf_at, origin, peer, &cf); #elif defined(USE_QUICHE) - result = Curl_cf_quiche_insert_after(cf_at, origin, peer); + result = Curl_cf_quiche_insert_after(cf_at, origin, peer, &cf); #else + (void)cf) (void)cf_at; (void)origin; (void)peer; @@ -1093,12 +1110,9 @@ CURLcode Curl_cf_quic_insert_after(struct Curl_cfilter *cf_at, #if defined(USE_HTTPSRR) && defined(USE_ECH) /* When using ECH, kick off the HTTPS-RR resolve */ if(!result && (origin->scheme->family == CURLPROTO_HTTP) && - CURLECH_ENABLED(data) && - Curl_ssl_supports(data, SSLSUPP_ECH) && - (data->set.tls_ech != CURLECH_GREASE) && - !CURL_EASY_STR(data, STRING_ECH_CONFIG)) { - result = Curl_conn_dns_add_https_resolve(data, cf_at->conn, - cf_at->sockindex, origin); + Curl_ssl_need_httpsrr(cf)) { + result = Curl_conn_dns_add_https_resolve(data, cf->conn, + cf->sockindex, origin); } #endif /* USE_HTTPSRR && USE_ECH */ return result; @@ -1189,10 +1203,9 @@ CURLcode Curl_cf_h3_proxy_create(struct Curl_cfilter **pcf, #endif /* !CURL_DISABLE_PROXY && USE_PROXY_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, - const struct connectdata *conn, - unsigned char transport) + struct connectdata *conn) { - if(transport == TRNSPRT_UNIX) { + if(Curl_conn_get_first_peer(conn, FIRSTSOCKET)->unix_socket) { failf(data, "HTTP/3 cannot be used over UNIX domain sockets"); return CURLE_QUIC_CONNECT_ERROR; } @@ -1321,12 +1334,10 @@ struct nghttp3_mem *Curl_nghttp3_mem(void) #else /* CURL_DISABLE_HTTP || !USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, - const struct connectdata *conn, - unsigned char transport) + struct connectdata *conn) { (void)data; (void)conn; - (void)transport; DEBUGF(infof(data, "QUIC is not supported in this build")); return CURLE_NOT_BUILT_IN; } diff --git a/lib/vquic/vquic.h b/lib/vquic/vquic.h index f1363e2c0a76..eea440d67b9f 100644 --- a/lib/vquic/vquic.h +++ b/lib/vquic/vquic.h @@ -86,7 +86,6 @@ extern struct Curl_cftype Curl_cft_h3_proxy; #endif /* !CURL_DISABLE_HTTP && USE_HTTP3 */ CURLcode Curl_conn_may_http3(struct Curl_easy *data, - const struct connectdata *conn, - unsigned char transport); + struct connectdata *conn); #endif /* HEADER_CURL_VQUIC_QUIC_H */ diff --git a/lib/vquic/vquic_int.h b/lib/vquic/vquic_int.h index 23e1102eb0d9..39398529357a 100644 --- a/lib/vquic/vquic_int.h +++ b/lib/vquic/vquic_int.h @@ -28,6 +28,7 @@ #ifdef USE_HTTP3 #include "bufq.h" +#include "u32_ptrset.h" #define MAX_UDP_PAYLOAD_SIZE 1452 @@ -78,7 +79,7 @@ struct cf_quic_ctx { }; #define H3_STREAM_CTX(ctx, data) \ - ((data) ? Curl_uint32_hash_get(&(ctx)->streams, (data)->mid) : NULL) + ((data) ? Curl_u32_ptrset_get(&(ctx)->streams, (data)->mid) : NULL) CURLcode Curl_vquic_ctx_init(struct Curl_easy *data, struct cf_quic_ctx *qctx); diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index 03a84d218b7a..c354ca55245b 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -834,7 +834,8 @@ static int myssh_in_AUTHLIST(struct Curl_easy *data, /* For public key auth we need either the private key or CURLSSH_AUTH_AGENT. */ if((sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) && - (sshc->priv_key || (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) { + ((sshc->priv_key && sshc->priv_key[0]) || + (data->set.ssh_auth_types & CURLSSH_AUTH_AGENT))) { myssh_to(data, sshc, SSH_AUTH_PKEY_INIT); infof(data, "Authentication using SSH public key file"); } @@ -864,8 +865,8 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, /* Two choices, (1) private key was given on CMD, * (2) use the "default" keys. */ - if(sshc->priv_key) { - if(sshc->pubkey && !data->set.ssl.primary.key_passwd) { + if(sshc->priv_key && sshc->priv_key[0]) { + if(sshc->pubkey && !sshc->passphrase) { rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, sshc->pubkey); if(rc == SSH_AUTH_AGAIN) return SSH_AGAIN; @@ -877,7 +878,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, } rc = ssh_pki_import_privkey_file(sshc->priv_key, - data->set.ssl.primary.key_passwd, NULL, + sshc->passphrase, NULL, NULL, &sshc->privkey); if(rc != SSH_OK) { failf(data, "Could not load private key file %s", sshc->priv_key); @@ -889,7 +890,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data, } else { rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL, - data->set.ssl.primary.key_passwd); + sshc->passphrase); if(rc == SSH_AUTH_AGAIN) return SSH_AGAIN; @@ -1916,6 +1917,7 @@ static void sshc_cleanup(struct ssh_conn *sshc) sshc->pubkey = NULL; } + curlx_safefree(sshc->passphrase); curlx_safefree(sshc->pub_key); curlx_safefree(sshc->priv_key); curlx_safefree(sshc->quote_path1); @@ -2499,14 +2501,15 @@ static CURLcode myssh_block_statemach(struct Curl_easy *data, if(block) { curl_socket_t fd_read = conn->sock[FIRSTSOCKET]; /* wait for the socket to become ready */ - (void)SOCKET_READABLE(fd_read, left_ms > 1000 ? 1000 : left_ms); + (void)SOCKET_READABLE(fd_read, + (!left_ms || left_ms > 1000) ? 1000 : left_ms); } } return result; } -static void myssh_easy_dtor(void *key, size_t klen, void *entry) +static void myssh_easy_dtor(const void *key, size_t klen, void *entry) { struct SSHPROTO *sshp = entry; (void)key; @@ -2515,7 +2518,7 @@ static void myssh_easy_dtor(void *key, size_t klen, void *entry) curlx_free(sshp); } -static void myssh_conn_dtor(void *key, size_t klen, void *entry) +static void myssh_conn_dtor(const void *key, size_t klen, void *entry) { struct ssh_conn *sshc = entry; (void)key; @@ -2759,7 +2762,7 @@ static CURLcode myssh_done(struct Curl_easy *data, else result = status; - if(Curl_pgrsDone(data)) + if(Curl_pgrsDone(data, Curl_pgrs_now(data))) return CURLE_ABORTED_BY_CALLBACK; CURL_REQ_CLEAR_IO(data); diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c index 24a651d30ebb..1a05eaa14461 100644 --- a/lib/vssh/libssh2.c +++ b/lib/vssh/libssh2.c @@ -53,7 +53,7 @@ #include "parsedate.h" /* for the week day and month names */ #include "multiif.h" #include "select.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "vssh/vssh.h" #include "curlx/strparse.h" #include "curlx/base64.h" /* for curlx_base64_encode() */ @@ -652,6 +652,12 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, !CURL_EASY_STR(data, STRING_SSH_HOST_PUBLIC_KEY_SHA256)) { struct libssh2_knownhost *store = NULL; struct connectdata *conn = data->conn; + /* key types already confirmed absent for this host, so repeat hashed + entries of the same type do not each force a fresh checkp() scan + of the whole known_hosts set. one slot per possible key type (indexed + by the type shifted down) so no entry ordering can overflow it */ + bool absent_type[(LIBSSH2_KNOWNHOST_KEY_MASK >> + LIBSSH2_KNOWNHOST_KEY_SHIFT) + 1] = { FALSE }; /* 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 is enclosed in */ @@ -685,9 +691,25 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data, break; } } - else { - found = TRUE; - break; + else if(store->key) { + unsigned int type = (store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) + >> LIBSSH2_KNOWNHOST_KEY_SHIFT; + if(!absent_type[type]) { + int keycheck = libssh2_knownhost_checkp( + sshc->kh, conn->origin->hostname, + (conn->origin->port != PORT_SSH) ? conn->origin->port : -1, + store->key, strlen(store->key), + LIBSSH2_KNOWNHOST_TYPE_PLAIN | + LIBSSH2_KNOWNHOST_KEYENC_BASE64 | + (store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK), + NULL); + if(keycheck == LIBSSH2_KNOWNHOST_CHECK_MATCH) { + found = TRUE; + break; + } + else if(keycheck == LIBSSH2_KNOWNHOST_CHECK_NOTFOUND) + absent_type[type] = TRUE; + } } } } @@ -1487,12 +1509,9 @@ static CURLcode ssh_state_auth_pkey(struct Curl_easy *data, */ struct connectdata *conn = data->conn; const char *user = Curl_creds_user(conn->creds); - int rc = - libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, - user, - curlx_uztoui(strlen(user)), - sshc->pub_key, - sshc->priv_key, sshc->passphrase); + int rc = libssh2_userauth_publickey_fromfile_ex( + sshc->ssh_session, user, curlx_uztoui(strlen(user)), + sshc->pub_key, sshc->priv_key, sshc->passphrase ? sshc->passphrase : ""); if(rc == LIBSSH2_ERROR_EAGAIN) return CURLE_AGAIN; @@ -2555,6 +2574,7 @@ static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data, DEBUGASSERT(!sshc->kh); DEBUGASSERT(!sshc->ssh_agent); + curlx_safefree(sshc->passphrase); curlx_safefree(sshc->pub_key); curlx_safefree(sshc->priv_key); curlx_safefree(sshc->quote_path1); @@ -3219,14 +3239,14 @@ static CURLcode ssh_block_statemach(struct Curl_easy *data, fd_write = sock; /* wait for the socket to become ready */ (void)Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, - left_ms > 1000 ? 1000 : left_ms); + (!left_ms || left_ms > 1000) ? 1000 : left_ms); } } return result; } -static void myssh_easy_dtor(void *key, size_t klen, void *entry) +static void myssh_easy_dtor(const void *key, size_t klen, void *entry) { struct SSHPROTO *sshp = entry; (void)key; @@ -3237,7 +3257,7 @@ static void myssh_easy_dtor(void *key, size_t klen, void *entry) curlx_free(sshp); } -static void myssh_conn_dtor(void *key, size_t klen, void *entry) +static void myssh_conn_dtor(const void *key, size_t klen, void *entry) { struct ssh_conn *sshc = entry; (void)key; @@ -3600,7 +3620,7 @@ static CURLcode ssh_done(struct Curl_easy *data, CURLcode status) else result = status; - if(Curl_pgrsDone(data)) + if(Curl_pgrsDone(data, Curl_pgrs_now(data))) return CURLE_ABORTED_BY_CALLBACK; CURL_REQ_CLEAR_IO(data); diff --git a/lib/vssh/ssh.h b/lib/vssh/ssh.h index 52bc1399901a..b1071419fdf9 100644 --- a/lib/vssh/ssh.h +++ b/lib/vssh/ssh.h @@ -143,7 +143,7 @@ struct ssh_conn { const char *authlist; /* List of auth. methods, managed by libssh2 */ /* common */ - const char *passphrase; /* pass-phrase to use */ + char *passphrase; /* strdup'ed pass-phrase to use or NULL */ char *pub_key; /* strdup'ed public key file */ char *priv_key; /* strdup'ed private key file */ sshstate state; /* always use ssh.c:state() to change state! */ diff --git a/lib/vssh/vssh.c b/lib/vssh/vssh.c index 9834546987d9..1aa8effa922c 100644 --- a/lib/vssh/vssh.c +++ b/lib/vssh/vssh.c @@ -32,7 +32,7 @@ #include "escape.h" #include "select.h" /* for Curl_pollset_change() */ #include "url.h" /* for Curl_conn_meta_get() */ -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #ifdef CURLVERBOSE const char *Curl_ssh_statename(sshstate state) @@ -427,9 +427,14 @@ CURLcode Curl_ssh_setup_pkey(struct Curl_easy *data, struct ssh_conn *sshc) goto fail; } - sshc->passphrase = data->set.ssl.primary.key_passwd; - if(!sshc->passphrase) - sshc->passphrase = ""; + { + const char *keypasswd = CURL_EASY_STR(data, STRING_KEY_PASSWD); + if(keypasswd) { + sshc->passphrase = curlx_strdup(keypasswd); + if(!sshc->passphrase) + goto fail; + } + } if(sshc->pub_key) infof(data, "SSH: public key file '%s'", sshc->pub_key); diff --git a/lib/vtls/apple.c b/lib/vtls/apple.c index ce7f0c5e1cd2..356cfff9f565 100644 --- a/lib/vtls/apple.c +++ b/lib/vtls/apple.c @@ -50,18 +50,30 @@ #include -#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ - MAC_OS_X_VERSION_MAX_ALLOWED >= 101400) || \ - (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000) +#if (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000) || \ + (defined(__TV_OS_VERSION_MAX_ALLOWED) && \ + __TV_OS_VERSION_MAX_ALLOWED >= 120000) || \ + (defined(__WATCH_OS_VERSION_MAX_ALLOWED) && \ + __WATCH_OS_VERSION_MAX_ALLOWED >= 50000) || \ + (defined(__VISION_OS_VERSION_MAX_ALLOWED) && \ + __VISION_OS_VERSION_MAX_ALLOWED >= 10000) #define SUPPORTS_SecTrustEvaluateWithError 1 #endif #if defined(SUPPORTS_SecTrustEvaluateWithError) && \ - ((defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ - MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || \ - (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ - __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000)) + ((defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ + __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400) || \ + (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && \ + __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000) || \ + (defined(__TV_OS_VERSION_MIN_REQUIRED) && \ + __TV_OS_VERSION_MIN_REQUIRED >= 120000) || \ + (defined(__WATCH_OS_VERSION_MIN_REQUIRED) && \ + __WATCH_OS_VERSION_MIN_REQUIRED >= 50000) || \ + (defined(__VISION_OS_VERSION_MIN_REQUIRED) && \ + __VISION_OS_VERSION_MIN_REQUIRED >= 10000)) #define REQUIRES_SecTrustEvaluateWithError 1 #endif @@ -71,10 +83,16 @@ #undef SUPPORTS_SecTrustEvaluateWithError #endif -#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \ - MAC_OS_X_VERSION_MAX_ALLOWED >= 100900) || \ - (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ - __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) +#if (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && \ + __MAC_OS_X_VERSION_MAX_ALLOWED >= 100900) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && \ + __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || \ + (defined(__TV_OS_VERSION_MAX_ALLOWED) && \ + __TV_OS_VERSION_MAX_ALLOWED >= 90000) || \ + (defined(__WATCH_OS_VERSION_MAX_ALLOWED) && \ + __WATCH_OS_VERSION_MAX_ALLOWED >= 20000) || \ + (defined(__VISION_OS_VERSION_MAX_ALLOWED) && \ + __VISION_OS_VERSION_MAX_ALLOWED >= 10000) #define SUPPORTS_SecOCSP 1 #endif @@ -87,7 +105,7 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, const unsigned char *ocsp_buf, size_t ocsp_len) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; SecTrustRef trust = NULL; SecPolicyRef policy = NULL; @@ -127,8 +145,7 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, #if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - if(!ssl_config->no_revoke) { + if(!conn_config->no_revoke) { if(__builtin_available(macOS 10.9, iOS 7, tvOS 9, watchOS 2, *)) { /* Even without this set, validation seemingly-unavoidably fails * for certificates that trustd already knows to be revoked. @@ -144,7 +161,7 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, * It seems that applications using this policy are expected to PIN * their certificate public keys or verification fails. * This does not seem to be what we want here. */ - if(!ssl_config->revoke_best_effort) { + if(!conn_config->revoke_best_effort) { revocation_flags |= kSecRevocationRequirePositiveResponse; } #endif @@ -205,7 +222,10 @@ CURLcode Curl_vtls_apple_verify(struct Curl_cfilter *cf, #if defined(HAVE_BUILTIN_AVAILABLE) && defined(SUPPORTS_SecOCSP) if(__builtin_available(macOS 10.9, iOS 7, tvOS 9, watchOS 2, *)) { CFDataRef ocspdata = CFDataCreate(NULL, ocsp_buf, (CFIndex)ocsp_len); - + if(!ocspdata) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } status = SecTrustSetOCSPResponse(trust, ocspdata); CFRelease(ocspdata); if(status != noErr) { diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 9d17573dc5d1..7955dbadb670 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -52,7 +52,7 @@ #include "connect.h" /* for the connect timeout */ #include "progress.h" #include "curlx/strdup.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "vtls/x509asn1.h" /* Enable GnuTLS debugging by defining GTLSDEBUG */ @@ -323,7 +323,7 @@ static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) static CURLcode gnutls_set_ssl_version_min_max( struct Curl_easy *data, struct ssl_peer *peer, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, const char **prioritylist, bool tls13support) { @@ -446,8 +446,8 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, struct Curl_easy *data, gnutls_certificate_credentials_t creds) { - struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *config = Curl_ssl_cf_get_filter_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); bool creds_are_empty = TRUE; int rc; @@ -541,6 +541,13 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, if(creds_are_empty) infof(data, " no trust anchors configured"); +#ifdef USE_APPLE_SECTRUST + if(config->CRLfile && config->native_ca_store) { + failf(data, "gnutls: CRL file not supported with native CA store; " + "the platform verifier has no CRL attachment API"); + return CURLE_NOT_BUILT_IN; + } +#endif if(config->CRLfile) { /* set the CRL list file */ rc = gnutls_certificate_set_x509_crl_file(creds, config->CRLfile, @@ -563,9 +570,8 @@ static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, static bool gtls_shared_creds_expired(struct Curl_easy *data, const struct gtls_shared_creds *sc) { - const struct ssl_general_config *cfg = &data->set.general_ssl; timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &sc->time); - timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + timediff_t timeout_ms = data->set.ssl_ca_cache_timeout * (timediff_t)1000; if(timeout_ms < 0) return FALSE; @@ -576,7 +582,7 @@ static bool gtls_shared_creds_expired(struct Curl_easy *data, static bool gtls_shared_creds_different(struct Curl_cfilter *cf, const struct gtls_shared_creds *sc) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); if(!sc->CAfile || !conn_config->CAfile) return sc->CAfile != conn_config->CAfile; @@ -590,7 +596,7 @@ static struct gtls_shared_creds *gtls_get_cached_creds(struct Curl_cfilter *cf, if(data->multi) { shared_creds = Curl_hash_pick(&data->multi->proto_hash, - CURL_UNCONST(MPROTO_GTLS_X509_KEY), + MPROTO_GTLS_X509_KEY, CURL_CSTRLEN(MPROTO_GTLS_X509_KEY)); if(shared_creds && shared_creds->creds && !gtls_shared_creds_expired(data, shared_creds) && @@ -601,7 +607,8 @@ static struct gtls_shared_creds *gtls_get_cached_creds(struct Curl_cfilter *cf, return NULL; } -static void gtls_shared_creds_hash_free(void *key, size_t key_len, void *p) +static void gtls_shared_creds_hash_free(const void *key, + size_t key_len, void *p) { struct gtls_shared_creds *sc = p; DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_GTLS_X509_KEY)); @@ -615,7 +622,7 @@ static void gtls_set_cached_creds(struct Curl_cfilter *cf, struct Curl_easy *data, struct gtls_shared_creds *sc) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); DEBUGASSERT(sc); DEBUGASSERT(sc->creds); @@ -634,7 +641,7 @@ static void gtls_set_cached_creds(struct Curl_cfilter *cf, return; if(!Curl_hash_add2(&data->multi->proto_hash, - CURL_UNCONST(MPROTO_GTLS_X509_KEY), + MPROTO_GTLS_X509_KEY, CURL_CSTRLEN(MPROTO_GTLS_X509_KEY), sc, gtls_shared_creds_hash_free)) { Curl_gtls_shared_creds_free(&sc); /* down reference again */ @@ -646,8 +653,7 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, struct Curl_easy *data, struct gtls_ctx *gtls) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct gtls_shared_creds *cached_creds = NULL; bool cache_criteria_met; CURLcode result; @@ -656,11 +662,11 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, /* Consider the X509 store cacheable if it comes exclusively from a CAfile, or no source is provided and we are falling back to OpenSSL's built-in default. */ - cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && + cache_criteria_met = (data->set.ssl_ca_cache_timeout != 0) && conn_config->verifypeer && !conn_config->CApath && !conn_config->ca_info_blob && - !ssl_config->primary.CRLfile && + !conn_config->CRLfile && !conn_config->native_ca_store && !conn_config->clientcert; /* GnuTLS adds client cert to its credentials! */ @@ -834,7 +840,7 @@ static CURLcode gtls_set_priority(struct Curl_cfilter *cf, struct gtls_ctx *gtls, const char *priority) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct dynbuf buf; const char *err = NULL; CURLcode result = CURLE_OK; @@ -879,8 +885,8 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, size_t earlydata_max, struct gtls_ctx *gtls) { - struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *config = Curl_ssl_cf_get_filter_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); unsigned int init_flags; int rc; const char *prioritylist; @@ -975,11 +981,11 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, if(result) return result; } - if(ssl_config->primary.cert_type && - curl_strequal(ssl_config->primary.cert_type, "P12")) { + if(config->cert_type && + curl_strequal(config->cert_type, "P12")) { rc = gnutls_certificate_set_x509_simple_pkcs12_file( gtls->shared_creds->creds, config->clientcert, GNUTLS_X509_FMT_DER, - ssl_config->primary.key_passwd ? ssl_config->primary.key_passwd : ""); + config->key_passwd ? config->key_passwd : ""); if(rc != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 potentially-encrypted key or certificate " @@ -997,15 +1003,14 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf, rc = gnutls_certificate_set_x509_key_file2( gtls->shared_creds->creds, config->clientcert, - ssl_config->primary.key ? ssl_config->primary.key : - config->clientcert, - gnutls_do_file_type(ssl_config->primary.cert_type), - ssl_config->primary.key_passwd, + config->key ? config->key : config->clientcert, + gnutls_do_file_type(config->cert_type), + config->key_passwd, supported_key_encryption_algorithms); if(rc != GNUTLS_E_SUCCESS) { failf(data, "error reading X.509 %skey file: %s", - ssl_config->primary.key_passwd ? "potentially-encrypted " : "", + config->key_passwd ? "potentially-encrypted " : "", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } @@ -1078,7 +1083,7 @@ static CURLcode gtls_apply_session( struct Curl_ssl_session *scs, bool *pwas_setup) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; int rc; @@ -1097,7 +1102,7 @@ static CURLcode gtls_apply_session( else { infof(data, "SSL reusing session with ALPN '%s'", scs->alpn ? scs->alpn : "-"); - if(ssl_config->earlydata && scs->alpn && + if(conn_config->earlydata && scs->alpn && !cf->conn->bits.connect_only) { bool do_early_data = FALSE; if(sess_reuse_cb) { @@ -1127,7 +1132,7 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, void *ssl_user_data, Curl_gtls_init_session_reuse_cb *sess_reuse_cb) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_session *scs = NULL; gnutls_datum_t gtls_alpns[ALPN_ENTRIES_MAX]; size_t gtls_alpns_count = 0; @@ -1425,127 +1430,26 @@ static void gtls_infof_cert(struct Curl_easy *data, static CURLcode gtls_verify_ocsp_status(struct Curl_easy *data, gnutls_session_t session) { - gnutls_ocsp_resp_t ocsp_resp = NULL; gnutls_datum_t status_request; - gnutls_certificate_credentials_t creds = NULL; - gnutls_x509_trust_list_t tlist = NULL; - unsigned int verify_status = 0; - gnutls_ocsp_cert_status_t status = GNUTLS_OCSP_CERT_UNKNOWN; - gnutls_x509_crl_reason_t reason; - CURLcode result = CURLE_OK; int rc; + /* We use gnutls_certificate_verify_peers2() to verify the result + * which - according to GnuTLS documentation - already takes + * an OCSP stapling extension received into account. + * We therefore need only to check if we indeed received + * a stapled OCSP response here. */ rc = gnutls_ocsp_status_request_get(session, &status_request); if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { failf(data, "No OCSP response received"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; + return CURLE_SSL_INVALIDCERTSTATUS; } else if(rc < 0) { failf(data, "Invalid OCSP response received"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; - } - - rc = gnutls_ocsp_resp_init(&ocsp_resp); - if(rc < 0) { - failf(data, "Failed to initialize OCSP response object"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; - } - - rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; - } - - if(!gnutls_credentials_get(session, GNUTLS_CRD_CERTIFICATE, (void **)&creds)) - gnutls_certificate_get_trust_list(creds, &tlist); - if(!tlist) { - failf(data, "OCSP response signature verification failed"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; - } - rc = gnutls_ocsp_resp_verify(ocsp_resp, tlist, &verify_status, 0); - if(rc < 0 || verify_status) { - failf(data, "OCSP response signature verification failed"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto out; + return CURLE_SSL_INVALIDCERTSTATUS; } - (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, - &status, NULL, NULL, NULL, &reason); - - switch(status) { - case GNUTLS_OCSP_CERT_GOOD: - break; - - case GNUTLS_OCSP_CERT_REVOKED: { - const char *crl_reason; - - switch(reason) { - default: - case GNUTLS_X509_CRLREASON_UNSPECIFIED: - crl_reason = "unspecified reason"; - break; - - case GNUTLS_X509_CRLREASON_KEYCOMPROMISE: - crl_reason = "private key compromised"; - break; - - case GNUTLS_X509_CRLREASON_CACOMPROMISE: - crl_reason = "CA compromised"; - break; - - case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED: - crl_reason = "affiliation has changed"; - break; - - case GNUTLS_X509_CRLREASON_SUPERSEDED: - crl_reason = "certificate superseded"; - break; - - case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: - crl_reason = "operation has ceased"; - break; - - case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: - crl_reason = "certificate is on hold"; - break; - - case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: - crl_reason = "will be removed from delta CRL"; - break; - - case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: - crl_reason = "privilege withdrawn"; - break; - - case GNUTLS_X509_CRLREASON_AACOMPROMISE: - crl_reason = "AA compromised"; - break; - } - - failf(data, "Server certificate was revoked: %s", crl_reason); - break; - } - - default: - case GNUTLS_OCSP_CERT_UNKNOWN: - failf(data, "Server certificate status is unknown"); - break; - } - - result = (status != GNUTLS_OCSP_CERT_GOOD) ? - CURLE_SSL_INVALIDCERTSTATUS : CURLE_OK; - -out: - if(ocsp_resp) - gnutls_ocsp_resp_deinit(ocsp_resp); - return result; + return CURLE_OK; } struct gtls_cert_chain { @@ -1582,8 +1486,8 @@ static CURLcode gtls_chain_get_der(struct Curl_cfilter *cf, ssl_config->certverifyresult as one or more gnutls_certificate_status_t enumerated elements bitwise or'd. */ static CURLcode gtls_verify_cert(struct Curl_easy *data, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, + struct ssl_filter_config *config, + struct ssl_easy_config *ssl_config, gnutls_session_t session, struct Curl_cfilter *cf, struct ssl_peer *peer, @@ -1636,8 +1540,8 @@ static CURLcode gtls_verify_cert(struct Curl_easy *data, failf(data, "SSL certificate verification failed: %s. (CAfile: %s " "CRLfile: %s)", cause, config->CAfile ? config->CAfile : "none", - ssl_config->primary.CRLfile ? - ssl_config->primary.CRLfile : "none"); + config->CRLfile ? + config->CRLfile : "none"); return CURLE_PEER_FAILED_VERIFICATION; } @@ -1647,8 +1551,8 @@ static CURLcode gtls_verify_cert(struct Curl_easy *data, CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, struct Curl_easy *data, gnutls_session_t session, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, + struct ssl_filter_config *config, + struct ssl_easy_config *ssl_config, struct ssl_peer *peer, const char *pinned_key) { @@ -1694,8 +1598,8 @@ CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, goto out; for(i = 0; i < chain.num_certs; i++) { - const char *beg = (const char *)chain.certs[i].data; - const char *end = beg + chain.certs[i].size; + const uint8_t *beg = chain.certs[i].data; + const uint8_t *end = beg + chain.certs[i].size; result = Curl_extract_certinfo(data, (int)i, beg, end); if(result) @@ -1864,8 +1768,8 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, gnutls_session_t session) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); #ifndef CURL_DISABLE_PROXY const char *pinned_key = Curl_ssl_cf_is_proxy(cf) ? CURL_EASY_STR(data, STRING_SSL_PINNEDPUBLICKEY_PROXY) : @@ -2090,8 +1994,13 @@ static CURLcode gtls_send(struct Curl_cfilter *cf, nwritten = gnutls_record_send(backend->gtls.session, buf, remain); if(nwritten >= 0) { + if((size_t)nwritten > remain) { + DEBUGASSERT(0); + failf(data, "gnutls_record_send(len=%zu) returned %zd", + remain, nwritten); + return CURLE_SEND_ERROR; + } *pnwritten += (size_t)nwritten; - DEBUGASSERT((size_t)nwritten <= remain); buf = (char *)CURL_UNCONST(buf) + (size_t)nwritten; remain -= (size_t)nwritten; } diff --git a/lib/vtls/gtls.h b/lib/vtls/gtls.h index 0ad9a7e68c72..6ca39f13e3df 100644 --- a/lib/vtls/gtls.h +++ b/lib/vtls/gtls.h @@ -34,8 +34,8 @@ struct Curl_easy; struct Curl_cfilter; struct alpn_spec; -struct ssl_primary_config; -struct ssl_config_data; +struct ssl_filter_config; +struct ssl_easy_config; struct ssl_peer; struct ssl_connect_data; struct Curl_ssl_session; @@ -89,8 +89,8 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, CURLcode Curl_gtls_verifyserver(struct Curl_cfilter *cf, struct Curl_easy *data, gnutls_session_t session, - struct ssl_primary_config *config, - struct ssl_config_data *ssl_config, + struct ssl_filter_config *config, + struct ssl_easy_config *ssl_config, struct ssl_peer *peer, const char *pinned_key); diff --git a/lib/vtls/hostcheck.c b/lib/vtls/hostcheck.c index 0af540993ffd..ec36cb5ba50d 100644 --- a/lib/vtls/hostcheck.c +++ b/lib/vtls/hostcheck.c @@ -38,7 +38,7 @@ #include "vtls/hostcheck.h" /* check the two input strings with given length, but do not - assume they end in nul-bytes */ + assume they are null-terminated */ static bool pmatch(const char *hostname, size_t hostlen, const char *pattern, size_t patternlen) { @@ -66,6 +66,8 @@ static bool pmatch(const char *hostname, size_t hostlen, * Only match on "*" being used for the leftmost label, not "a*", "a*b" nor * "*b". * + * The pattern string does not necessarily need to be null-terminated. + * * Return TRUE on a match. FALSE if not. * * @unittest: 1397 @@ -84,16 +86,16 @@ static bool hostmatch(const char *hostname, DEBUGASSERT(hostlen); /* normalize pattern and hostname by stripping off trailing dots */ - if(hostname[hostlen - 1] == '.') + if(hostlen > 0 && hostname[hostlen - 1] == '.') hostlen--; - if(pattern[patternlen - 1] == '.') + if(patternlen > 0 && pattern[patternlen - 1] == '.') patternlen--; - if(strncmp(pattern, "*.", 2)) + if(patternlen > 2 && strncmp(pattern, "*.", 2)) return pmatch(hostname, hostlen, pattern, patternlen); /* detect host as IP address or starting with a dot and fail if so */ - else if(Curl_host_is_ipnum(hostname) || (hostname[0] == '.')) + else if(Curl_host_is_ipnum(hostname) || (hostlen > 0 && hostname[0] == '.')) return FALSE; /* We require at least 2 dots in the pattern to avoid too wide wildcard @@ -120,7 +122,7 @@ static bool hostmatch(const char *hostname, bool Curl_cert_hostcheck(const char *match, size_t matchlen, const char *hostname, size_t hostlen) { - if(match && *match && hostname && *hostname) + if(matchlen > 0 && hostlen > 0) return hostmatch(hostname, hostlen, match, matchlen); return FALSE; } diff --git a/lib/vtls/keylog.c b/lib/vtls/keylog.c index ac9649a41f78..d5bf560c32ea 100644 --- a/lib/vtls/keylog.c +++ b/lib/vtls/keylog.c @@ -28,7 +28,7 @@ defined(USE_RUSTLS) #include "escape.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" /* The fp for the open SSLKEYLOGFILE, or NULL if not open */ static FILE *keylog_file_fp; diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index dbee473d7dbb..c7cfc005da2c 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -182,7 +182,7 @@ static int mbedtls_bio_cf_read(void *bio, unsigned char *buf, size_t blen) static CURLcode mbed_set_ssl_version_min_max( struct Curl_easy *data, struct mbed_ssl_backend_data *backend, - struct ssl_primary_config *conn_config) + struct ssl_filter_config *conn_config) { mbedtls_ssl_protocol_version ver_min = #ifdef MBEDTLS_SSL_PROTO_TLS1_2 @@ -433,9 +433,8 @@ static void mbed_extract_certinfo(struct Curl_easy *data, result = Curl_ssl_init_certinfo(data, cert_count); for(i = 0, cur = crt; result == CURLE_OK && cur; ++i, cur = cur->next) { - const char *beg = (const char *)cur->raw.p; - const char *end = beg + cur->raw.len; - result = Curl_extract_certinfo(data, i, beg, end); + result = Curl_extract_certinfo(data, i, cur->raw.p, + cur->raw.p + cur->raw.len); } } @@ -443,7 +442,7 @@ static int mbed_verify_cb(void *ptr, mbedtls_x509_crt *crt, int depth, uint32_t *flags) { struct Curl_cfilter *cf = (struct Curl_cfilter *)ptr; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_easy *data = CF_DATA_CURRENT(cf); if(depth == 0) { @@ -480,7 +479,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ @@ -488,8 +487,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf, const bool verifypeer = conn_config->verifypeer; const char * const ssl_capath = conn_config->CApath; #ifdef MBEDTLS_PEM_PARSE_C - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - const char * const ssl_cert_type = ssl_config->primary.cert_type; + const char * const ssl_cert_type = conn_config->cert_type; #endif int ret = -1; char errorbuf[128]; @@ -580,11 +578,11 @@ static CURLcode mbed_load_clicert(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - char * const ssl_cert = ssl_config->primary.clientcert; - const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + char * const ssl_cert = conn_config->clientcert; + const struct curl_blob *ssl_cert_blob = conn_config->cert_blob; #ifdef MBEDTLS_PEM_PARSE_C - const char * const ssl_cert_type = ssl_config->primary.cert_type; + const char * const ssl_cert_type = conn_config->cert_type; #endif int ret = -1; char errorbuf[128]; @@ -659,18 +657,18 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int ret = -1; char errorbuf[128]; mbedtls_pk_init(&backend->pk); - if(ssl_config->primary.key || ssl_config->primary.key_blob) { - if(ssl_config->primary.key) { + if(conn_config->key || conn_config->key_blob) { + if(conn_config->key) { #ifdef MBEDTLS_FS_IO #if MBEDTLS_VERSION_NUMBER >= 0x04000000 - ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key, - ssl_config->primary.key_passwd); + ret = mbedtls_pk_parse_keyfile(&backend->pk, conn_config->key, + conn_config->key_passwd); if(ret == 0 && !(mbedtls_pk_can_do_psa(&backend->pk, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_ANY_HASH), @@ -680,8 +678,8 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, PSA_KEY_USAGE_SIGN_HASH))) ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; #else - ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key, - ssl_config->primary.key_passwd, + ret = mbedtls_pk_parse_keyfile(&backend->pk, conn_config->key, + conn_config->key_passwd, mbedtls_ctr_drbg_random, &rng.drbg); if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || @@ -692,7 +690,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, if(ret) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); failf(data, "mbedTLS: error reading private key %s: (-0x%04X) %s", - ssl_config->primary.key, (unsigned int)-ret, errorbuf); + conn_config->key, (unsigned int)-ret, errorbuf); return CURLE_SSL_CERTPROBLEM; } #else @@ -701,18 +699,25 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, #endif } else { - const struct curl_blob *ssl_key_blob = ssl_config->primary.key_blob; - const char *passwd = ssl_config->primary.key_passwd; + const struct curl_blob *ssl_key_blob = conn_config->key_blob; + const char *passwd = conn_config->key_passwd; + const bool is_der = conn_config->key_type && + curl_strequal(conn_config->key_type, "DER"); /* Unfortunately, mbedtls_pk_parse_key() requires the data to be null-terminated if the data is PEM encoded (even when provided the exact length). */ unsigned char *newblob = curlx_memdup0(ssl_key_blob->data, ssl_key_blob->len); + /* mbedtls_pk_parse_key() requires the terminator to be counted in the + length for PEM input, while the DER parsers require the exact + length: a DER key with the terminator counted in is rejected with + MBEDTLS_ERR_ASN1_LENGTH_MISMATCH. */ + size_t keylen = is_der ? ssl_key_blob->len : ssl_key_blob->len + 1; if(!newblob) return CURLE_OUT_OF_MEMORY; #if MBEDTLS_VERSION_NUMBER >= 0x04000000 - ret = mbedtls_pk_parse_key(&backend->pk, newblob, ssl_key_blob->len, + ret = mbedtls_pk_parse_key(&backend->pk, newblob, keylen, (const unsigned char *)passwd, passwd ? strlen(passwd) : 0); if(ret == 0 && @@ -724,7 +729,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf, PSA_KEY_USAGE_SIGN_HASH))) ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; #else - ret = mbedtls_pk_parse_key(&backend->pk, newblob, ssl_key_blob->len, + ret = mbedtls_pk_parse_key(&backend->pk, newblob, keylen, (const unsigned char *)passwd, passwd ? strlen(passwd) : 0, mbedtls_ctr_drbg_random, @@ -753,8 +758,8 @@ static CURLcode mbed_load_crl(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - const char * const ssl_crlfile = ssl_config->primary.CRLfile; + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + const char * const ssl_crlfile = conn_config->CRLfile; #ifdef MBEDTLS_X509_CRL_PARSE_C mbedtls_x509_crl_init(&backend->crl); @@ -826,8 +831,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct mbed_ssl_backend_data *backend = (struct mbed_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int ret; bool session_applied = FALSE; CURLcode result; @@ -983,7 +987,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, #endif ); - if(ssl_config->primary.key || ssl_config->primary.key_blob) { + if(conn_config->key || conn_config->key_blob) { mbedtls_ssl_conf_own_cert(&backend->config, &backend->clicert, &backend->pk); } @@ -1019,9 +1023,9 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf, #endif /* give application a chance to interfere with mbedTLS set up. */ - if(data->set.ssl.fsslctx) { - result = (*data->set.ssl.fsslctx)(data, &backend->config, - data->set.ssl.fsslctxp); + if(data->set.ssl_fsslctx) { + result = (*data->set.ssl_fsslctx)(data, &backend->config, + data->set.ssl_fsslctxp); if(result) failf(data, "error signaled by SSL ctx callback"); } @@ -1033,7 +1037,7 @@ static CURLcode mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result; if((conn_config->version == CURL_SSLVERSION_SSLv2) || @@ -1276,6 +1280,9 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, int nwritten; DEBUGASSERT(backend); +#ifdef MBEDTLS_SSL_PROTO_TLS1_3 +do_send: +#endif *pnwritten = 0; connssl->io_need = CURL_SSL_IO_NEED_NONE; /* mbedTLS is picky when a mbedtls_ssl_write() was previously blocked. @@ -1283,7 +1290,12 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, * 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); + if(backend->send_blocked_len > len) { + DEBUGASSERT(0); + failf(data, "re-send data shorter than previously"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> previously blocked " "on %zu bytes", len, backend->send_blocked_len); len = backend->send_blocked_len; @@ -1301,6 +1313,12 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, switch(nwritten) { #ifdef MBEDTLS_SSL_PROTO_TLS1_3 case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET: + result = mbed_new_session(cf, data); + /* This return code is not blocking. Having treated the new + * ticket, resume sending until we get a "real" result. */ + if(!result) + goto do_send; + break; #endif case MBEDTLS_ERR_SSL_WANT_READ: connssl->io_need = CURL_SSL_IO_NEED_RECV; @@ -1320,6 +1338,7 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data, } } +out: CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> %d, %zu", len, (int)result, *pnwritten); return result; @@ -1449,6 +1468,9 @@ static CURLcode mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, int nread; DEBUGASSERT(backend); +#ifdef MBEDTLS_SSL_SESSION_TICKETS +do_read: +#endif *pnread = 0; connssl->io_need = CURL_SSL_IO_NEED_NONE; @@ -1462,8 +1484,12 @@ static CURLcode mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data, switch(nread) { #ifdef MBEDTLS_SSL_SESSION_TICKETS case MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET: - mbed_new_session(cf, data); - FALLTHROUGH(); + result = mbed_new_session(cf, data); + /* This is not blocking anything. We can try again until a + * "real" result comes. */ + if(!result) + goto do_read; + break; #endif case MBEDTLS_ERR_SSL_WANT_READ: connssl->io_need = CURL_SSL_IO_NEED_RECV; diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 17ed1485c56e..bb4584bf6403 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -172,6 +172,14 @@ typedef unsigned long sslerr_t; #endif #define ossl_valsize_t numcert_t +#if OPENSSL_VERSION_NUMBER < 0x40100000L +static size_t ASN1_STRING_get_length(const ASN1_STRING *str) +{ + const int length = ASN1_STRING_length(str); + return length >= 0 ? (size_t)length : 0; +} +#endif + static CURLcode push_certinfo(struct Curl_easy *data, BIO *mem, const char *label, int num) WARN_UNUSED_RESULT; @@ -398,11 +406,9 @@ static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) result = CURLE_OUT_OF_MEMORY; for(i = 0; !result && (i < (int)numcerts); i++) { - ASN1_INTEGER *num; - const unsigned char *numdata; X509 *x = sk_X509_value(sk, (ossl_valsize_t)i); EVP_PKEY *pubkey = NULL; - int j; + size_t j; const ASN1_BIT_STRING *psig = NULL; X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE); @@ -420,12 +426,15 @@ static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) if(result) break; - num = X509_get_serialNumber(x); - if(ASN1_STRING_type(num) == V_ASN1_NEG_INTEGER) - BIO_puts(mem, "-"); - numdata = ASN1_STRING_get0_data(num); - for(j = 0; j < ASN1_STRING_length(num); j++) - BIO_printf(mem, "%02x", numdata[j]); + { + ASN1_INTEGER *num = X509_get_serialNumber(x); + const unsigned char *numdata = ASN1_STRING_get0_data(num); + const size_t numlen = ASN1_STRING_get_length(num); + if(ASN1_STRING_type(num) == V_ASN1_NEG_INTEGER) + BIO_puts(mem, "-"); + for(j = 0; j < numlen; j++) + BIO_printf(mem, "%02x", numdata[j]); + } result = push_certinfo(data, mem, "Serial Number", i); if(result) break; @@ -495,7 +504,8 @@ static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) if(!result && psig) { const unsigned char *psigdata = ASN1_STRING_get0_data(psig); - for(j = 0; j < ASN1_STRING_length(psig); j++) + const size_t psiglen = ASN1_STRING_get_length(psig); + for(j = 0; j < psiglen; j++) BIO_printf(mem, "%02x:", psigdata[j]); result = push_certinfo(data, mem, "Signature", i); } @@ -1026,13 +1036,9 @@ static int enginecheck(struct Curl_easy *data, /* Implicitly use pkcs11 engine if none was provided and the * key_file is a PKCS#11 URI */ - if(!data->state.engine) { - if(is_pkcs11_uri(key_file)) { - if(ossl_set_engine(data, "pkcs11") != CURLE_OK) { - return 0; - } - } - } + if(!data->state.engine && is_pkcs11_uri(key_file) && + ossl_set_engine(data, "pkcs11") != CURLE_OK) + return 0; if(data->state.engine) { UI_METHOD *ui_method = UI_create_method("curl user interface"); @@ -1081,12 +1087,9 @@ static int providercheck(struct Curl_easy *data, char error_buffer[256]; /* Implicitly use pkcs11 provider if none was provided and the * key_file is a PKCS#11 URI */ - if(!data->state.provider_loaded) { - if(is_pkcs11_uri(key_file)) { - if(ossl_set_provider(data, "pkcs11") != CURLE_OK) { - return 0; - } - } + if(!data->state.provider_loaded && is_pkcs11_uri(key_file) && + ossl_set_provider(data, "pkcs11") != CURLE_OK) { + return 0; } if(data->state.provider_loaded) { @@ -1168,13 +1171,9 @@ static int engineload(struct Curl_easy *data, char error_buffer[256]; /* Implicitly use pkcs11 engine if none was provided and the * cert_file is a PKCS#11 URI */ - if(!data->state.engine) { - if(is_pkcs11_uri(cert_file)) { - if(ossl_set_engine(data, "pkcs11") != CURLE_OK) { - return 0; - } - } - } + if(!data->state.engine && is_pkcs11_uri(cert_file) && + ossl_set_engine(data, "pkcs11") != CURLE_OK) + return 0; if(data->state.engine) { static const char cmd_name[] = "LOAD_CERT_CTRL"; @@ -1237,13 +1236,9 @@ static int providerload(struct Curl_easy *data, char error_buffer[256]; /* Implicitly use pkcs11 provider if none was provided and the * cert_file is a PKCS#11 URI */ - if(!data->state.provider_loaded) { - if(is_pkcs11_uri(cert_file)) { - if(ossl_set_provider(data, "pkcs11") != CURLE_OK) { - return 0; - } - } - } + if(!data->state.provider_loaded && is_pkcs11_uri(cert_file) && + ossl_set_provider(data, "pkcs11") != CURLE_OK) + return 0; if(data->state.provider_loaded) { /* Load the certificate from the provider */ @@ -2100,7 +2095,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, if(check->type == target) { /* get data and length */ const char *altptr = (const char *)ASN1_STRING_get0_data(check->d.ia5); - size_t altlen = (size_t)ASN1_STRING_length(check->d.ia5); + size_t altlen = ASN1_STRING_get_length(check->d.ia5); switch(target) { case GEN_DNS: /* name/pattern comparison */ @@ -2146,7 +2141,7 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, distinguished one to get the most significant one. */ int i = -1; unsigned char *cn = NULL; - int cnlen = 0; + size_t cnlen = 0; bool free_cn = FALSE; /* The following is done because of a bug in 0.9.6b */ @@ -2171,17 +2166,21 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, conditional in the future when OpenSSL has been fixed. */ if(tmp) { if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { - cnlen = ASN1_STRING_length(tmp); + cnlen = ASN1_STRING_get_length(tmp); cn = (unsigned char *)CURL_UNCONST(ASN1_STRING_get0_data(tmp)); } else { /* not a UTF8 name */ - cnlen = ASN1_STRING_to_UTF8(&cn, tmp); + const int utf8len = ASN1_STRING_to_UTF8(&cn, tmp); + if(utf8len < 0) + cnlen = 0; + else + cnlen = (size_t)utf8len; free_cn = TRUE; } - if((cnlen <= 0) || !cn) + if((cnlen == 0) || !cn) result = CURLE_OUT_OF_MEMORY; - else if((size_t)cnlen != strlen((char *)cn)) { + else if(memchr(cn, '\0', cnlen)) { /* there was a null-terminator before the end of string, this cannot match and we return failure! */ failf(data, "SSL: illegal cert name field"); @@ -2199,12 +2198,13 @@ static CURLcode ossl_verifyhost(struct Curl_easy *data, } else if(!Curl_cert_hostcheck((const char *)cn, cnlen, peer->origin->hostname, hostlen)) { - failf(data, "SSL: certificate subject name '%s' does not match " - "target hostname '%s'", cn, peer->origin->user_hostname); + failf(data, "SSL: certificate subject name '%.*s' does not match " + "target hostname '%s'", (int)cnlen, cn, + peer->origin->user_hostname); result = CURLE_PEER_FAILED_VERIFICATION; } else { - infof(data, " common name: %s (matched)", cn); + infof(data, " common name: %.*s (matched)", (int)cnlen, cn); } if(free_cn) OPENSSL_free(cn); @@ -2555,7 +2555,7 @@ static CURLcode ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx, unsigned int ssl_version_min) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); /* first, TLS min version... */ long curl_ssl_version_min = (long)ssl_version_min; long curl_ssl_version_max; @@ -2988,7 +2988,7 @@ static CURLcode ossl_load_trust_anchors(struct Curl_cfilter *cf, struct ossl_ctx *octx, X509_STORE *store) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ @@ -3098,11 +3098,10 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, struct ossl_ctx *octx, X509_STORE *store) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; X509_LOOKUP *lookup = NULL; - const char * const ssl_crlfile = ssl_config->primary.CRLfile; + const char * const ssl_crlfile = conn_config->CRLfile; unsigned long x509flags = 0; CURL_TRC_CF(data, cf, "configuring OpenSSL's x509 trust store"); @@ -3120,6 +3119,13 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, return result; /* Does not make sense to load a CRL file without peer verification */ +#ifdef USE_APPLE_SECTRUST + if(ssl_crlfile && conn_config->native_ca_store) { + failf(data, "openssl: CRL file not supported with native CA store; " + "the platform verifier has no CRL attachment API"); + return CURLE_NOT_BUILT_IN; + } +#endif if(ssl_crlfile) { /* tell OpenSSL where to find CRL file that is used to check certificate * revocation */ @@ -3141,7 +3147,7 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, */ x509flags |= X509_V_FLAG_TRUSTED_FIRST; - if(!ssl_config->no_partialchain && !ssl_crlfile) { + if(!conn_config->no_partialchain && !ssl_crlfile) { /* Have intermediate certificates in the trust store be treated as trust-anchors, in the same way as self-signed root CA certificates are. This allows users to verify servers using the intermediate cert only, @@ -3161,13 +3167,14 @@ static CURLcode ossl_populate_x509_store(struct Curl_cfilter *cf, struct ossl_x509_share { char *CAfile; /* CAfile path used to generate X509 store */ + char *CApath; /* CApath path used to generate X509 store */ X509_STORE *store; /* cached X509 store or NULL if none */ struct curltime time; /* when the cached store was created */ BIT(store_is_empty); /* no certs/paths/blobs are in the store */ BIT(no_partialchain); /* keep partial chain state */ }; -static void oss_x509_share_free(void *key, size_t key_len, void *p) +static void oss_x509_share_free(const void *key, size_t key_len, void *p) { struct ossl_x509_share *share = p; DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)); @@ -3178,18 +3185,18 @@ static void oss_x509_share_free(void *key, size_t key_len, void *p) X509_STORE_free(share->store); } curlx_free(share->CAfile); + curlx_free(share->CApath); curlx_free(share); } static bool ossl_cached_x509_store_expired(struct Curl_easy *data, const struct ossl_x509_share *mb) { - const struct ssl_general_config *cfg = &data->set.general_ssl; - if(cfg->ca_cache_timeout < 0) + if(data->set.ssl_ca_cache_timeout < 0) return FALSE; else { timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &mb->time); - timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + timediff_t timeout_ms = data->set.ssl_ca_cache_timeout * (timediff_t)1000; return elapsed_ms >= timeout_ms; } @@ -3199,14 +3206,25 @@ static bool ossl_cached_x509_store_different(struct Curl_cfilter *cf, const struct Curl_easy *data, const struct ossl_x509_share *mb) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = - Curl_ssl_cf_get_config(cf, CURL_UNCONST(data)); - if(mb->no_partialchain != ssl_config->no_partialchain) + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + (void)data; + if(mb->no_partialchain != conn_config->no_partialchain) + return TRUE; + if(!mb->CAfile || !conn_config->CAfile) { + if(mb->CAfile != conn_config->CAfile) + return TRUE; + } + else if(strcmp(mb->CAfile, conn_config->CAfile)) return TRUE; - if(!mb->CAfile || !conn_config->CAfile) - return mb->CAfile != conn_config->CAfile; - return strcmp(mb->CAfile, conn_config->CAfile); + + if(!mb->CApath || !conn_config->CApath) { + if(mb->CApath != conn_config->CApath) + return TRUE; + } + else if(strcmp(mb->CApath, conn_config->CApath)) + return TRUE; + + return FALSE; } static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, @@ -3220,7 +3238,7 @@ static X509_STORE *ossl_get_cached_x509_store(struct Curl_cfilter *cf, DEBUGASSERT(multi); *pempty = TRUE; share = multi ? Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_OSSL_X509_KEY), + MPROTO_OSSL_X509_KEY, CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)) : NULL; if(share && share->store && !ossl_cached_x509_store_expired(data, share) && @@ -3237,7 +3255,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, X509_STORE *store, bool is_empty) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_multi *multi = data->multi; struct ossl_x509_share *share; @@ -3245,7 +3263,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, if(!multi) return; share = Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_OSSL_X509_KEY), + MPROTO_OSSL_X509_KEY, CURL_CSTRLEN(MPROTO_OSSL_X509_KEY)); if(!share) { @@ -3253,7 +3271,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, if(!share) return; if(!Curl_hash_add2(&multi->proto_hash, - CURL_UNCONST(MPROTO_OSSL_X509_KEY), + MPROTO_OSSL_X509_KEY, CURL_CSTRLEN(MPROTO_OSSL_X509_KEY), share, oss_x509_share_free)) { curlx_free(share); @@ -3263,8 +3281,7 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, if(X509_STORE_up_ref(store)) { char *CAfile = NULL; - struct ssl_config_data *ssl_config = - Curl_ssl_cf_get_config(cf, CURL_UNCONST(data)); + char *CApath = NULL; if(conn_config->CAfile) { CAfile = curlx_strdup(conn_config->CAfile); @@ -3274,16 +3291,27 @@ static void ossl_set_cached_x509_store(struct Curl_cfilter *cf, } } + if(conn_config->CApath) { + CApath = curlx_strdup(conn_config->CApath); + if(!CApath) { + curlx_free(CAfile); + X509_STORE_free(store); + return; + } + } + if(share->store) { X509_STORE_free(share->store); curlx_free(share->CAfile); + curlx_free(share->CApath); } share->time = *Curl_pgrs_now(data); share->store = store; share->store_is_empty = is_empty; share->CAfile = CAfile; - share->no_partialchain = ssl_config->no_partialchain; + share->CApath = CApath; + share->no_partialchain = conn_config->no_partialchain; } } @@ -3291,20 +3319,18 @@ CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, struct ossl_ctx *octx) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; X509_STORE *cached_store; bool cache_criteria_met, is_empty; - /* Consider the X509 store cacheable if it comes exclusively from a CAfile, + /* Consider the X509 store cacheable if it comes from CAfile/CApath, or no source is provided and we are falling back to OpenSSL's built-in default. */ - cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && + cache_criteria_met = (data->set.ssl_ca_cache_timeout != 0) && conn_config->verifypeer && - !conn_config->CApath && !conn_config->ca_info_blob && - !ssl_config->primary.CRLfile && + !conn_config->CRLfile && !conn_config->native_ca_store; ERR_set_mark(); @@ -3336,8 +3362,7 @@ static bool ossl_apply_session( Curl_ossl_init_session_reuse_cb *sess_reuse_cb, struct Curl_ssl_session *scs) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct ssl_primary_config *conn_cfg = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_cfg = Curl_ssl_cf_get_filter_config(cf); const unsigned char *der_sessionid = scs->sdata; size_t der_sessionid_size = scs->sdata_len; SSL_SESSION *ssl_session = NULL; @@ -3376,7 +3401,7 @@ static bool ossl_apply_session( infof(data, "SSL verify result: %lx", (unsigned long)SSL_get_verify_result(octx->ssl)); #ifdef HAVE_OPENSSL_EARLYDATA - if(ssl_config->earlydata && scs->alpn && + if(conn_cfg->earlydata && scs->alpn && SSL_SESSION_get_max_early_data(ssl_session) && !cf->conn->bits.connect_only && (SSL_version(octx->ssl) == TLS1_3_VERSION)) { @@ -3391,7 +3416,7 @@ static bool ossl_apply_session( } #else (void)alpns; - (void)ssl_config; + (void)conn_cfg; (void)sess_reuse_cb; #endif } @@ -3412,7 +3437,7 @@ static CURLcode ossl_init_session_and_alpns( const struct alpn_spec *alpns_requested, Curl_ossl_init_session_reuse_cb *sess_reuse_cb) { - struct ssl_primary_config *conn_cfg = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_cfg = Curl_ssl_cf_get_filter_config(cf); struct alpn_spec alpns; CURLcode result; @@ -3461,28 +3486,18 @@ static CURLcode ossl_init_session_and_alpns( } #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) || - CURL_EASY_STR(data, STRING_ECH_CONFIG)) - return FALSE; - return TRUE; -} - static CURLcode ossl_init_ech(struct ossl_ctx *octx, struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer) { - const char *outername = CURL_EASY_STR(data, STRING_ECH_PUBLIC); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int trying_ech_now = 0; - if(!CURLECH_ENABLED(data)) + if(!Curl_ssl_ech_enabled(cf)) return CURLE_OK; - if(data->set.tls_ech == CURLECH_GREASE) { + if(conn_config->ech == CURLECH_GREASE) { infof(data, "ECH: will GREASE ClientHello"); #ifdef HAVE_BORINGSSL_LIKE SSL_set_enable_ech_grease(octx->ssl, 1); @@ -3490,28 +3505,27 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE); #endif } - else if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + else if(conn_config->ech && conn_config->ech_config) { #ifdef HAVE_BORINGSSL_LIKE - /* have to do base64 decode here for BoringSSL */ - const char *b64 = CURL_EASY_STR(data, STRING_ECH_CONFIG); uint8_t *ech_config; size_t ech_config_len = 0; CURLcode result; - if(!b64) { + if(!conn_config->ech_config[0]) { infof(data, "ECH: ECHConfig from command line empty"); return CURLE_SSL_CONNECT_ERROR; } - ech_config_len = 2 * strlen(b64); - result = curlx_base64_decode(b64, &ech_config, &ech_config_len); + ech_config_len = 2 * strlen(conn_config->ech_config); + result = curlx_base64_decode(conn_config->ech_config, + &ech_config, &ech_config_len); if(result || !ech_config) { infof(data, "ECH: cannot base64 decode ECHConfig from command line"); - if(data->set.tls_ech == CURLECH_HARD) + if(conn_config->ech == CURLECH_HARD) return result; } if(SSL_set1_ech_config_list(octx->ssl, ech_config, ech_config_len) != 1) { infof(data, "ECH: SSL_ECH_set1_ech_config_list failed"); - if(data->set.tls_ech == CURLECH_HARD) { + if(conn_config->ech == CURLECH_HARD) { curlx_free(ech_config); return CURLE_SSL_CONNECT_ERROR; } @@ -3519,18 +3533,17 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, curlx_free(ech_config); trying_ech_now = 1; #else - const char *ech_config = CURL_EASY_STR(data, STRING_ECH_CONFIG); size_t ech_config_len = 0; - if(!ech_config) { + if(!conn_config->ech_config[0]) { infof(data, "ECH: ECHConfig from command line empty"); return CURLE_SSL_CONNECT_ERROR; } - ech_config_len = strlen(ech_config); + ech_config_len = strlen(conn_config->ech_config); if(SSL_set1_ech_config_list(octx->ssl, - (const uint8_t *)ech_config, + (const uint8_t *)conn_config->ech_config, ech_config_len) != 1) { infof(data, "ECH: SSL_ECH_set1_ech_config_list failed"); - if(data->set.tls_ech == CURLECH_HARD) + if(conn_config->ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } else @@ -3542,14 +3555,14 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, const struct Curl_https_rrinfo *rinfo = Curl_conn_dns_get_https(data, cf->sockindex, peer->origin); - if(rinfo && rinfo->echconfiglist) { + if(Curl_httpsrr_is_for_peer(peer->origin, rinfo) && rinfo->echconfiglist) { const unsigned char *ecl = rinfo->echconfiglist; size_t elen = rinfo->echconfiglist_len; 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) + if(conn_config->ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } else { @@ -3559,23 +3572,25 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, } else { infof(data, "ECH: requested but no ECHConfig available"); - if(data->set.tls_ech == CURLECH_HARD) + if(conn_config->ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } } #ifdef HAVE_BORINGSSL_LIKE (void)peer; - if(trying_ech_now && outername) { + if(trying_ech_now && conn_config->ech_public) { infof(data, "ECH: setting public_name not supported with BoringSSL"); return CURLE_SSL_CONNECT_ERROR; } #else - if(trying_ech_now && outername) { + if(trying_ech_now && conn_config->ech_public) { int ret; infof(data, "ECH: inner: '%s', outer: '%s'", - peer->origin->hostname ? peer->origin->hostname : "NULL", outername); + peer->origin->hostname ? peer->origin->hostname : "NULL", + conn_config->ech_public); ret = SSL_ech_set1_server_names(octx->ssl, - peer->origin->hostname, outername, + peer->origin->hostname, + conn_config->ech_public, 0 /* do send outer */); if(ret != 1) { infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", ret); @@ -3591,12 +3606,6 @@ static CURLcode ossl_init_ech(struct ossl_ctx *octx, return CURLE_OK; } -#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, @@ -3619,7 +3628,7 @@ static CURLcode ossl_init_ssl(struct ossl_ctx *octx, SSL_set_app_data(octx->ssl, ssl_user_data); #ifndef OPENSSL_NO_OCSP - if(Curl_ssl_cf_get_primary_config(cf)->verifystatus) + if(Curl_ssl_cf_get_filter_config(cf)->verifystatus) SSL_set_tlsext_status_type(octx->ssl, TLSEXT_STATUSTYPE_ocsp); #endif @@ -3650,7 +3659,7 @@ static CURLcode ossl_init_method(struct Curl_cfilter *cf, const SSL_METHOD **pmethod, unsigned int *pssl_version_min) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); *pmethod = NULL; *pssl_version_min = conn_config->version; @@ -3712,11 +3721,11 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, const char *ciphers; const SSL_METHOD *req_method = NULL; ctx_option_t ctx_options = 0; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - char * const ssl_cert = ssl_config->primary.clientcert; - const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; - const char * const ssl_cert_type = ssl_config->primary.cert_type; + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); + char * const ssl_cert = conn_config->clientcert; + const struct curl_blob *ssl_cert_blob = conn_config->cert_blob; + const char * const ssl_cert_type = conn_config->cert_type; unsigned int ssl_version_min; char error_buffer[256]; @@ -3808,7 +3817,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, /* unless the user explicitly asks to allow the protocol vulnerability we use the workaround */ - if(!ssl_config->enable_beast) + if(!conn_config->enable_beast) ctx_options &= ~(ctx_option_t)SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; DEBUGASSERT(ssl_version_min != CURL_SSLVERSION_DEFAULT); @@ -3887,9 +3896,9 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, if(ssl_cert || ssl_cert_blob || ssl_cert_type) { result = client_cert(data, octx->ssl_ctx, ssl_cert, ssl_cert_blob, ssl_cert_type, - ssl_config->primary.key, ssl_config->primary.key_blob, - ssl_config->primary.key_type, - ssl_config->primary.key_passwd); + conn_config->key, conn_config->key_blob, + conn_config->key_type, + conn_config->key_passwd); if(result) /* failf() is already done in client_cert() */ return result; @@ -3955,7 +3964,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, } /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { + if(data->set.ssl_fsslctx) { struct Curl_mapi_guard guard; /* When a user callback is installed to modify the SSL_CTX, * we need to do the full initialization before calling it. @@ -3967,8 +3976,8 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, octx->x509_store_setup = TRUE; } CURL_CBAPI_START(&guard, data, easy_fsslctx); - result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx, - data->set.ssl.fsslctxp); + result = (*data->set.ssl_fsslctx)(data, octx->ssl_ctx, + data->set.ssl_fsslctxp); CURL_CBAPI_END(&guard); if(result) { failf(data, "error signaled by SSL ctx callback"); @@ -4077,7 +4086,8 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, #ifdef HAVE_SSL_SET1_ECH_CONFIG_LIST /* If we have retry configs, then trace those out */ -static int ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL *ssl, +static int ossl_trace_ech_retry_configs(struct Curl_cfilter *cf, + struct Curl_easy *data, SSL *ssl, int reason) { CURLcode result = CURLE_OK; @@ -4097,7 +4107,7 @@ static int ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL *ssl, NOVERBOSE((void)reason); /* nothing to trace if not doing ECH */ - if(!CURLECH_ENABLED(data)) + if(!Curl_ssl_ech_enabled(cf)) return rv; #ifndef HAVE_BORINGSSL_LIKE rv = SSL_ech_get1_retry_config(ssl, &rcs, &rcl); @@ -4143,13 +4153,13 @@ static int ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL *ssl, static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { - int err; struct ssl_connect_data *connssl = cf->ctx; struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); + int err; + DEBUGASSERT(connssl->connecting_state == ssl_connect_2); DEBUGASSERT(octx); - connssl->io_need = CURL_SSL_IO_NEED_NONE; ERR_clear_error(); @@ -4262,10 +4272,10 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, #endif /* HAVE_BORINGSSL_LIKE */ /* trace retry_configs if we got some */ - ossl_trace_ech_retry_configs(data, octx->ssl, reason); + ossl_trace_ech_retry_configs(cf, data, octx->ssl, reason); result = CURLE_ECH_REQUIRED; - failf(data, "ECH required: %s", + failf(data, "ECH: rejected, %s", ossl_strerror(errdetail, error_buffer, sizeof(error_buffer))); } #endif @@ -4301,7 +4311,9 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, Curl_ossl_report_handshake(data, octx); #if defined(HAVE_SSL_SET1_ECH_CONFIG_LIST) && !defined(HAVE_BORINGSSL_LIKE) - if(CURLECH_ENABLED(data)) { + if(Curl_ssl_ech_enabled(cf)) { + struct ssl_filter_config *conn_config = + Curl_ssl_cf_get_filter_config(cf); char *inner = NULL, *outer = NULL; int rv; VERBOSE(const char *status); @@ -4333,8 +4345,6 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, 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.origin->hostname)) { VERBOSE(status = "bad name (tolerated without peer verification)"); @@ -4349,7 +4359,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, VERBOSE(status = "unexpected status"); infof(data, "ECH: unexpected status %d", rv); } - infof(data, "ECH: result: status is %s, inner is %s, outer is %s", + infof(data, "ECH: result '%s' (inner=%s, outer=%s)", (status ? status : "NULL"), (inner ? inner : "NULL"), (outer ? outer : "NULL")); @@ -4357,9 +4367,9 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, OPENSSL_free(outer); if(rv == SSL_ECH_STATUS_GREASE_ECH) { /* trace retry_configs if we got some */ - ossl_trace_ech_retry_configs(data, octx->ssl, 0); + ossl_trace_ech_retry_configs(cf, data, octx->ssl, 0); } - if(rv != SSL_ECH_STATUS_SUCCESS && (data->set.tls_ech == CURLECH_HARD)) { + if(rv != SSL_ECH_STATUS_SUCCESS && (conn_config->ech == CURLECH_HARD)) { infof(data, "ECH: ech-hard failed"); return CURLE_SSL_CONNECT_ERROR; } @@ -4520,7 +4530,7 @@ static CURLcode ossl_check_issuer(struct Curl_cfilter *cf, struct Curl_easy *data, X509 *server_cert) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); X509 *issuer = NULL; BIO *fp = NULL; char err_buf[256] = ""; @@ -4709,7 +4719,7 @@ static CURLcode ossl_apple_verify(struct Curl_cfilter *cf, struct ossl_ctx *octx, struct ssl_peer *peer) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct ossl_certs_ctx chain; CURLcode result; @@ -4775,8 +4785,8 @@ CURLcode Curl_ossl_check_peer_cert(struct Curl_cfilter *cf, struct ssl_peer *peer) { struct connectdata *conn = cf->conn; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; long ossl_verify; X509 *server_cert; @@ -5013,7 +5023,7 @@ static CURLcode ossl_connect(struct Curl_cfilter *cf, connssl->io_need = CURL_SSL_IO_NEED_NONE; if(connssl->connecting_state == ssl_connect_1) { - if(Curl_ossl_need_httpsrr(data) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, connssl->peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR, delaying connect"); @@ -5123,8 +5133,14 @@ static CURLcode ossl_send(struct Curl_cfilter *cf, octx->blocked_ssl_write_len = 0; nwritten = SSL_write(octx->ssl, mem, memlen); - if(nwritten > 0) + if(nwritten > 0) { + if(nwritten > memlen) { + failf(data, "SSL_write(len=%d) returned %d", memlen, nwritten); + result = CURLE_SEND_ERROR; + goto out; + } *pnwritten = (size_t)nwritten; + } else { err = SSL_get_error(octx->ssl, nwritten); diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h index dfac78c21e57..f1e914cb498c 100644 --- a/lib/vtls/openssl.h +++ b/lib/vtls/openssl.h @@ -159,9 +159,6 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, 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 diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 9c0b0d7be3b3..72edac9e730d 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -29,7 +29,7 @@ #include -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/strerr.h" #include "urldata.h" #include "curl_trc.h" @@ -226,7 +226,10 @@ static CURLcode cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data, plainlen - *pnread, &n); if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) { + /* A TLS message that carried no plaintext. Break out of the loop + * to prevent a server from keeping us here forever. */ backend->data_in_pending = FALSE; + break; } else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) { failf(data, "rustls: peer closed TCP connection " @@ -525,8 +528,9 @@ static void cr_keylog_log_cb(struct rustls_str label, } static CURLcode -init_config_builder(struct Curl_easy *data, - const struct ssl_primary_config *conn_config, +init_config_builder(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_filter_config *conn_config, struct rustls_client_config_builder **config_builder) { const struct rustls_supported_ciphersuite **cipher_suites = NULL; @@ -580,11 +584,13 @@ init_config_builder(struct Curl_easy *data, } #ifdef USE_ECH - if(CURLECH_ENABLED(data)) { + if(Curl_ssl_ech_enabled(cf)) { tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3; tls_versions_len = 1; infof(data, "rustls: ECH enabled, forcing TLSv1.3"); } +#else + (void)cf; #endif /* USE_ECH */ cipher_suites = curlx_malloc(sizeof(*cipher_suites) * cipher_suites_len); @@ -676,7 +682,7 @@ init_config_builder_alpn(struct Curl_easy *data, static CURLcode init_config_builder_verifier_crl( struct Curl_easy *data, - const struct ssl_primary_config *conn_config, + const struct ssl_filter_config *conn_config, struct rustls_web_pki_server_cert_verifier_builder *builder) { CURLcode result = CURLE_OK; @@ -708,7 +714,7 @@ static CURLcode init_config_builder_verifier_crl( static CURLcode init_config_builder_verifier(struct Curl_easy *data, struct rustls_client_config_builder *builder, - const struct ssl_primary_config *conn_config, + const struct ssl_filter_config *conn_config, const struct curl_blob *ca_info_blob, const char * const ssl_cafile) { @@ -841,8 +847,7 @@ init_config_builder_keylog(struct Curl_easy *data, static CURLcode init_config_builder_client_auth(struct Curl_easy *data, - const struct ssl_primary_config *conn_config, - const struct ssl_config_data *ssl_config, + const struct ssl_filter_config *conn_config, struct rustls_client_config_builder *builder) { struct dynbuf cert_contents; @@ -850,31 +855,62 @@ init_config_builder_client_auth(struct Curl_easy *data, rustls_result rr; const struct rustls_certified_key *certified_key = NULL; CURLcode result = CURLE_OK; + const bool have_cert = conn_config->clientcert || conn_config->cert_blob; + const bool have_key = conn_config->key || conn_config->key_blob; - if(conn_config->clientcert && !ssl_config->primary.key) { - failf(data, "rustls: must provide key with certificate '%s'", - conn_config->clientcert); + if(have_cert && !have_key) { + failf(data, "rustls: must provide key with certificate"); + return CURLE_SSL_CERTPROBLEM; + } + else if(!have_cert && have_key) { + failf(data, "rustls: must provide certificate with key"); return CURLE_SSL_CERTPROBLEM; } - else if(!conn_config->clientcert && ssl_config->primary.key) { - failf(data, "rustls: must provide certificate with key '%s'", - ssl_config->primary.key); + + if(conn_config->cert_type && !curl_strequal(conn_config->cert_type, "PEM")) { + failf(data, "rustls: unsupported certificate type '%s', only 'PEM' is " + "supported", conn_config->cert_type); + return CURLE_SSL_CERTPROBLEM; + } + if(conn_config->key_type && !curl_strequal(conn_config->key_type, "PEM")) { + failf(data, "rustls: unsupported key type '%s', only 'PEM' is supported", + conn_config->key_type); return CURLE_SSL_CERTPROBLEM; } curlx_dyn_init(&cert_contents, DYN_CERTFILE_SIZE); curlx_dyn_init(&key_contents, DYN_KEYFILE_SIZE); - if(!read_file_into(conn_config->clientcert, &cert_contents)) { + if(conn_config->cert_blob) { + result = curlx_dyn_addn(&cert_contents, + conn_config->cert_blob->data, + conn_config->cert_blob->len); + if(result) { + failf(data, "rustls: failed to copy client certificate blob (%s)", + result == CURLE_TOO_LARGE ? "too large" : "out of memory"); + goto cleanup; + } + } + else if(!read_file_into(conn_config->clientcert, &cert_contents)) { failf(data, "rustls: failed to read client certificate file: '%s'", conn_config->clientcert); result = CURLE_SSL_CERTPROBLEM; goto cleanup; } - if(!read_file_into(ssl_config->primary.key, &key_contents)) { + if(conn_config->key_blob) { + result = curlx_dyn_addn(&key_contents, + conn_config->key_blob->data, + conn_config->key_blob->len); + if(result) { + failf(data, "rustls: failed to copy key blob (%s)", + result == CURLE_TOO_LARGE ? "too large" : "out of memory"); + goto cleanup; + } + } + else if(!read_file_into(conn_config->key, &key_contents)) { failf(data, "rustls: failed to read key file: '%s'", - ssl_config->primary.key); + conn_config->key); result = CURLE_SSL_CERTPROBLEM; goto cleanup; } @@ -918,21 +954,12 @@ init_config_builder_client_auth(struct Curl_easy *data, #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) || - CURL_EASY_STR(data, STRING_ECH_CONFIG)) - return FALSE; - return TRUE; -} - static CURLcode init_config_builder_ech(struct Curl_easy *data, struct Curl_cfilter *cf, struct rustls_client_config_builder *builder) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); const rustls_hpke *hpke = rustls_supported_hpke(); unsigned char *ech_config = NULL; size_t ech_config_len = 0; @@ -948,13 +975,13 @@ init_config_builder_ech(struct Curl_easy *data, goto cleanup; } - if(CURL_EASY_STR(data, STRING_ECH_PUBLIC)) { + if(conn_config->ech_public) { failf(data, "rustls: ECH outername not supported"); result = CURLE_SSL_CONNECT_ERROR; goto cleanup; } - if(data->set.tls_ech == CURLECH_GREASE) { + if(conn_config->ech == CURLECH_GREASE) { rr = rustls_client_config_builder_enable_ech_grease(builder, hpke); if(rr != RUSTLS_RESULT_OK) { rustls_failf(data, rr, "rustls: failed to configure ECH GREASE"); @@ -964,10 +991,10 @@ init_config_builder_ech(struct Curl_easy *data, return CURLE_OK; } - if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { - const char *b64 = CURL_EASY_STR(data, STRING_ECH_CONFIG); + if(conn_config->ech && conn_config->ech_config) { + const char *b64 = conn_config->ech_config; size_t decode_result; - if(!b64) { + if(!b64[0]) { infof(data, "rustls: ECHConfig from command line empty"); result = CURLE_SSL_CONNECT_ERROR; goto cleanup; @@ -985,7 +1012,8 @@ init_config_builder_ech(struct Curl_easy *data, const struct Curl_https_rrinfo *rinfo = Curl_conn_dns_get_https(data, cf->sockindex, connssl->peer.origin); - if(!rinfo || !rinfo->echconfiglist) { + if(!Curl_httpsrr_is_for_peer(connssl->peer.origin, rinfo) || + !rinfo->echconfiglist) { failf(data, "rustls: ECH requested but no ECHConfig available"); result = CURLE_SSL_CONNECT_ERROR; goto cleanup; @@ -1005,7 +1033,7 @@ init_config_builder_ech(struct Curl_easy *data, } cleanup: /* if we base64 decoded, we can free now */ - if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + if(conn_config->ech && conn_config->ech_config) { curlx_free(ech_config); } if(dns) { @@ -1020,9 +1048,8 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, struct rustls_ssl_backend_data * const backend) { const struct ssl_connect_data *connssl = cf->ctx; - const struct ssl_primary_config *conn_config = - Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + const struct ssl_filter_config *conn_config = + Curl_ssl_cf_get_filter_config(cf); struct rustls_connection *rconn = NULL; struct rustls_client_config_builder *config_builder = NULL; @@ -1036,7 +1063,7 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, DEBUGASSERT(backend); rconn = backend->conn; - result = init_config_builder(data, conn_config, &config_builder); + result = init_config_builder(cf, data, conn_config, &config_builder); if(result != CURLE_OK) { return result; } @@ -1074,10 +1101,10 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, } } - if(conn_config->clientcert || ssl_config->primary.key) { + if(conn_config->clientcert || conn_config->cert_blob || + conn_config->key || conn_config->key_blob) { result = init_config_builder_client_auth(data, conn_config, - ssl_config, config_builder); if(result != CURLE_OK) { rustls_client_config_builder_free(config_builder); @@ -1086,9 +1113,9 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf, } #ifdef USE_ECH - if(CURLECH_ENABLED(data)) { + if(Curl_ssl_ech_enabled(cf)) { result = init_config_builder_ech(data, cf, config_builder); - if((result != CURLE_OK) && (data->set.tls_ech == CURLECH_HARD)) { + if((result != CURLE_OK) && (conn_config->ech == CURLECH_HARD)) { rustls_client_config_builder_free(config_builder); return result; } @@ -1162,7 +1189,7 @@ static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, #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) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, connssl->peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); @@ -1221,9 +1248,9 @@ static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, "rustls: handshake complete, %s, ciphersuite: %.*s, " "key exchange group: %.*s", ver, - (int) ciphersuite_name.len, + (int)ciphersuite_name.len, ciphersuite_name.data, - (int) kex_group_name.len, + (int)kex_group_name.len, kex_group_name.data); } if(data->set.ssl.certinfo) { @@ -1257,15 +1284,10 @@ static CURLcode cr_connect(struct Curl_cfilter *cf, struct Curl_easy *data, (int)errorlen, errorbuf); return map_error(rresult); } - { - const char *beg; - const char *end; - beg = (const char *)der_data; - end = (const char *)(der_data + der_len); - result = Curl_extract_certinfo(data, (int)i, beg, end); - if(result) - return result; - } + result = Curl_extract_certinfo(data, (int)i, + der_data, der_data + der_len); + if(result) + return result; } } diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 98b738d0c65e..f3c314619b19 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -45,7 +45,7 @@ #include "curlx/strdup.h" #include "strerror.h" #include "select.h" /* for the socket readiness */ -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/multibyte.h" #include "vtls/x509asn1.h" #include "curlx/version_win32.h" @@ -61,8 +61,10 @@ */ #ifdef CURL_SCHANNEL_DEV_DEBUG #define SCH_DEV(x) x -#define SCH_DEV_SHOWBOOL(x) \ - infof(data, "schannel: " #x " %s", (x) ? "TRUE" : "FALSE"); +#define SCH_DEV_SHOWBOOL(x) \ + do { \ + infof(data, "schannel: " #x " %s", (x) ? "TRUE" : "FALSE"); \ + } while(0) #else #define SCH_DEV(x) do {} while(0) #define SCH_DEV_SHOWBOOL(x) do {} while(0) @@ -166,7 +168,7 @@ static CURLcode schannel_set_ssl_version_min_max(DWORD *enabled_protocols, struct Curl_cfilter *cf, struct Curl_easy *data) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); long ssl_version = conn_config->version; long ssl_version_max = (long)conn_config->version_max; long i = ssl_version; @@ -381,7 +383,7 @@ static CURLcode get_client_cert(struct Curl_cfilter *cf, HCERTSTORE *out_cert_store, PCCERT_CONTEXT *out_cert_context) { - struct ssl_primary_config *sslc = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *sslc = Curl_ssl_cf_get_filter_config(cf); PCCERT_CONTEXT client_cert = NULL; HCERTSTORE client_cert_store = NULL; CURLcode result = CURLE_OK; @@ -605,7 +607,7 @@ static CURLcode acquire_sspi_handle(struct Curl_cfilter *cf, DWORD flags, DWORD enabled_protocols) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); SECURITY_STATUS sspi_status = SEC_E_OK; CURLcode result; @@ -724,8 +726,7 @@ static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); PCCERT_CONTEXT client_cert = NULL; HCERTSTORE client_cert_store = NULL; @@ -746,14 +747,14 @@ static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, else flags = SCH_CRED_AUTO_CRED_VALIDATION; - if(ssl_config->no_revoke) { + if(conn_config->no_revoke) { flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE; DEBUGF(infof(data, "schannel: disabled server certificate revocation " "checks")); } - else if(ssl_config->revoke_best_effort) { + else if(conn_config->revoke_best_effort) { flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | SCH_CRED_IGNORE_REVOCATION_OFFLINE | SCH_CRED_REVOCATION_CHECK_CHAIN; @@ -780,7 +781,7 @@ static CURLcode schannel_acquire_credential_handle(struct Curl_cfilter *cf, "names in server certificates.")); } - if(!ssl_config->auto_client_cert) { + if(!conn_config->auto_client_cert) { flags &= ~(DWORD)SCH_CRED_USE_DEFAULT_CREDS; flags |= SCH_CRED_NO_DEFAULT_CREDS; infof(data, "schannel: disabled automatic use of client certificate"); @@ -849,8 +850,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); SecBuffer outbuf; SecBufferDesc outbuf_desc; SecBuffer inbuf; @@ -934,6 +934,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, int list_start_index = 0; unsigned int *extension_len = NULL; unsigned short *list_len = NULL; + const unsigned int uiALPN = SecApplicationProtocolNegotiationExt_ALPN; struct alpn_proto_buf proto; /* The first four bytes is an unsigned int indicating number @@ -943,8 +944,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, /* 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; + memcpy(&alpn_buffer[cur], &uiALPN, sizeof(uiALPN)); cur += (int)sizeof(unsigned int); /* The next two bytes is an unsigned short indicating the number @@ -986,7 +986,7 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | - (!ssl_config->auto_client_cert ? ISC_REQ_USE_SUPPLIED_CREDS : 0); + (!conn_config->auto_client_cert ? ISC_REQ_USE_SUPPLIED_CREDS : 0); /* allocate memory for the security context handle */ backend->ctxt = (struct Curl_schannel_ctxt *) @@ -1064,6 +1064,9 @@ static CURLcode schannel_connect_step1(struct Curl_cfilter *cf, backend->recv_renegotiating = FALSE; backend->renegotiate_state.started = FALSE; backend->encdata_is_incomplete = FALSE; + backend->shutdown_generated = FALSE; + backend->shutdown_offset = 0; + memset(&backend->shutdown_token, 0, sizeof(backend->shutdown_token)); /* continue to second handshake step */ connssl->connecting_state = ssl_connect_2; @@ -1126,7 +1129,7 @@ static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, do { SECURITY_STATUS sspi_status; - const char *x509_der; + uint8_t *x509_der; DWORD x509_der_len; struct Curl_X509certificate x509_parsed; struct Curl_asn1Element *pubkey; @@ -1147,7 +1150,7 @@ static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, (pCertContextServer->cbCertEncoded > 0))) break; - x509_der = (const char *)pCertContextServer->pbCertEncoded; + x509_der = (uint8_t *)pCertContextServer->pbCertEncoded; x509_der_len = pCertContextServer->cbCertEncoded; memset(&x509_parsed, 0, sizeof(x509_parsed)); if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len)) @@ -1238,7 +1241,7 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int i; size_t nread = 0; SecBuffer outbuf[3]; @@ -1328,7 +1331,8 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, /* setup input buffers */ InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, - curlx_malloc(backend->encdata.offset), + curlx_memdup0((const char *)backend->encdata.buffer, + backend->encdata.offset), curlx_uztoul(backend->encdata.offset)); InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&inbuf_desc, inbuf, 2); @@ -1344,10 +1348,6 @@ static CURLcode schannel_connect_step2(struct Curl_cfilter *cf, return CURLE_OUT_OF_MEMORY; } - /* copy received handshake data into input buffer */ - memcpy(inbuf[0].pvBuffer, backend->encdata.buffer, - backend->encdata.offset); - /* 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 @@ -1567,12 +1567,12 @@ static bool add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, struct Adder_args *args = (struct Adder_args *)raw_arg; args->result = CURLE_OK; if(valid_cert_encoding(ccert_context)) { - const char *beg = (const char *)ccert_context->pbCertEncoded; - const char *end = beg + ccert_context->cbCertEncoded; int insert_index = reverse_order ? (args->certs_count - 1) - args->idx : args->idx; args->result = Curl_extract_certinfo(args->data, insert_index, - beg, end); + ccert_context->pbCertEncoded, + ccert_context->pbCertEncoded + + ccert_context->cbCertEncoded); args->idx++; } return args->result == CURLE_OK; @@ -1603,7 +1603,7 @@ static CURLcode schannel_connect_step3(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct schannel_ssl_backend_data *backend = (struct schannel_ssl_backend_data *)connssl->backend; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_easy_config *ssl_config = Curl_ssl_cf_get_easy_config(cf, data); CURLcode result = CURLE_OK; SECURITY_STATUS sspi_status = SEC_E_OK; CERT_CONTEXT *ccert_context = NULL; @@ -2449,7 +2449,8 @@ static CURLcode schannel_shutdown(struct Curl_cfilter *cf, goto out; } - if(backend->cred && backend->ctxt && !backend->sent_shutdown) { + if(backend->cred && backend->ctxt && !backend->sent_shutdown && + !backend->shutdown_generated) { SecBufferDesc BuffDesc; SecBuffer Buffer; SECURITY_STATUS sspi_status; @@ -2489,41 +2490,60 @@ static CURLcode schannel_shutdown(struct Curl_cfilter *cf, &backend->ret_flags, NULL); if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) { - /* send close message which is in output buffer */ - size_t written; + backend->shutdown_token = outbuf; + backend->shutdown_generated = TRUE; + } + else { + char buffer[STRERROR_LEN]; + if(outbuf.pvBuffer) + Curl_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + failf(data, "schannel: generating close msg failed: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + result = CURLE_SEND_ERROR; + goto out; + } + } + + if(backend->shutdown_generated && !backend->sent_shutdown) { + size_t written = 0; + size_t remaining; + + DEBUGASSERT(backend->shutdown_offset <= backend->shutdown_token.cbBuffer); + remaining = backend->shutdown_token.cbBuffer - backend->shutdown_offset; + if(remaining) { result = Curl_conn_cf_send(cf->next, data, - (const uint8_t *)outbuf.pvBuffer, - outbuf.cbBuffer, - FALSE, &written); - Curl_pSecFn->FreeContextBuffer(outbuf.pvBuffer); - if(!result) { - if(written < outbuf.cbBuffer) { - result = CURLE_SEND_ERROR; - failf(data, "schannel: failed to send close msg: %s" - " (bytes written: %zu)", curl_easy_strerror(result), written); - goto out; - } - backend->sent_shutdown = TRUE; - *done = TRUE; - } - else if(result == CURLE_AGAIN) { + ((uint8_t *)backend->shutdown_token.pvBuffer + + backend->shutdown_offset), + remaining, FALSE, &written); + if(!result) + backend->shutdown_offset += written; + if(result == CURLE_AGAIN || (!result && written < remaining)) { connssl->io_need = CURL_SSL_IO_NEED_SEND; result = CURLE_OK; goto out; } - else { - if(!backend->recv_connection_closed) { - result = CURLE_SEND_ERROR; - failf(data, "schannel: error sending close msg: %d", (int)result); - goto out; - } - /* Looks like server already closed the connection. - * An error to send our close notify is not a failure. */ + } + else /* no shutdown message to send? */ + result = CURLE_OK; + if(result) { + if(backend->recv_connection_closed) { + /* The server already closed, so sending close_notify is optional. */ *done = TRUE; result = CURLE_OK; } + else { + failf(data, "schannel: error sending close msg: %d", (int)result); + result = CURLE_SEND_ERROR; + } + goto out; } + if(backend->shutdown_token.pvBuffer) + Curl_pSecFn->FreeContextBuffer(backend->shutdown_token.pvBuffer); + memset(&backend->shutdown_token, 0, sizeof(backend->shutdown_token)); + backend->shutdown_offset = 0; + backend->sent_shutdown = TRUE; + *done = TRUE; } /* If the connection seems open and we have not seen the close notify @@ -2565,8 +2585,16 @@ static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) DEBUGASSERT(data); DEBUGASSERT(backend); + if(backend->shutdown_token.pvBuffer) + Curl_pSecFn->FreeContextBuffer(backend->shutdown_token.pvBuffer); + memset(&backend->shutdown_token, 0, sizeof(backend->shutdown_token)); + backend->shutdown_offset = 0; + /* free SSPI Schannel API security context handle */ if(backend->ctxt) { + if(cf->conn->sslContext == &backend->ctxt->ctxt_handle) + cf->conn->sslContext = NULL; + DEBUGF(infof(data, "schannel: clear security context handle")); Curl_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle); curlx_safefree(backend->ctxt); @@ -2736,11 +2764,10 @@ static void *schannel_get_internals(struct ssl_connect_data *connssl, HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_multi *multi = data->multi; const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; struct schannel_cert_share *share; - const struct ssl_general_config *cfg = &data->set.general_ssl; timediff_t timeout_ms; unsigned char info_blob_digest[CURL_SHA256_DIGEST_LENGTH]; @@ -2751,21 +2778,21 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, } share = Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), + MPROTO_SCHANNEL_CERT_SHARE_KEY, CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); if(!share || !share->cert_store) { return NULL; } /* zero ca_cache_timeout completely disables caching */ - if(!cfg->ca_cache_timeout) { + if(!data->set.ssl_ca_cache_timeout) { return NULL; } /* check for cache timeout by using the cached_x509_store_expired timediff calculation pattern from openssl.c. negative timeout means retain forever. */ - timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + timeout_ms = data->set.ssl_ca_cache_timeout * (timediff_t)1000; if(timeout_ms >= 0) { timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &share->time); @@ -2798,7 +2825,7 @@ HCERTSTORE Curl_schannel_get_cached_cert_store(struct Curl_cfilter *cf, return share->cert_store; } -static void schannel_cert_share_free(void *key, size_t key_len, void *p) +static void schannel_cert_share_free(const void *key, size_t key_len, void *p) { struct schannel_cert_share *share = p; DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); @@ -2816,7 +2843,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, struct Curl_easy *data, HCERTSTORE cert_store) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_multi *multi = data->multi; const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; struct schannel_cert_share *share; @@ -2843,7 +2870,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, } share = Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), + MPROTO_SCHANNEL_CERT_SHARE_KEY, CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY)); if(!share) { share = curlx_calloc(1, sizeof(*share)); @@ -2852,7 +2879,7 @@ bool Curl_schannel_set_cached_cert_store(struct Curl_cfilter *cf, return FALSE; } if(!Curl_hash_add2(&multi->proto_hash, - CURL_UNCONST(MPROTO_SCHANNEL_CERT_SHARE_KEY), + MPROTO_SCHANNEL_CERT_SHARE_KEY, CURL_CSTRLEN(MPROTO_SCHANNEL_CERT_SHARE_KEY), share, schannel_cert_share_free)) { curlx_free(share); diff --git a/lib/vtls/schannel_int.h b/lib/vtls/schannel_int.h index ccbb63d7e20c..99790b3778ed 100644 --- a/lib/vtls/schannel_int.h +++ b/lib/vtls/schannel_int.h @@ -31,7 +31,7 @@ #include "vtls/vtls.h" #include "curl_sha256.h" -#if defined(_MSC_VER) && (_MSC_VER < 1700) +#if defined(_MSC_VER) && (_MSC_VER <= 1700) /* Workaround for warning: 'type cast' : conversion from 'int' to 'LPCSTR' of greater size */ #undef CERT_STORE_PROV_MEMORY @@ -118,6 +118,8 @@ struct schannel_ssl_backend_data { struct sbuffer decdata; struct Curl_schannel_cred *cred; struct Curl_schannel_ctxt *ctxt; + SecBuffer shutdown_token; + size_t shutdown_offset; SecPkgContext_StreamSizes stream_sizes; unsigned long req_flags, ret_flags; CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ @@ -132,6 +134,7 @@ struct schannel_ssl_backend_data { BIT(use_alpn); /* true if ALPN is used for this connection */ BIT(use_manual_cred_validation); /* true if manual cred validation is used */ BIT(sent_shutdown); + BIT(shutdown_generated); /* encdata_is_incomplete: if encdata contains only a partial record that cannot be decrypted without another recv() (that is, status is SEC_E_INCOMPLETE_MESSAGE) then set this true. after recv() adds more diff --git a/lib/vtls/schannel_verify.c b/lib/vtls/schannel_verify.c index 8f9ce6c846ea..74b83a10bb4b 100644 --- a/lib/vtls/schannel_verify.c +++ b/lib/vtls/schannel_verify.c @@ -43,7 +43,7 @@ #include "vtls/vtls_int.h" #include "curl_trc.h" #include "strerror.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curlx/inet_pton.h" #include "curlx/multibyte.h" #include "curlx/version_win32.h" @@ -541,7 +541,7 @@ CURLcode Curl_verify_host(struct Curl_cfilter *cf, struct Curl_easy *data) } /* CertGetNameString guarantees that the returned name does not contain - * embedded null bytes. This appears to be undocumented behavior. + * embedded NUL bytes. This appears to be undocumented behavior. */ cert_hostname_buff = curlx_malloc(len * sizeof(TCHAR)); if(!cert_hostname_buff) { @@ -631,8 +631,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); SECURITY_STATUS sspi_status; CURLcode result = CURLE_OK; CERT_CONTEXT *pCertContextServer = NULL; @@ -734,7 +733,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, /* Win8/Server2012 allows us to match partial chains */ if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, VERSION_GREATER_THAN_EQUAL) && - !ssl_config->no_partialchain) { + !conn_config->no_partialchain) { engine_config.cbSize = sizeof(engine_config); engine_config.dwExclusiveFlags = CERT_CHAIN_EXCLUSIVE_ENABLE_CA_FLAG; } @@ -774,7 +773,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, NULL, pCertContextServer->hCertStore, &ChainPara, - (ssl_config->no_revoke ? 0 : + (conn_config->no_revoke ? 0 : CERT_CHAIN_REVOCATION_CHECK_CHAIN), NULL, &pChainContext)) { @@ -790,7 +789,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED); dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus; - if(ssl_config->revoke_best_effort) { + if(conn_config->revoke_best_effort) { /* Ignore errors when root certificates are missing the revocation * list URL, or when the list could not be downloaded because the * server is currently unreachable. */ diff --git a/lib/vtls/unitytls.c b/lib/vtls/unitytls.c index eee634b6df38..4feff5f9b59b 100644 --- a/lib/vtls/unitytls.c +++ b/lib/vtls/unitytls.c @@ -284,7 +284,7 @@ static unitytls_x509verify_result unitytls_on_verify(void* userData, unitytls_x5 struct Curl_cfilter *cf = (struct Curl_cfilter*)userData; struct ssl_connect_data *connssl = cf->ctx; struct ssl_backend_data* backend = connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); const bool verifypeer = conn_config->verifypeer; const bool verifyhost = conn_config->verifyhost; const char* const hostname = connssl->peer.origin->hostname; @@ -432,15 +432,14 @@ static CURLcode unitytls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy { struct ssl_connect_data *connssl = cf->ctx; struct ssl_backend_data *backend = connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); const char* const ssl_cafile = (ca_info_blob ? NULL : conn_config->CAfile); const bool verifypeer = conn_config->verifypeer; const char* const ssl_capath = conn_config->CApath; - char* const ssl_cert = ssl_config->primary.clientcert; - const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *ssl_key_blob = ssl_config->primary.key_blob; + char* const ssl_cert = conn_config->clientcert; + const struct curl_blob *ssl_cert_blob = conn_config->cert_blob; + const struct curl_blob *ssl_key_blob = conn_config->key_blob; const char* const hostname = connssl->peer.origin->hostname; unitytls_errorstate err = unitytls->unitytls_errorstate_create(); @@ -507,15 +506,15 @@ static CURLcode unitytls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy } /* Load the client private key */ - if(ssl_config->primary.key) { - backend->pk = unitytls_key_parse_pem_from_file(ssl_config->primary.key, ssl_config->primary.key_passwd, &err); + if(conn_config->key) { + backend->pk = unitytls_key_parse_pem_from_file(conn_config->key, conn_config->key_passwd, &err); if(!backend->pk || err.code != UNITYTLS_SUCCESS) { - failf(data, "Error reading private key %s", ssl_config->primary.key); + failf(data, "Error reading private key %s", conn_config->key); return CURLE_SSL_CERTPROBLEM; } } else if(ssl_key_blob) { - backend->pk = unitytls_key_parse_pem_from_blob(ssl_key_blob, ssl_config->primary.key_passwd, &err); + backend->pk = unitytls_key_parse_pem_from_blob(ssl_key_blob, conn_config->key_passwd, &err); if(!backend->pk || err.code != UNITYTLS_SUCCESS) { failf(data, "Error parsing private key blob"); return CURLE_SSL_CERTPROBLEM; @@ -590,8 +589,8 @@ static CURLcode unitytls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy #endif /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - CURLcode result = (*data->set.ssl.fsslctx)(data, backend->ctx, data->set.ssl.fsslctxp); + if(data->set.ssl_fsslctx) { + CURLcode result = (*data->set.ssl_fsslctx)(data, backend->ctx, data->set.ssl_fsslctxp); if(result != CURLE_OK) { failf(data, "error signaled by ssl ctx callback"); return result; diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index ff759789b5d2..06a79d780462 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -64,7 +64,7 @@ #include "strcase.h" #include "url.h" #include "progress.h" -#include "curlx/fopen.h" +#include "curlx/win32-fopen.h" #include "curl_sha256.h" #include "curlx/base64.h" #include "curlx/inet_pton.h" @@ -133,7 +133,7 @@ CURLcode Curl_unity_certverify(struct Curl_cfilter *cf, const unsigned char *der, size_t derlen) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result; DEBUGASSERT(conn_config->unity_certverify); @@ -199,22 +199,23 @@ int Curl_ssl_init(void) return 1; } -static bool ssl_prefs_check(struct Curl_easy *data) +static bool ssl_prefs_check(struct Curl_cfilter *cf, + struct Curl_easy *data) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); /* check for CURLOPT_SSLVERSION invalid parameter value */ - const unsigned char sslver = data->set.ssl.primary.version; - if(sslver >= CURL_SSLVERSION_LAST) { + if(conn_config->version >= CURL_SSLVERSION_LAST) { failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION"); return FALSE; } - switch(data->set.ssl.primary.version_max) { + switch(conn_config->version_max) { case CURL_SSLVERSION_MAX_NONE: case CURL_SSLVERSION_MAX_DEFAULT: break; default: - if((data->set.ssl.primary.version_max >> 16) < sslver) { + if((conn_config->version_max >> 16) < conn_config->version) { failf(data, "CURL_SSLVERSION_MAX incompatible with CURL_SSLVERSION"); return FALSE; } @@ -940,7 +941,7 @@ static ssl_peer_type get_peer_type(const char *hostname) CURLcode Curl_ssl_peer_init(struct ssl_peer *ssl_peer, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, const char *tls_id, uint8_t transport) { @@ -1030,7 +1031,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, *done = FALSE; if(!connssl->prefs_checked) { - if(!ssl_prefs_check(data)) { + if(!ssl_prefs_check(cf, data)) { result = CURLE_SSL_CONNECT_ERROR; goto out; } @@ -1427,7 +1428,7 @@ static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, static CURLcode cf_ssl_peer_init(struct Curl_cfilter *cf, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc) + struct ssl_filter_config *sslc) { struct ssl_connect_data *connssl = cf->ctx; char tls_id[80]; @@ -1472,10 +1473,7 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, #if defined(USE_HTTPSRR) && defined(USE_ECH) /* When using ECH, kick off the HTTPS-RR resolve */ if((origin->scheme->family == CURLPROTO_HTTP) && - CURLECH_ENABLED(data) && - Curl_ssl_supports(data, SSLSUPP_ECH) && - (data->set.tls_ech != CURLECH_GREASE) && - !CURL_EASY_STR(data, STRING_ECH_CONFIG)) { + Curl_ssl_need_httpsrr(cf)) { result = Curl_conn_dns_add_https_resolve(data, cf->conn, cf->sockindex, origin); } @@ -1546,6 +1544,30 @@ bool Curl_ssl_supports(struct Curl_easy *data, unsigned int ssl_option) return (Curl_ssl->supports & ssl_option); } +#ifdef USE_ECH +bool Curl_ssl_ech_enabled(struct Curl_cfilter *cf) +{ + if(Curl_ssl->supports & SSLSUPP_ECH) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + return !!conn_config->ech; + } + return FALSE; +} + +bool Curl_ssl_need_httpsrr(struct Curl_cfilter *cf) +{ + if(Curl_ssl->supports & SSLSUPP_ECH) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); + if(!conn_config->ech) + return FALSE; + if((conn_config->ech == CURLECH_GREASE) || conn_config->ech_config) + return FALSE; + return TRUE; + } + return FALSE; +} +#endif + static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf, struct Curl_easy *data, bool send_shutdown, bool *done) @@ -1564,7 +1586,7 @@ static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf, *done = FALSE; while(!result && !*done && loop--) { - timeout_ms = Curl_shutdown_timeleft(data, cf->conn, cf->sockindex); + timeout_ms = Curl_cshutdn_timeleft_ms(data, cf->conn, cf->sockindex); if(timeout_ms < 0) { /* no need to continue if time is already up */ @@ -1611,9 +1633,9 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, if(cf->cft == &Curl_cft_ssl) { bool done; CURL_TRC_CF(data, cf, "shutdown and remove SSL, start"); - Curl_shutdown_start(data, sockindex, 0); + Curl_cshutdn_start_timer(data, sockindex, 0); result = vtls_shutdown_blocking(cf, data, send_shutdown, &done); - Curl_shutdown_clear(data, sockindex); + Curl_cshutdn_clear_timer(data, sockindex); if(!result && !done) /* blocking failed? */ result = CURLE_SSL_SHUTDOWN_FAILED; Curl_conn_cf_discard(&cf, data); @@ -1630,8 +1652,9 @@ bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) return (cf->cft->flags & CF_TYPE_SSL) && (cf->cft->flags & CF_TYPE_PROXY); } -struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, - struct Curl_easy *data) +struct ssl_easy_config * +Curl_ssl_cf_get_easy_config(struct Curl_cfilter *cf, + struct Curl_easy *data) { #ifdef CURL_DISABLE_PROXY (void)cf; @@ -1641,8 +1664,8 @@ struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, #endif } -struct ssl_primary_config *Curl_ssl_cf_get_primary_config( - struct Curl_cfilter *cf) +struct ssl_filter_config * +Curl_ssl_cf_get_filter_config(struct Curl_cfilter *cf) { #ifdef CURL_DISABLE_PROXY return &cf->conn->ssl_config; diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index 5eae5bd6a267..3cf88ecc11f3 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -26,8 +26,8 @@ #include "curl_setup.h" struct connectdata; -struct ssl_config_data; -struct ssl_primary_config; +struct ssl_easy_config; +struct ssl_filter_config; struct Curl_cfilter; struct Curl_easy; struct dynbuf; @@ -49,13 +49,19 @@ struct dynbuf; #define SSLSUPP_ISSUERCERT_BLOB (1 << 14) /* CURLOPT_ISSUERCERT_BLOB */ #ifdef USE_ECH -/* CURLECH_ values for the tls_ech option */ +/* CURLECH_ values for the ech option */ #define CURLECH_DISABLE 0 #define CURLECH_GREASE 1 #define CURLECH_ENABLE 2 #define CURLECH_HARD 3 -#define CURLECH_ENABLED(data) ((data)->set.tls_ech) +/* TRUE if ECH is enabled (and supported) */ +bool Curl_ssl_ech_enabled(struct Curl_cfilter *cf); +/* TRUE if SSL ECH configuration requires DNS HTTPS-RR record */ +bool Curl_ssl_need_httpsrr(struct Curl_cfilter *cf); +#else +#define Curl_ssl_ech_enabled(c) FALSE +#define Curl_ssl_need_httpsrr(c) FALSE #endif /* USE_ECH */ #define ALPN_ACCEPTED "ALPN: server accepted " @@ -123,7 +129,7 @@ CURLcode Curl_unity_certverify(struct Curl_cfilter *cf, CURLcode Curl_ssl_peer_init(struct ssl_peer *ssl_peer, struct Curl_peer *origin, struct Curl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, const char *tls_id, uint8_t transport); /** @@ -215,17 +221,14 @@ CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, */ bool Curl_ssl_supports(struct Curl_easy *data, unsigned int ssl_option); -/** - * Get the ssl_config_data in `data` that is relevant for cfilter `cf`. - */ -struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf, - struct Curl_easy *data); +/* Get the ssl_easy_config from `data` relevant for cfilter `cf`. */ +struct ssl_easy_config * +Curl_ssl_cf_get_easy_config(struct Curl_cfilter *cf, + struct Curl_easy *data); -/** - * Get the primary config relevant for the filter from its connection. - */ -struct ssl_primary_config *Curl_ssl_cf_get_primary_config( - struct Curl_cfilter *cf); +/* Get the filter config relevant for cfilter `cf`. */ +struct ssl_filter_config * +Curl_ssl_cf_get_filter_config(struct Curl_cfilter *cf); extern struct Curl_cftype Curl_cft_ssl; #ifndef CURL_DISABLE_PROXY @@ -247,8 +250,8 @@ extern struct Curl_cftype Curl_cft_ssl_proxy; #define Curl_ssl_supports(a, b) FALSE #define Curl_ssl_cfilter_add(a, b, c, d) CURLE_NOT_BUILT_IN #define Curl_ssl_cfilter_remove(a, b, c) CURLE_OK -#define Curl_ssl_cf_get_config(a, b) NULL -#define Curl_ssl_cf_get_primary_config(a) NULL +#define Curl_ssl_cf_get_easy_config(a, b) NULL +#define Curl_ssl_cf_get_filter_config(a) NULL #endif #endif /* HEADER_CURL_VTLS_H */ diff --git a/lib/vtls/vtls_config.c b/lib/vtls/vtls_config.c index e4615104f46d..66723deda908 100644 --- a/lib/vtls/vtls_config.c +++ b/lib/vtls/vtls_config.c @@ -97,7 +97,7 @@ static bool blobcmp(struct curl_blob *first, struct curl_blob *second) return !memcmp(first->data, second->data, first->len); /* same data */ } -void Curl_ssl_config_init(struct ssl_primary_config *sslc) +void Curl_ssl_config_init(struct ssl_easy_config *sslc) { /* * libcurl 7.10 introduced SSL verification *by default*! This needs to be @@ -108,7 +108,7 @@ void Curl_ssl_config_init(struct ssl_primary_config *sslc) sslc->cache_session = TRUE; /* caching by default */ } -void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc) +void Curl_ssl_config_cleanup(struct ssl_filter_config *sslc) { if(sslc->deep_copy) { curlx_safefree(sslc->CApath); @@ -129,13 +129,17 @@ void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc) curlx_safefree(sslc->key); curlx_safefree(sslc->key_type); curlx_safefree(sslc->key_passwd); +#ifdef USE_ECH + curlx_safefree(sslc->ech_config); + curlx_safefree(sslc->ech_public); +#endif sslc->deep_copy = FALSE; } } static bool match_ssl_primary_config(struct Curl_easy *data, - struct ssl_primary_config *c1, - struct ssl_primary_config *c2) + struct ssl_filter_config *c1, + struct ssl_filter_config *c2) { (void)data; if((c1->version == c2->version) && @@ -149,6 +153,12 @@ static bool match_ssl_primary_config(struct Curl_easy *data, (c1->unity_certverify == c2->unity_certverify) && (c1->unity_certverify_userp == c2->unity_certverify_userp) && #endif /* UNITY_CERTVERIFY */ + (c1->auto_client_cert == c2->auto_client_cert) && +#ifdef USE_ECH + (c1->ech == c2->ech) && + Curl_safecmp(c1->ech_config, c2->ech_config) && + Curl_safecmp(c1->ech_public, c2->ech_public) && +#endif blobcmp(c1->cert_blob, c2->cert_blob) && blobcmp(c1->ca_info_blob, c2->ca_info_blob) && blobcmp(c1->issuercert_blob, c2->issuercert_blob) && @@ -173,22 +183,23 @@ static bool match_ssl_primary_config(struct Curl_easy *data, } bool Curl_ssl_conn_config_match(struct Curl_easy *data, + struct ssl_filter_config *conn_config, struct connectdata *candidate, bool proxy) { #ifndef CURL_DISABLE_PROXY if(proxy) - return match_ssl_primary_config(data, &data->set.proxy_ssl.primary, + return match_ssl_primary_config(data, conn_config, &candidate->proxy_ssl_config); #else (void)proxy; #endif - return match_ssl_primary_config(data, &data->set.ssl.primary, + return match_ssl_primary_config(data, conn_config, &candidate->ssl_config); } -static bool clone_ssl_primary_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest) +static bool clone_ssl_primary_config(struct ssl_filter_config *source, + struct ssl_filter_config *dest) { DEBUGASSERT(!dest->deep_copy); dest->deep_copy = TRUE; @@ -197,13 +208,19 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, dest->verifypeer = source->verifypeer; dest->verifyhost = source->verifyhost; dest->verifystatus = source->verifystatus; + dest->ssl_options = source->ssl_options; dest->native_ca_store = source->native_ca_store; dest->cache_session = source->cache_session; - dest->ssl_options = source->ssl_options; #if UNITY_CERTVERIFY dest->unity_certverify = source->unity_certverify; dest->unity_certverify_userp = source->unity_certverify_userp; #endif /* UNITY_CERTVERIFY */ + dest->auto_client_cert = source->auto_client_cert; + dest->earlydata = source->earlydata; + dest->enable_beast = source->enable_beast; + dest->no_partialchain = source->no_partialchain; + dest->no_revoke = source->no_revoke; + dest->revoke_best_effort = source->revoke_best_effort; CLONE_BLOB(cert_blob); CLONE_BLOB(ca_info_blob); @@ -224,26 +241,33 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source, CLONE_STRING(key_type); CLONE_STRING(key_passwd); CLONE_BLOB(key_blob); +#ifdef USE_ECH + dest->ech = source->ech; + CLONE_STRING(ech_config); + CLONE_STRING(ech_public); +#endif return TRUE; } -static void ssl_easy_config_compl_options(struct Curl_peer *origin, - struct Curl_peer *initial_origin, - struct ssl_config_data *sslc) +static void +ssl_easy_config_compl_options(struct Curl_peer *origin, + struct Curl_peer *initial_origin, + struct ssl_easy_config *sslc, + struct ssl_filter_config *primary) { - uint8_t options = sslc->primary.ssl_options; + uint8_t options = sslc->ssl_options; /* If set via CURLOPT_(PROXY_)SSL_OPTIONS, we definitely use it. * If not, we switch it on for supported backends if no custom * CA settings exist. */ - sslc->primary.native_ca_store = !!(options & CURLSSLOPT_NATIVE_CA); - sslc->enable_beast = !!(options & CURLSSLOPT_ALLOW_BEAST); - sslc->no_partialchain = !!(options & CURLSSLOPT_NO_PARTIALCHAIN); - sslc->no_revoke = !!(options & CURLSSLOPT_NO_REVOKE); - sslc->revoke_best_effort = !!(options & CURLSSLOPT_REVOKE_BEST_EFFORT); - sslc->earlydata = !!(options & CURLSSLOPT_EARLYDATA); - - sslc->auto_client_cert = Curl_peer_equal(origin, initial_origin) && - !!(options & CURLSSLOPT_AUTO_CLIENT_CERT); + primary->native_ca_store = !!(options & CURLSSLOPT_NATIVE_CA); + primary->earlydata = !!(options & CURLSSLOPT_EARLYDATA); + primary->auto_client_cert = + Curl_peer_equal(origin, initial_origin) && + !!(options & CURLSSLOPT_AUTO_CLIENT_CERT); + primary->enable_beast = !!(options & CURLSSLOPT_ALLOW_BEAST); + primary->no_partialchain = !!(options & CURLSSLOPT_NO_PARTIALCHAIN); + primary->no_revoke = !!(options & CURLSSLOPT_NO_REVOKE); + primary->revoke_best_effort = !!(options & CURLSSLOPT_REVOKE_BEST_EFFORT); } static char *ssl_easy_steal(struct Curl_easy *data, enum dupstring id) @@ -253,20 +277,23 @@ static char *ssl_easy_steal(struct Curl_easy *data, enum dupstring id) return CURL_UNCONST(CURL_EASY_STR(data, id)); } -CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, - struct Curl_peer *origin) +CURLcode Curl_ssl_filter_config_tmp_init( + struct Curl_easy *data, struct Curl_peer *origin, + struct ssl_filter_config *ssl_origin, + struct ssl_filter_config *ssl_proxy) { - struct ssl_config_data *sslc = &data->set.ssl; + struct ssl_easy_config *sslc = &data->set.ssl; #if defined(CURL_CA_PATH) || defined(CURL_CA_BUNDLE) CURLcode result; #endif - ssl_easy_config_compl_options(origin, data->state.initial_origin, sslc); + ssl_easy_config_compl_options(origin, data->state.initial_origin, sslc, + ssl_origin); if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { #if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) - sslc->primary.native_ca_store = TRUE; + ssl_origin->native_ca_store = TRUE; #endif #ifdef CURL_CA_PATH if(!sslc->custom_capath && !CURL_EASY_STR(data, STRING_SSL_CAPATH)) { @@ -283,51 +310,73 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, } #endif } - sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE); - sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE); - sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH); - sslc->primary.cipher_list = ssl_easy_steal(data, STRING_SSL_CIPHER_LIST); - sslc->primary.cipher_list13 = ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST); - sslc->primary.signature_algorithms = + + ssl_origin->version = sslc->version; + ssl_origin->version_max = sslc->version_max; + ssl_origin->verifypeer = sslc->verifypeer; + ssl_origin->verifyhost = sslc->verifyhost; + ssl_origin->verifystatus = sslc->verifystatus; + ssl_origin->cache_session = sslc->cache_session; + ssl_origin->ssl_options = sslc->ssl_options; +#if UNITY_CERTVERIFY + ssl_origin->unity_certverify = sslc->unity_certverify; + ssl_origin->unity_certverify_userp = sslc->unity_certverify_userp; +#endif /* UNITY_CERTVERIFY */ + ssl_origin->CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE); + ssl_origin->CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE); + ssl_origin->CApath = ssl_easy_steal(data, STRING_SSL_CAPATH); + ssl_origin->cipher_list = ssl_easy_steal(data, STRING_SSL_CIPHER_LIST); + ssl_origin->cipher_list13 = ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST); + ssl_origin->signature_algorithms = ssl_easy_steal(data, STRING_SSL_SIGNATURE_ALGORITHMS); - sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO]; - sslc->primary.curves = ssl_easy_steal(data, STRING_SSL_EC_CURVES); + ssl_origin->ca_info_blob = data->set.blobs[BLOB_CAINFO]; + ssl_origin->curves = ssl_easy_steal(data, STRING_SSL_EC_CURVES); /* Maybe these should not be used for another origin. But for * backwards compatibility, keep them in. */ - sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT); - sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; + ssl_origin->issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT); + ssl_origin->issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT]; if(Curl_peer_equal(data->state.initial_origin, origin)) { - sslc->primary.pinned_key = + ssl_origin->pinned_key = ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY); - sslc->primary.cert_blob = data->set.blobs[BLOB_CERT]; - sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE); - sslc->primary.key = ssl_easy_steal(data, STRING_KEY); - sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE); - sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD); - sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT); - sslc->primary.key_blob = data->set.blobs[BLOB_KEY]; + ssl_origin->cert_blob = data->set.blobs[BLOB_CERT]; + ssl_origin->cert_type = ssl_easy_steal(data, STRING_CERT_TYPE); + ssl_origin->key = ssl_easy_steal(data, STRING_KEY); + ssl_origin->key_type = ssl_easy_steal(data, STRING_KEY_TYPE); + ssl_origin->key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD); + ssl_origin->clientcert = ssl_easy_steal(data, STRING_CERT); + ssl_origin->key_blob = data->set.blobs[BLOB_KEY]; +#ifdef USE_ECH + ssl_origin->ech = data->set.ssl.ech; + ssl_origin->ech_config = ssl_easy_steal(data, STRING_ECH_CONFIG); + ssl_origin->ech_public = ssl_easy_steal(data, STRING_ECH_PUBLIC); +#endif } else { - sslc->primary.pinned_key = NULL; - sslc->primary.cert_blob = NULL; - sslc->primary.cert_type = NULL; - sslc->primary.key = NULL; - sslc->primary.key_type = NULL; - sslc->primary.key_passwd = NULL; - sslc->primary.clientcert = NULL; - sslc->primary.key_blob = NULL; + ssl_origin->pinned_key = NULL; + ssl_origin->cert_blob = NULL; + ssl_origin->cert_type = NULL; + ssl_origin->key = NULL; + ssl_origin->key_type = NULL; + ssl_origin->key_passwd = NULL; + ssl_origin->clientcert = NULL; + ssl_origin->key_blob = NULL; +#ifdef USE_ECH + ssl_origin->ech = 0; + ssl_origin->ech_config = NULL; + ssl_origin->ech_public = NULL; +#endif } #ifndef CURL_DISABLE_PROXY sslc = &data->set.proxy_ssl; - /* no initial origin for proxy, it is not changed for redirects */ - ssl_easy_config_compl_options(NULL, NULL, sslc); - if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL) { + ssl_easy_config_compl_options(NULL, NULL, sslc, ssl_proxy); + if((Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL)) { + /* no initial origin for proxy, it is not changed for redirects */ #if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE) if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob) - sslc->primary.native_ca_store = TRUE; + ssl_proxy->native_ca_store = TRUE; #endif #ifdef CURL_CA_PATH if(!sslc->custom_capath && @@ -346,42 +395,65 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, } #endif } - sslc->primary.CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE_PROXY); - sslc->primary.CApath = ssl_easy_steal(data, STRING_SSL_CAPATH_PROXY); - sslc->primary.cipher_list = + + ssl_proxy->version = sslc->version; + ssl_proxy->version_max = sslc->version_max; + ssl_proxy->verifypeer = sslc->verifypeer; + ssl_proxy->verifyhost = sslc->verifyhost; + ssl_proxy->verifystatus = sslc->verifystatus; + ssl_proxy->cache_session = sslc->cache_session; + ssl_proxy->ssl_options = sslc->ssl_options; +#if UNITY_CERTVERIFY + ssl_proxy->unity_certverify = sslc->unity_certverify; + ssl_proxy->unity_certverify_userp = sslc->unity_certverify_userp; +#endif /* UNITY_CERTVERIFY */ + ssl_proxy->CAfile = ssl_easy_steal(data, STRING_SSL_CAFILE_PROXY); + ssl_proxy->CApath = ssl_easy_steal(data, STRING_SSL_CAPATH_PROXY); + ssl_proxy->cipher_list = ssl_easy_steal(data, STRING_SSL_CIPHER_LIST_PROXY); - sslc->primary.cipher_list13 = + ssl_proxy->cipher_list13 = ssl_easy_steal(data, STRING_SSL_CIPHER13_LIST_PROXY); - sslc->primary.pinned_key = + ssl_proxy->pinned_key = ssl_easy_steal(data, STRING_SSL_PINNEDPUBLICKEY_PROXY); - sslc->primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY]; - sslc->primary.ca_info_blob = data->set.blobs[BLOB_CAINFO_PROXY]; - sslc->primary.issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT_PROXY); - sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; - sslc->primary.CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE_PROXY); - sslc->primary.cert_type = ssl_easy_steal(data, STRING_CERT_TYPE_PROXY); - sslc->primary.key = ssl_easy_steal(data, STRING_KEY_PROXY); - sslc->primary.key_type = ssl_easy_steal(data, STRING_KEY_TYPE_PROXY); - sslc->primary.key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD_PROXY); - sslc->primary.clientcert = ssl_easy_steal(data, STRING_CERT_PROXY); - sslc->primary.key_blob = data->set.blobs[BLOB_KEY_PROXY]; + ssl_proxy->cert_blob = data->set.blobs[BLOB_CERT_PROXY]; + ssl_proxy->ca_info_blob = data->set.blobs[BLOB_CAINFO_PROXY]; + ssl_proxy->issuercert = ssl_easy_steal(data, STRING_SSL_ISSUERCERT_PROXY); + ssl_proxy->issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY]; + ssl_proxy->CRLfile = ssl_easy_steal(data, STRING_SSL_CRLFILE_PROXY); + ssl_proxy->cert_type = ssl_easy_steal(data, STRING_CERT_TYPE_PROXY); + ssl_proxy->key = ssl_easy_steal(data, STRING_KEY_PROXY); + ssl_proxy->key_type = ssl_easy_steal(data, STRING_KEY_TYPE_PROXY); + ssl_proxy->key_passwd = ssl_easy_steal(data, STRING_KEY_PASSWD_PROXY); + ssl_proxy->clientcert = ssl_easy_steal(data, STRING_CERT_PROXY); + ssl_proxy->key_blob = data->set.blobs[BLOB_KEY_PROXY]; +#ifdef USE_ECH + /* Not configurable via a CURLOPT_* */ + ssl_proxy->ech = 0; + ssl_proxy->ech_config = NULL; + ssl_proxy->ech_public = NULL; +#endif +#else + (void)ssl_proxy; #endif /* CURL_DISABLE_PROXY */ return CURLE_OK; } -CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, - struct connectdata *conn) +CURLcode Curl_ssl_conn_config_clone(struct ssl_filter_config *ssl_config, + struct ssl_filter_config *proxy_ssl_config, + struct connectdata *conn) { /* 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)) + if(!clone_ssl_primary_config(ssl_config, &conn->ssl_config)) return CURLE_OUT_OF_MEMORY; #ifndef CURL_DISABLE_PROXY - if(!clone_ssl_primary_config(&data->set.proxy_ssl.primary, - &conn->proxy_ssl_config)) + if(proxy_ssl_config && + !clone_ssl_primary_config(proxy_ssl_config, &conn->proxy_ssl_config)) return CURLE_OUT_OF_MEMORY; +#else + (void)proxy_ssl_config; #endif return CURLE_OK; } @@ -398,17 +470,21 @@ void Curl_ssl_conn_config_update(struct Curl_easy *data, bool for_proxy) { /* May be called on an easy that has no connection yet */ if(data->conn) { - struct ssl_primary_config *src, *dest; + struct ssl_easy_config *src; + struct ssl_filter_config *dest; #ifndef CURL_DISABLE_PROXY - src = for_proxy ? &data->set.proxy_ssl.primary : &data->set.ssl.primary; + src = for_proxy ? &data->set.proxy_ssl : &data->set.ssl; dest = for_proxy ? &data->conn->proxy_ssl_config : &data->conn->ssl_config; #else (void)for_proxy; - src = &data->set.ssl.primary; + src = &data->set.ssl; dest = &data->conn->ssl_config; #endif dest->verifyhost = src->verifyhost; dest->verifypeer = src->verifypeer; dest->verifystatus = src->verifystatus; +#ifdef USE_ECH + dest->ech = src->ech; +#endif } } diff --git a/lib/vtls/vtls_config.h b/lib/vtls/vtls_config.h index 9913279d177e..c2f5c609cf97 100644 --- a/lib/vtls/vtls_config.h +++ b/lib/vtls/vtls_config.h @@ -29,7 +29,7 @@ struct Curl_easy; struct connectdata; struct Curl_peer; -struct ssl_primary_config { +struct ssl_filter_config { char *CApath; /* certificate directory (does not work on Windows) */ char *CAfile; /* certificate to verify peer against */ char *issuercert; /* optional issuer certificate filename */ @@ -43,14 +43,18 @@ struct ssl_primary_config { char *key; /* private key filename */ char *key_type; /* format for private key (default: PEM) */ char *key_passwd; /* plain text private key password */ +#ifdef USE_ECH + char *ech_config; /* TLS ECH configuration, base64 string */ + char *ech_public; /* TLS ECH public DNS (SNI in outer ClientHello) */ +#endif struct curl_blob *cert_blob; struct curl_blob *ca_info_blob; struct curl_blob *issuercert_blob; struct curl_blob *key_blob; char *curves; /* list of curves to use */ #if UNITY_CERTVERIFY - /* Unity: peer verification callback and its userdata. These live in the - primary config rather than in ssl_config_data so that they take part in + /* Unity: peer verification callback and its userdata, copied from + ssl_easy_config. They live in the filter config so that they take part in the connection reuse check, i.e. a connection verified by one callback is never reused by a transfer carrying a different one. */ curl_unity_certverify_callback unity_certverify; @@ -59,52 +63,65 @@ struct ssl_primary_config { uint32_t version_max; /* max supported version the client wants to use */ uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ uint8_t version; /* what version the client wants to use */ +#ifdef USE_ECH + uint8_t ech; /* TLS ECH CURLECH_((DIS|EN)ABLE|GREASE) */ +#endif BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ BIT(verifystatus); /* set TRUE if certificate status must be checked */ - BIT(native_ca_store); /* use the native CA store of operating system */ + BIT(native_ca_store); /* use the native CA store of operating system */ BIT(cache_session); /* cache session or not */ BIT(deep_copy); /* members are deep copies, eg. owned here */ -}; - -struct ssl_config_data { - struct ssl_primary_config primary; - long certverifyresult; /* result from the certificate verification */ - curl_ssl_ctx_callback fsslctx; /* function to initialize SSL ctx */ - void *fsslctxp; /* parameter for call back */ - BIT(certinfo); /* gather lots of certificate info */ - BIT(earlydata); /* use TLS 1.3 early data */ + BIT(earlydata); /* use TLS 1.3 early data */ + BIT(auto_client_cert); /* automatically locate and use a client + certificate for authentication (Schannel) */ BIT(enable_beast); /* allow this flaw for interoperability's sake */ - BIT(no_revoke); /* disable SSL certificate revocation checks */ BIT(no_partialchain); /* do not accept partial certificate chains */ + BIT(no_revoke); /* disable SSL certificate revocation checks */ BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation list errors */ - BIT(auto_client_cert); /* automatically locate and use a client - certificate for authentication (Schannel) */ +}; + +struct ssl_easy_config { + long certverifyresult; /* result from the certificate verification */ + uint32_t version_max; /* max supported version the client wants to use */ + uint8_t version; /* what version the client wants to use */ + uint8_t ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */ +#ifdef USE_ECH + uint8_t ech; /* TLS ECH CURLECH_((DIS|EN)ABLE|GREASE) */ +#endif + BIT(verifypeer); /* set TRUE if this is desired */ + BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ + BIT(verifystatus); /* set TRUE if certificate status must be checked */ + BIT(cache_session); /* cache session or not */ + BIT(certinfo); /* gather lots of certificate info */ BIT(custom_cafile); /* application has set custom CA file */ BIT(custom_capath); /* application has set custom CA path */ BIT(custom_cablob); /* application has set custom CA blob */ +#if UNITY_CERTVERIFY + /* Unity: peer verification callback and its userdata, set via setopt. + Copied into the connection's ssl_filter_config by + Curl_ssl_filter_config_tmp_init() so they take part in the connection + reuse check. */ + curl_unity_certverify_callback unity_certverify; + void *unity_certverify_userp; +#endif /* UNITY_CERTVERIFY */ }; -struct ssl_general_config { - int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ -}; - -void Curl_ssl_config_init(struct ssl_primary_config *sslc); -void Curl_ssl_config_cleanup(struct ssl_primary_config *sslc); +void Curl_ssl_config_init(struct ssl_easy_config *sslc); +void Curl_ssl_config_cleanup(struct ssl_filter_config *sslc); -/** - * Init the `data->set.ssl` and `data->set.proxy_ssl` for - * connection matching use. - */ -CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data, - struct Curl_peer *origin); +/* Init the SSL filter configs *shallow* for connection matching + * with origin and data's configuration settings. */ +CURLcode Curl_ssl_filter_config_tmp_init( + struct Curl_easy *data, struct Curl_peer *origin, + struct ssl_filter_config *ssl_origin, + struct ssl_filter_config *ssl_proxy); -/** - * Init SSL configs (main + proxy) for a new connection from the easy handle. - */ -CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, - struct connectdata *conn); +/* Clone the given filter configs into the connection. */ +CURLcode Curl_ssl_conn_config_clone(struct ssl_filter_config *ssl_config, + struct ssl_filter_config *proxy_ssl_config, + struct connectdata *conn); /** * Free allocated resources in SSL configs (main + proxy) for @@ -113,11 +130,12 @@ CURLcode Curl_ssl_conn_config_init(struct Curl_easy *data, void Curl_ssl_conn_config_cleanup(struct connectdata *conn); /** - * Return TRUE iff SSL configuration from `data` is functionally the + * Return TRUE if `conn_config` is functionally the * same as the one on `candidate`. * @param proxy match the proxy SSL config or the main one */ bool Curl_ssl_conn_config_match(struct Curl_easy *data, + struct ssl_filter_config *conn_config, struct connectdata *candidate, bool proxy); diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c index 8140ad6c87ba..533875381683 100644 --- a/lib/vtls/vtls_scache.c +++ b/lib/vtls/vtls_scache.c @@ -124,7 +124,7 @@ static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf, } static CURLcode cf_ssl_peer_key_add_mtls(struct dynbuf *buf, - struct ssl_primary_config *ssl, + struct ssl_filter_config *ssl, bool *is_local) { CURLcode result = CURLE_OK; @@ -194,7 +194,7 @@ static CURLcode ssl_peer_key_add_transport(struct dynbuf *buf, } static CURLcode ssl_peer_key_add_vrfy(struct dynbuf *buf, - struct ssl_primary_config *ssl, + struct ssl_filter_config *ssl, const struct ssl_peer *peer) { CURLcode result; @@ -225,7 +225,7 @@ static CURLcode ssl_peer_key_add_vrfy(struct dynbuf *buf, return CURLE_OK; } -static CURLcode ssl_peer_key_build(struct ssl_primary_config *ssl, +static CURLcode ssl_peer_key_build(struct ssl_filter_config *ssl, const struct ssl_peer *peer, const char *tls_id, char **ppeer_key) @@ -357,7 +357,7 @@ static CURLcode ssl_peer_key_build(struct ssl_primary_config *ssl, } CURLcode Curl_ssl_peer_key_make(const struct ssl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, const char *tls_id, char **ppeer_key) { @@ -689,8 +689,9 @@ void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache) bool Curl_ssl_scache_use(struct Curl_cfilter *cf, struct Curl_easy *data) { if(cf_ssl_scache_get(data)) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - return ssl_config ? ssl_config->primary.cache_session : FALSE; + struct ssl_filter_config *conn_config = + Curl_ssl_cf_get_filter_config(cf); + return conn_config ? conn_config->cache_session : FALSE; } return FALSE; } @@ -753,7 +754,7 @@ bool Curl_ssl_scache_is_locked_by_current_thread(struct Curl_easy *data) } static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, - struct ssl_primary_config *conn_config) + struct ssl_filter_config *conn_config) { if(!conn_config) { if(peer->clientcert || peer->key_passwd) @@ -770,7 +771,7 @@ static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer, static CURLcode cf_ssl_find_peer_by_key(struct Curl_easy *data, struct Curl_ssl_scache *scache, const char *ssl_peer_key, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, struct Curl_ssl_scache_peer **ppeer) { size_t i, peer_key_len = 0; @@ -864,7 +865,7 @@ static struct Curl_ssl_scache_peer *cf_ssl_get_free_peer( static CURLcode cf_ssl_add_peer(struct Curl_easy *data, struct Curl_ssl_scache *scache, const char *ssl_peer_key, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, struct Curl_ssl_scache_peer **ppeer) { struct Curl_ssl_scache_peer *peer = NULL; @@ -937,7 +938,7 @@ static CURLcode cf_scache_add_session(struct Curl_cfilter *cf, struct Curl_ssl_session *s) { struct Curl_ssl_scache_peer *peer = NULL; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OUT_OF_MEMORY; curl_off_t now = (curl_off_t)time(NULL); curl_off_t max_lifetime; @@ -992,11 +993,11 @@ CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf, struct Curl_ssl_session *s) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result; - DEBUGASSERT(ssl_config); + DEBUGASSERT(conn_config); - if(!scache || !ssl_config->primary.cache_session) { + if(!scache || !conn_config->cache_session) { Curl_ssl_session_destroy(s); return CURLE_OK; } @@ -1026,7 +1027,7 @@ CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf, struct Curl_ssl_session **ps) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_scache_peer *peer = NULL; struct Curl_llist_node *n; struct Curl_ssl_session *s = NULL; @@ -1070,7 +1071,7 @@ CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf, Curl_ssl_scache_obj_dtor *sobj_dtor_cb) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; @@ -1102,7 +1103,7 @@ void *Curl_ssl_scache_get_obj(struct Curl_cfilter *cf, const char *ssl_peer_key) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; void *sobj; @@ -1127,7 +1128,7 @@ void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf, const char *ssl_peer_key) { struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_ssl_scache_peer *peer = NULL; CURLcode result; @@ -1228,9 +1229,10 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, struct Curl_ssl_scache *scache = cf_ssl_scache_get(data); struct Curl_ssl_scache_peer *peer = NULL; struct Curl_ssl_session *s = NULL; + bool have_lock = FALSE; CURLcode result; - if(!scache) { + if(!scache || !sdata || !sdata_len) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; } @@ -1244,6 +1246,7 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, goto out; Curl_ssl_scache_lock(data); + have_lock = TRUE; if(ssl_peer_key) { result = cf_ssl_add_peer(data, scache, ssl_peer_key, NULL, &peer); @@ -1284,7 +1287,7 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data, } out: - if(scache && scache->is_locked) + if(scache && have_lock) Curl_ssl_scache_unlock(data); Curl_ssl_session_destroy(s); return result; diff --git a/lib/vtls/vtls_scache.h b/lib/vtls/vtls_scache.h index c9f35e800f62..9a393c72f6fd 100644 --- a/lib/vtls/vtls_scache.h +++ b/lib/vtls/vtls_scache.h @@ -62,7 +62,7 @@ void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache); * @param ppeer_key on successful return, the key generated */ CURLcode Curl_ssl_peer_key_make(const struct ssl_peer *peer, - struct ssl_primary_config *sslc, + struct ssl_filter_config *sslc, const char *tls_id, char **ppeer_key); diff --git a/lib/vtls/vtls_spack.c b/lib/vtls/vtls_spack.c index d8e51c4fd118..b53b46b0a19d 100644 --- a/lib/vtls/vtls_spack.c +++ b/lib/vtls/vtls_spack.c @@ -247,6 +247,7 @@ CURLcode Curl_ssl_session_unpack(struct Curl_easy *data, size_t dlen; CURLcode result; + DEBUGASSERT(ps); DEBUGASSERT(buf); DEBUGASSERT(buflen); *ps = NULL; diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 4b9facadf01e..f3bea53879b8 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -230,13 +230,6 @@ static int wssl_bio_cf_create(WOLFSSL_BIO *bio) return 1; } -static int wssl_bio_cf_destroy(WOLFSSL_BIO *bio) -{ - if(!bio) - return 0; - return 1; -} - static long wssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) { struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); @@ -373,7 +366,6 @@ static int wssl_bio_cf_init_methods(void) wolfSSL_BIO_meth_set_read(wssl_bio_cf_method, &wssl_bio_cf_in_read); wolfSSL_BIO_meth_set_ctrl(wssl_bio_cf_method, &wssl_bio_cf_ctrl); wolfSSL_BIO_meth_set_create(wssl_bio_cf_method, &wssl_bio_cf_create); - wolfSSL_BIO_meth_set_destroy(wssl_bio_cf_method, &wssl_bio_cf_destroy); return TRUE; /* fine */ } @@ -525,7 +517,7 @@ static bool wssl_apply_session( Curl_wssl_init_session_reuse_cb *sess_reuse_cb, struct Curl_ssl_session *scs) { - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; WOLFSSL_SESSION *session = NULL; bool success = FALSE; @@ -546,7 +538,7 @@ static bool wssl_apply_session( infof(data, "SSL reusing session with ALPN '%s'", scs->alpn ? scs->alpn : "-"); success = TRUE; - if(ssl_config->earlydata && + if(conn_config->earlydata && !cf->conn->bits.connect_only && !strcmp("TLSv1.3", wolfSSL_get_version(wss->ssl))) { bool do_early_data = FALSE; @@ -586,7 +578,7 @@ static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, WOLFSSL_X509_STORE *store, struct wssl_ctx *wssl) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; const char * const ssl_cafile = /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ @@ -680,7 +672,7 @@ struct wssl_x509_share { struct curltime time; /* when the cached store was created */ }; -static void wssl_x509_share_free(void *key, size_t key_len, void *p) +static void wssl_x509_share_free(const void *key, size_t key_len, void *p) { struct wssl_x509_share *share = p; DEBUGASSERT(key_len == CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)); @@ -697,9 +689,8 @@ static void wssl_x509_share_free(void *key, size_t key_len, void *p) static bool wssl_cached_x509_store_expired(struct Curl_easy *data, const struct wssl_x509_share *mb) { - const struct ssl_general_config *cfg = &data->set.general_ssl; timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &mb->time); - timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000; + timediff_t timeout_ms = data->set.ssl_ca_cache_timeout * (timediff_t)1000; if(timeout_ms < 0) return FALSE; @@ -710,7 +701,7 @@ static bool wssl_cached_x509_store_expired(struct Curl_easy *data, static bool wssl_cached_x509_store_different(struct Curl_cfilter *cf, const struct wssl_x509_share *mb) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); if(!mb->CAfile || !conn_config->CAfile) return mb->CAfile != conn_config->CAfile; @@ -726,7 +717,7 @@ static WOLFSSL_X509_STORE *wssl_get_cached_x509_store(struct Curl_cfilter *cf, DEBUGASSERT(multi); share = multi ? Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_WSSL_X509_KEY), + MPROTO_WSSL_X509_KEY, CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)) : NULL; if(share && share->store && !wssl_cached_x509_store_expired(data, share) && @@ -741,7 +732,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, WOLFSSL_X509_STORE *store) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); struct Curl_multi *multi = data->multi; struct wssl_x509_share *share; @@ -749,7 +740,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, if(!multi) return; share = Curl_hash_pick(&multi->proto_hash, - CURL_UNCONST(MPROTO_WSSL_X509_KEY), + MPROTO_WSSL_X509_KEY, CURL_CSTRLEN(MPROTO_WSSL_X509_KEY)); if(!share) { @@ -757,7 +748,7 @@ static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, if(!share) return; if(!Curl_hash_add2(&multi->proto_hash, - CURL_UNCONST(MPROTO_WSSL_X509_KEY), + MPROTO_WSSL_X509_KEY, CURL_CSTRLEN(MPROTO_WSSL_X509_KEY), share, wssl_x509_share_free)) { curlx_free(share); @@ -791,8 +782,7 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, struct wssl_ctx *wssl) { - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); - struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result = CURLE_OK; WOLFSSL_X509_STORE *cached_store; bool cache_criteria_met; @@ -803,11 +793,11 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, /* Consider the X509 store cacheable if it comes exclusively from a CAfile, or no source is provided and we are falling back to wolfSSL's built-in default. */ - cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) && + cache_criteria_met = (data->set.ssl_ca_cache_timeout != 0) && conn_config->verifypeer && !conn_config->CApath && !conn_config->ca_info_blob && - !ssl_config->primary.CRLfile && + !conn_config->CRLfile && !conn_config->native_ca_store; cached_store = cache_criteria_met ? wssl_get_cached_x509_store(cf, data) @@ -909,17 +899,17 @@ static int wssl_legacy_CTX_set_max_proto_version(WOLFSSL_CTX *ctx, int version) #endif /* OPENSSL_EXTRA */ static CURLcode wssl_client_cert(struct Curl_easy *data, - struct ssl_config_data *ssl_config, + struct ssl_filter_config *conn_config, struct wssl_ctx *wctx) { /* Load the client certificate, and private key */ #ifndef NO_FILESYSTEM - if(ssl_config->primary.cert_blob || ssl_config->primary.clientcert) { - const char *cert_file = ssl_config->primary.clientcert; - const char *key_file = ssl_config->primary.key; - const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->primary.key_blob; - int file_type = wssl_do_file_type(ssl_config->primary.cert_type); + if(conn_config->cert_blob || conn_config->clientcert) { + const char *cert_file = conn_config->clientcert; + const char *key_file = conn_config->key; + const struct curl_blob *cert_blob = conn_config->cert_blob; + const struct curl_blob *key_blob = conn_config->key_blob; + int file_type = wssl_do_file_type(conn_config->cert_type); int rc; switch(file_type) { @@ -950,7 +940,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, key_file = cert_file; } else - file_type = wssl_do_file_type(ssl_config->primary.key_type); + file_type = wssl_do_file_type(conn_config->key_type); rc = key_blob ? wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, @@ -962,10 +952,10 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, } } #else /* NO_FILESYSTEM */ - if(ssl_config->primary.cert_blob) { - const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; - const struct curl_blob *key_blob = ssl_config->primary.key_blob; - int file_type = wssl_do_file_type(ssl_config->primary.cert_type); + if(conn_config->cert_blob) { + const struct curl_blob *cert_blob = conn_config->cert_blob; + const struct curl_blob *key_blob = conn_config->key_blob; + int file_type = wssl_do_file_type(conn_config->cert_type); int rc; switch(file_type) { @@ -990,7 +980,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, if(!key_blob) key_blob = cert_blob; else - file_type = wssl_do_file_type(ssl_config->primary.key_type); + file_type = wssl_do_file_type(conn_config->key_type); if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, (long)key_blob->len, @@ -1004,7 +994,7 @@ static CURLcode wssl_client_cert(struct Curl_easy *data, } static CURLcode ssl_version(struct Curl_easy *data, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, struct wssl_ctx *wctx, int *min_version, int *max_version) { @@ -1078,7 +1068,7 @@ static CURLcode ssl_version(struct Curl_easy *data, static CURLcode wssl_init_ciphers(struct Curl_easy *data, struct wssl_ctx *wctx, - struct ssl_primary_config *conn_config, + struct ssl_filter_config *conn_config, int tls_min, int tls_max) { #ifndef WOLFSSL_TLS13 @@ -1144,7 +1134,7 @@ static CURLcode wssl_init_ciphers(struct Curl_easy *data, static CURLcode wssl_init_curves(struct Curl_easy *data, struct wssl_ctx *wctx, - struct ssl_primary_config *conn_config) + struct ssl_filter_config *conn_config) { char *curves = conn_config->curves; /* Without an explicit list, leave the key share group selection to @@ -1257,25 +1247,25 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, struct Curl_easy *data, struct ssl_peer *peer) { + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int trying_ech_now = 0; - if(CURL_EASY_STR(data, STRING_ECH_PUBLIC)) { - infof(data, "ECH: outername not (yet) supported" - " with wolfSSL"); + if(conn_config->ech_public) { + infof(data, "ECH: outername not 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(conn_config->ech == CURLECH_GREASE) { + infof(data, "ECH: GREASE is done by default by wolfSSL: no need to ask"); } - if(data->set.tls_ech && CURL_EASY_STR(data, STRING_ECH_CONFIG)) { - const char *b64val = CURL_EASY_STR(data, STRING_ECH_CONFIG); + else if(conn_config->ech && conn_config->ech_config) { + const char *b64val = conn_config->ech_config; word32 b64len = 0; b64len = (word32)strlen(b64val); if(b64len && wolfSSL_SetEchConfigsBase64(wctx->ssl, CURL_UNCONST(b64val), b64len) != WOLFSSL_SUCCESS) { - if(data->set.tls_ech == CURLECH_HARD) + if(conn_config->ech == CURLECH_HARD) return CURLE_SSL_CONNECT_ERROR; } else { @@ -1287,7 +1277,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, const struct Curl_https_rrinfo *rinfo = Curl_conn_dns_get_https(data, cf->sockindex, peer->origin); - if(rinfo && rinfo->echconfiglist) { + if(Curl_httpsrr_is_for_peer(peer->origin, rinfo) && rinfo->echconfiglist) { const unsigned char *ecl = rinfo->echconfiglist; size_t elen = rinfo->echconfiglist_len; @@ -1295,7 +1285,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32)elen) != WOLFSSL_SUCCESS) { infof(data, "ECH: wolfSSL_SetEchConfigs failed"); - if(data->set.tls_ech == CURLECH_HARD) { + if(conn_config->ech == CURLECH_HARD) { return CURLE_SSL_CONNECT_ERROR; } } @@ -1306,7 +1296,7 @@ static CURLcode wssl_init_ech(struct wssl_ctx *wctx, } else { infof(data, "ECH: requested but no ECHConfig available"); - if(data->set.tls_ech == CURLECH_HARD) { + if(conn_config->ech == CURLECH_HARD) { return CURLE_SSL_CONNECT_ERROR; } } @@ -1331,8 +1321,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, 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; + struct ssl_filter_config *conn_config; WOLFSSL_METHOD *req_method = NULL; struct alpn_spec alpns; CURLcode result = CURLE_FAILED_INIT; @@ -1341,7 +1330,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, DEBUGASSERT(!wctx->ssl_ctx); DEBUGASSERT(!wctx->ssl); - conn_config = Curl_ssl_cf_get_primary_config(cf); + conn_config = Curl_ssl_cf_get_filter_config(cf); if(!conn_config) { result = CURLE_FAILED_INIT; goto out; @@ -1379,7 +1368,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, if(result) goto out; - result = wssl_client_cert(data, ssl_config, wctx); + result = wssl_client_cert(data, conn_config, wctx); if(result) goto out; @@ -1419,14 +1408,14 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, } /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { + if(data->set.ssl_fsslctx) { if(!wctx->x509_store_setup) { result = Curl_wssl_setup_x509_store(cf, data, wctx); if(result) goto out; } - result = (*data->set.ssl.fsslctx)(data, wctx->ssl_ctx, - data->set.ssl.fsslctxp); + result = (*data->set.ssl_fsslctx)(data, wctx->ssl_ctx, + data->set.ssl_fsslctxp); if(result) { failf(data, "error signaled by SSL ctx callback"); goto out; @@ -1449,7 +1438,7 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, goto out; #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG - if(CURLECH_ENABLED(data)) { + if(Curl_ssl_ech_enabled(cf)) { result = wssl_init_ech(wctx, cf, data, peer); if(result) goto out; @@ -1470,21 +1459,6 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, 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) || - CURL_EASY_STR(data, STRING_ECH_CONFIG)) - 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. @@ -1494,7 +1468,7 @@ static CURLcode wssl_connect_step1(struct Curl_cfilter *cf, { struct ssl_connect_data *connssl = cf->ctx; struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); CURLcode result; DEBUGASSERT(wssl); @@ -1589,7 +1563,7 @@ CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, if(pinnedpubkey) { #ifdef KEEP_PEER_CERT - const char *x509_der; + const uint8_t *x509_der; int x509_der_len; struct Curl_X509certificate x509_parsed; struct Curl_asn1Element *pubkey; @@ -1602,7 +1576,7 @@ CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, goto end; } - x509_der = (const char *)wolfSSL_X509_get_der(x509, &x509_der_len); + x509_der = wolfSSL_X509_get_der(x509, &x509_der_len); if(!x509_der) { failf(data, "SSL: failed retrieving ASN.1 server certificate"); goto end; @@ -1681,7 +1655,7 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) { struct ssl_connect_data *connssl = cf->ctx; struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; - struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_filter_config *conn_config = Curl_ssl_cf_get_filter_config(cf); int ret = -1, detail; CURLcode result; @@ -2160,7 +2134,7 @@ static CURLcode wssl_connect(struct Curl_cfilter *cf, #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) && + if(Curl_ssl_need_httpsrr(cf) && !Curl_conn_dns_resolved_https(data, cf->sockindex, connssl->peer.peer)) { CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); diff --git a/lib/vtls/wolfssl.h b/lib/vtls/wolfssl.h index 2016a0069f0e..17dd81644fcb 100644 --- a/lib/vtls/wolfssl.h +++ b/lib/vtls/wolfssl.h @@ -73,9 +73,6 @@ CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, 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/lib/vtls/x509asn1.c b/lib/vtls/x509asn1.c index 4c5dac3e8b83..bf736701b550 100644 --- a/lib/vtls/x509asn1.c +++ b/lib/vtls/x509asn1.c @@ -127,11 +127,11 @@ static const struct Curl_OID OIDtable[] = { #define CURL_ASN1_MAX_RECURSIONS 16 -static const char *getASN1Element_(struct Curl_asn1Element *elem, - const char *beg, const char *end, - size_t lvl) +static const uint8_t *getASN1Element_(struct Curl_asn1Element *elem, + const uint8_t *beg, const uint8_t *end, + size_t lvl) { - unsigned char b; + uint8_t b; size_t len; struct Curl_asn1Element lelem; @@ -146,7 +146,7 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, /* Process header byte. */ elem->header = beg; - b = (unsigned char)*beg++; + b = *beg++; elem->constructed = (b & 0x20) != 0; elem->eclass = (b >> 6) & 3; b &= 0x1F; @@ -157,7 +157,7 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, /* Process length. */ if(beg >= end) return NULL; - b = (unsigned char)*beg++; + b = *beg++; if(!(b & 0x80)) len = b; else { @@ -178,7 +178,7 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, elem->end = beg; return beg + 1; } - else if((unsigned)b > (size_t)(end - beg)) + else if(b > (size_t)(end - beg)) return NULL; /* Does not fit in source. */ else { /* Get long length. */ @@ -186,7 +186,7 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, do { if(len & 0xFF000000L) return NULL; /* Lengths > 32 bits are not supported. */ - len = (len << 8) | (unsigned char) *beg++; + len = (len << 8) | *beg++; } while(--b); } } @@ -200,10 +200,10 @@ static const char *getASN1Element_(struct Curl_asn1Element *elem, /* * @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) +UNITTEST const uint8_t *getASN1Element(struct Curl_asn1Element *elem, + const uint8_t *beg, const uint8_t *end); +UNITTEST const uint8_t *getASN1Element(struct Curl_asn1Element *elem, + const uint8_t *beg, const uint8_t *end) { return getASN1Element_(elem, beg, end, 0); } @@ -231,12 +231,12 @@ static const struct Curl_OID *searchOID(const char *oid) */ static CURLcode bool2str(struct dynbuf *store, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { uint8_t val; if(end - beg != 1) return CURLE_BAD_FUNCTION_ARGUMENT; - val = (uint8_t)*beg; + val = *beg; if((val != 0xff) && (val != 0x00)) return CURLE_BAD_FUNCTION_ARGUMENT; return curlx_dyn_add(store, val ? "TRUE" : "FALSE"); @@ -248,22 +248,23 @@ static CURLcode bool2str(struct dynbuf *store, * Return error code. */ static CURLcode octet2str(struct dynbuf *store, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { CURLcode result = CURLE_OK; while(!result && beg < end) - result = curlx_dyn_addf(store, "%02x:", (unsigned char)*beg++); + result = curlx_dyn_addf(store, "%02x:", *beg++); return result; } -static CURLcode bit2str(struct dynbuf *store, const char *beg, const char *end) +static CURLcode bit2str(struct dynbuf *store, + const uint8_t *beg, const uint8_t *end) { /* Convert an ASN.1 bit string to a printable string. */ if(beg < end) { - uint8_t unused_bits = (uint8_t)*beg++; + uint8_t unused_bits = *beg++; if(unused_bits > 7) return CURLE_BAD_FUNCTION_ARGUMENT; } @@ -277,13 +278,14 @@ static CURLcode bit2str(struct dynbuf *store, const char *beg, const char *end) * * Returns error. */ -static CURLcode int2str(struct dynbuf *store, const char *beg, const char *end) +static CURLcode int2str(struct dynbuf *store, + const uint8_t *beg, const uint8_t *end) { uint32_t uval = 0; int32_t sval = 0; size_t n = end - beg; size_t i; - const unsigned char *p = (const unsigned char *)beg; + const uint8_t *p = beg; if(!n) return CURLE_BAD_FUNCTION_ARGUMENT; @@ -315,8 +317,8 @@ static CURLcode int2str(struct dynbuf *store, const char *beg, const char *end) * * Returns error. */ -static CURLcode utf8asn1str(struct dynbuf *to, int type, const char *from, - const char *end) +static CURLcode utf8asn1str(struct dynbuf *to, int type, + const uint8_t *from, const uint8_t *end) { size_t inlength = end - from; int size = 1; @@ -357,14 +359,14 @@ static CURLcode utf8asn1str(struct dynbuf *to, int type, const char *from, switch(size) { case 4: - wc = (wc << 8) | *(const unsigned char *)from++; - wc = (wc << 8) | *(const unsigned char *)from++; + wc = (wc << 8) | *from++; + wc = (wc << 8) | *from++; FALLTHROUGH(); case 2: - wc = (wc << 8) | *(const unsigned char *)from++; + wc = (wc << 8) | *from++; FALLTHROUGH(); default: /* case 1: */ - wc = (wc << 8) | *(const unsigned char *)from++; + wc = (wc << 8) | *from++; } if(wc >= 0x00000080) { if(wc >= 0x00000800) { @@ -404,9 +406,9 @@ static CURLcode utf8asn1str(struct dynbuf *to, int type, const char *from, * @unittest 1666 */ UNITTEST CURLcode encodeOID(struct dynbuf *store, - const char *beg, const char *end); + const uint8_t *beg, const uint8_t *end); UNITTEST CURLcode encodeOID(struct dynbuf *store, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { uint32_t x; uint32_t y; @@ -417,7 +419,7 @@ UNITTEST CURLcode encodeOID(struct dynbuf *store, /* Process the first two numbers. The initial digit cannot be larger than 2 */ - y = *(const unsigned char *)beg++; + y = *beg++; if(y <= 80) x = y / 40; else @@ -434,7 +436,7 @@ UNITTEST CURLcode encodeOID(struct dynbuf *store, do { if((t & 0xFE000000) || (beg >= end)) return CURLE_BAD_FUNCTION_ARGUMENT; - y = *(const unsigned char *)beg++; + y = *beg++; t = (t << 7) | (y & 0x7F); } while(y & 0x80); y = t; @@ -454,7 +456,7 @@ UNITTEST CURLcode encodeOID(struct dynbuf *store, return CURLE_BAD_FUNCTION_ARGUMENT; else if((x & 0xFE000000) || (beg == end)) return CURLE_BAD_FUNCTION_ARGUMENT; - y = *(const unsigned char *)beg++; + y = *beg++; x = (x << 7) | (y & 0x7F); } while(y & 0x80); result = curlx_dyn_addf(store, ".%u", x); @@ -469,7 +471,7 @@ UNITTEST CURLcode encodeOID(struct dynbuf *store, */ static CURLcode OID2str(struct dynbuf *store, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { CURLcode result = CURLE_OK; if(beg < end) { @@ -651,10 +653,12 @@ UNITTEST CURLcode ASN1tostr(struct dynbuf *store, result = OID2str(store, elem->beg, elem->end); break; case CURL_ASN1_UTC_TIME: - result = UTime2str(store, elem->beg, elem->end); + result = UTime2str(store, (const char *)elem->beg, + (const char *)elem->end); break; case CURL_ASN1_GENERALIZED_TIME: - result = GTime2str(store, elem->beg, elem->end); + result = GTime2str(store, (const char *)elem->beg, + (const char *)elem->end); break; case CURL_ASN1_UTF8_STRING: case CURL_ASN1_NUMERIC_STRING: @@ -682,10 +686,10 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) struct Curl_asn1Element atv; struct Curl_asn1Element oid; struct Curl_asn1Element value; - const char *p1; - const char *p2; - const char *p3; - const char *str; + const uint8_t *p1; + const uint8_t *p2; + const uint8_t *p3; + const uint8_t *str; CURLcode result = CURLE_OK; bool added = FALSE; struct dynbuf temp; @@ -717,7 +721,7 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) if(result) goto error; - str = curlx_dyn_ptr(&temp); + str = curlx_dyn_uptr(&temp); if(!str) { result = CURLE_BAD_FUNCTION_ARGUMENT; @@ -738,7 +742,7 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) } /* Encode attribute name. */ - result = curlx_dyn_add(store, str); + result = curlx_dyn_add(store, (const char *)str); if(result) goto error; @@ -770,13 +774,14 @@ static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) * See RFC 5280. */ int Curl_parseX509(struct Curl_X509certificate *cert, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { struct Curl_asn1Element elem; struct Curl_asn1Element tbsCertificate; - const char *ccp; - static const char defaultVersion = 0; /* v1. */ + const uint8_t *ccp; + static const uint8_t defaultVersion = 0; /* v1. */ + memset(cert, 0, sizeof(*cert)); cert->certificate.header = NULL; cert->certificate.beg = beg; cert->certificate.end = end; @@ -832,6 +837,9 @@ int Curl_parseX509(struct Curl_X509certificate *cert, return -1; if(!getASN1Element(&cert->notAfter, ccp, elem.end)) return -1; + /* These two fields MUST consume all of the validity elem */ + if(elem.end != cert->notAfter.end) + return -1; /* Get subject. */ beg = getASN1Element(&cert->subject, beg, end); if(!beg) @@ -852,10 +860,10 @@ int Curl_parseX509(struct Curl_X509certificate *cert, cert->issuerUniqueID.tag = cert->subjectUniqueID.tag = 0; cert->extensions.tag = elem.tag = 0; cert->issuerUniqueID.header = cert->subjectUniqueID.header = NULL; - cert->issuerUniqueID.beg = cert->issuerUniqueID.end = ""; - cert->subjectUniqueID.beg = cert->subjectUniqueID.end = ""; + cert->issuerUniqueID.beg = cert->issuerUniqueID.end = beg; + cert->subjectUniqueID.beg = cert->subjectUniqueID.end = beg; cert->extensions.header = NULL; - cert->extensions.beg = cert->extensions.end = ""; + cert->extensions.beg = cert->extensions.end = beg; if(beg < end) { beg = getASN1Element(&elem, beg, end); if(!beg) @@ -889,7 +897,7 @@ int Curl_parseX509(struct Curl_X509certificate *cert, static CURLcode dumpAlgo(struct dynbuf *store, struct Curl_asn1Element *param, - const char *beg, const char *end) + const uint8_t *beg, const uint8_t *end) { struct Curl_asn1Element oid; @@ -902,7 +910,7 @@ static CURLcode dumpAlgo(struct dynbuf *store, param->tag = 0; param->beg = param->end = end; if(beg < end) { - const char *p = getASN1Element(param, beg, end); + const uint8_t *p = getASN1Element(param, beg, end); if(!p) return CURLE_BAD_FUNCTION_ARGUMENT; } @@ -973,7 +981,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, const char *algo, { struct Curl_asn1Element elem; struct Curl_asn1Element pk; - const char *p; + const uint8_t *p; /* Generate all information records for the public key. */ @@ -1005,7 +1013,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, const char *algo, return 1; if(curl_strequal(algo, "rsaEncryption")) { - const char *q; + const uint8_t *q; size_t len; p = getASN1Element(&elem, pk.beg, pk.end); @@ -1018,7 +1026,7 @@ static int do_pubkey(struct Curl_easy *data, int certnum, const char *algo, len = ((elem.end - q) * 8); if(len) { unsigned int i; - for(i = *(const unsigned char *)q; !(i & 0x80); i <<= 1) + for(i = *q; !(i & 0x80); i <<= 1) len--; } if(len > 32) @@ -1084,8 +1092,8 @@ static CURLcode DNtostr(struct dynbuf *store, struct Curl_asn1Element *dn) CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum, - const char *beg, - const char *end) + const uint8_t *beg, + const uint8_t *end) { struct Curl_X509certificate cert; struct Curl_asn1Element param; @@ -1094,7 +1102,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, struct dynbuf out; CURLcode result = CURLE_OK; unsigned int version; - const char *ptr; + const uint8_t *ptr; int rc; if(!data->set.ssl.certinfo) @@ -1133,7 +1141,7 @@ CURLcode Curl_extract_certinfo(struct Curl_easy *data, /* Version (always fits in less than 32 bits). */ version = 0; for(ptr = cert.version.beg; ptr < cert.version.end; ptr++) - version = (version << 8) | *(const unsigned char *)ptr; + version = (version << 8) | *ptr; if(data->set.ssl.certinfo) { result = curlx_dyn_addf(&out, "%x", version); if(result) diff --git a/lib/vtls/x509asn1.h b/lib/vtls/x509asn1.h index dda55ac77fcc..cf97fffba63f 100644 --- a/lib/vtls/x509asn1.h +++ b/lib/vtls/x509asn1.h @@ -77,11 +77,11 @@ struct Curl_asn1Element; /* ASN.1 parsed element. */ struct Curl_asn1Element { - const char *header; /* Pointer to header byte. */ - const char *beg; /* Pointer to element data. */ - const char *end; /* Pointer to 1st byte after element. */ - unsigned char eclass; /* ASN.1 element class. */ - unsigned char tag; /* ASN.1 element tag. */ + const uint8_t *header; /* Pointer to header byte. */ + const uint8_t *beg; /* Pointer to element data. */ + const uint8_t *end; /* Pointer to 1st byte after element. */ + uint8_t eclass; /* ASN.1 element class. */ + uint8_t tag; /* ASN.1 element tag. */ BIT(constructed); /* Element is constructed. */ }; @@ -109,11 +109,11 @@ struct Curl_X509certificate { */ int Curl_parseX509(struct Curl_X509certificate *cert, - const char *beg, const char *end); + const uint8_t *beg, const uint8_t *end); CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum, - const char *beg, const char *end); + const uint8_t *beg, const uint8_t *end); CURLcode Curl_verifyhost(struct Curl_cfilter *cf, struct Curl_easy *data, - const char *beg, const char *end); + const uint8_t *beg, const uint8_t *end); #endif /* USE_GNUTLS || USE_WOLFSSL || USE_SCHANNEL || USE_MBEDTLS || USE_RUSTLS */ #endif /* HEADER_CURL_X509ASN1_H */ diff --git a/lib/ws.c b/lib/ws.c index a7653df6781b..704f7ee27681 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -123,6 +123,7 @@ struct websocket { struct curl_ws_frame recvframe; /* the current WS FRAME received */ struct ws_cntrl_frame pending; /* a control frame pending to be sent */ size_t sendbuf_payload; /* number of payload bytes in sendbuf */ + BIT(using_reader); /* sending WS data is handled via a client reader */ }; #ifdef CURLVERBOSE @@ -617,13 +618,6 @@ struct ws_cw_dec_ctx { static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, bool blocking); -static CURLcode ws_enc_send(struct Curl_easy *data, - struct websocket *ws, - const uint8_t *buffer, - size_t buflen, - curl_off_t fragsize, - unsigned int flags, - size_t *pnsent); static CURLcode ws_enc_add_pending(struct Curl_easy *data, struct websocket *ws); @@ -669,7 +663,7 @@ static CURLcode ws_cw_dec_next(const uint8_t *buf, size_t buflen, struct ws_cw_dec_ctx *ctx = user_data; struct Curl_easy *data = ctx->data; struct websocket *ws = ctx->ws; - bool auto_pong = !data->set.ws_no_auto_pong; + bool auto_pong = !data->set.ws_raw_mode && !data->set.ws_no_auto_pong; curl_off_t remain; CURLcode result; @@ -759,18 +753,18 @@ static CURLcode ws_cw_write(struct Curl_easy *data, } } - if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) { - failf(data, "[WS] decode ending with %zu frame bytes remaining", - Curl_bufq_len(&ctx->buf)); - result = CURLE_RECV_ERROR; - } - out: - if(!result) { + if(!result && !ws->using_reader) { result = ws_flush(data, ws, Curl_api_is_in_callback(data)); if(result == CURLE_AGAIN) result = CURLE_OK; } + + if(!result && (type & CLIENTWRITE_EOS) && + (!Curl_bufq_is_empty(&ctx->buf) || (ws->dec.state != WS_DEC_INIT))) { + failf(data, "[WS] decode ended with incomplete frame"); + result = CURLE_RECV_ERROR; + } return result; } @@ -1284,13 +1278,22 @@ static CURLcode cr_ws_read(struct Curl_easy *data, return result; } +static curl_off_t cr_ws_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} + static const struct Curl_crtype ws_cr_encode = { "ws-encode", cr_ws_init, cr_ws_read, cr_ws_close, Curl_creader_def_needs_rewind, - Curl_creader_def_total_length, + cr_ws_total_length, Curl_creader_def_resume_from, Curl_creader_def_cntrl, Curl_creader_def_is_paused, @@ -1360,7 +1363,7 @@ CURLcode Curl_ws_request(struct Curl_easy *data, struct dynbuf *req) return result; } -static void ws_conn_dtor(void *key, size_t klen, void *entry) +static void ws_conn_dtor(const void *key, size_t klen, void *entry) { struct websocket *ws = entry; (void)key; @@ -1480,6 +1483,7 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, if(result) goto out; ws_enc_reader = NULL; /* owned by transfer now */ + ws->using_reader = TRUE; } /* start over with sending */ @@ -1534,7 +1538,7 @@ static CURLcode ws_client_collect(const uint8_t *buf, size_t buflen, { struct ws_collect *ctx = userp; struct Curl_easy *data = ctx->data; - bool auto_pong = !data->set.ws_no_auto_pong; + bool auto_pong = !data->set.ws_raw_mode && !data->set.ws_no_auto_pong; curl_off_t remain; CURLcode result = CURLE_OK; @@ -1596,19 +1600,20 @@ CURLcode curl_ws_recv(CURL *curl, void *buffer, struct Curl_eapi_guard guard; CURLcode result = CURLE_OK; - *recv = 0; - *metap = NULL; if(CURL_EAPI_ENTER(&guard, curl, ws_recv, &result)) { struct Curl_easy *data = curl; struct connectdata *conn; struct websocket *ws; struct ws_collect ctx; - if(buflen && !buffer) { + if(!recv || !metap || (buflen && !buffer)) { result = CURLE_BAD_FUNCTION_ARGUMENT; goto out; } + *recv = 0; + *metap = NULL; + conn = data->conn; if(!conn) { /* Unhappy hack with lifetimes of transfers and connection */ @@ -1686,7 +1691,7 @@ CURLcode curl_ws_recv(CURL *curl, void *buffer, 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. */ - if(!data->set.ws_raw_mode && ws->pending.type) { + if(!data->set.ws_raw_mode && !ws->using_reader && ws->pending.type) { CURLcode r2 = ws_enc_add_pending(data, ws); if(!r2) (void)ws_flush(data, ws, Curl_api_is_in_callback(data)); diff --git a/m4/curl-openssl.m4 b/m4/curl-openssl.m4 index 8a1d07b02a4d..61a2e758acf8 100644 --- a/m4/curl-openssl.m4 +++ b/m4/curl-openssl.m4 @@ -265,8 +265,9 @@ if test "x$OPT_OPENSSL" != "xno"; then AC_LANG_PROGRAM([[ #include ]],[[ - int dummy = LIBRESSL_VERSION_NUMBER; - (void)dummy; + #ifndef LIBRESSL_VERSION_NUMBER + #error not LibreSSL + #endif ]]) ],[ AC_MSG_RESULT([yes]) diff --git a/projects/OS400/README.OS400 b/projects/OS400/README.OS400 index 71a78990f378..d1551dcfe72b 100644 --- a/projects/OS400/README.OS400 +++ b/projects/OS400/README.OS400 @@ -167,8 +167,19 @@ entry containing one of the above option. This additional entry holds the CCSID in its value field, and the option field is meaningless. It is not possible to have a string pointer and its CCSID across a function parameter/array boundary. + A single call to this function describes a single field. To support +old-style file data in multiple chunks, it is however possible to list +data options more than once within a single call. Because each data +option may be associated with a length option, these tuples must be +separated from each other by a CURLFORM_CONTENTTYPE option that serves +as a delimiter. In consequence, associated data and length options may not +be separated by a CURLFORM_CONTENTTYPE option, whatever the data option +count is. In all cases, the new-style multi-chunk file definition (several +fields with the same name and filename) must be preferred. Please note that CURLFORM_PTRCONTENTS and CURLFORM_BUFFERPTR are considered unconvertible strings and thus are NOT followed by a CCSID. + The form API is now deprecated by the MIME API and the latter should +be preferred for new designs. _ curl_easy_getinfo_ccsid() The following options are followed by a 'char * *' and a CCSID. Unlike @@ -246,7 +257,6 @@ _ POP3 _ RTSP _ SCP if libssh2 is enabled _ SFTP if libssh2 is enabled -_ SMB _ SMTP _ TELNET _ TFTP diff --git a/projects/OS400/ccsidcurl.c b/projects/OS400/ccsidcurl.c index 8b3b2fe6c6a0..2ee4d71eafc5 100644 --- a/projects/OS400/ccsidcurl.c +++ b/projects/OS400/ccsidcurl.c @@ -48,7 +48,7 @@ #include "os400sys.h" #ifndef SIZE_MAX -#define SIZE_MAX ((size_t) ~0) +#define SIZE_MAX ((size_t)~0) #endif #define ASCII_CCSID 819 /* Use ISO-8859-1 as ASCII. */ @@ -58,14 +58,12 @@ #define ALLOC_GRANULE 8 /* Alloc. granule for curl_formadd_ccsid(). */ - /* A string terminator that works for all CCSIDs. */ -static const char universal_terminator[] = {0, 0, 0, 0}; +static const char universal_terminator[] = { 0, 0, 0, 0 }; /* Freeing const pointers more easily. */ #define untyped_free(p) curlx_free(CURL_UNCONST(p)) - static void makeOS400IconvCode(char buf[ICONV_ID_SIZE], unsigned int ccsid) { /* @@ -144,7 +142,7 @@ static int convert(char *d, size_t dlen, const char *s, size_t slen, i = dlen; - if((int)iconv(cd, (char **) &s, &lslen, &d, &dlen) < 0) + if((int)iconv(cd, (char **)&s, &lslen, &d, &dlen) < 0) i = -1; else i -= dlen; @@ -181,9 +179,9 @@ static CURLcode dyn_addn_CCSID(struct dynbuf *db, int err = 0; dlen = sizeof(buffer); - if((int)iconv(cd, (char **) &mem, - len == CURL_ZERO_TERMINATED ? &dummylen : &len, - &dptr, &dlen) < 0) { + if((int)iconv(cd, (char **)&mem, + len == CURL_ZERO_TERMINATED ? &dummylen : &len, &dptr, + &dlen) < 0) { /* !checksrc! disable ERRNOVAR 1 */ err = errno; } @@ -215,10 +213,10 @@ static CURLcode dyn_addn_CCSID(struct dynbuf *db, dlen = curlx_dyn_len(db); if(len == CURL_ZERO_TERMINATED) { - /* The null terminator has been converted AND counted as a character. + /* The null-terminator has been converted AND counted as a character. * Measure it by an additional conversion and drop it. */ int tlen = convert(buffer, sizeof(buffer), universal_terminator, 1, - ASCII_CCSID, ccsidout); + ASCII_CCSID, ccsidout); if(tlen < 0 || tlen > dlen) { curlx_dyn_free(db); @@ -262,7 +260,7 @@ static struct curl_slist *slist_convert(struct curl_slist *from, nl = Curl_slist_append_nodup(to, s); if(!nl) { curl_slist_free_all(to); - free(s); + untyped_free(s); return NULL; } to = nl; @@ -421,7 +419,7 @@ time_t curl_getdate_ccsid(const char *p, const time_t *unused, s = curl_from_ccsid(p, ccsid); if(!s) - return (time_t) -1; + return (time_t)-1; t = curl_getdate(s, unused); untyped_free(s); @@ -842,7 +840,7 @@ CURLFORMcode curl_formadd_ccsid(struct curl_httppost **httppost, switch(option) { case CURLFORM_END: - forms = NULL; /* Leave array mode. */ + forms = NULL; /* Leave array mode. */ continue; case CURLFORM_ARRAY: @@ -1022,8 +1020,8 @@ CURLFORMcode curl_formadd_ccsid(struct curl_httppost **httppost, struct cfcdata { curl_formget_callback append; - void * arg; - unsigned int ccsid; + void *arg; + unsigned int ccsid; }; static size_t formget_callback_ccsid(void *arg, const char *buf, size_t len) @@ -1041,7 +1039,7 @@ static size_t formget_callback_ccsid(void *arg, const char *buf, size_t len) curlx_dyn_init(&db, MAX_CONV_EXPANSION * CURL_MAX_INPUT_LENGTH); if(dyn_addn_CCSID(&db, buf, len, ASCII_CCSID, p->ccsid)) - return (size_t) -1; + return (size_t)-1; olen = curlx_dyn_len(&db); ret = p->append(p->arg, curlx_dyn_ptr(&db), olen); @@ -1057,7 +1055,7 @@ int curl_formget_ccsid(struct curl_httppost *form, void *arg, lcfc.append = append; lcfc.arg = arg; lcfc.ccsid = ccsid; - return curl_formget(form, (void *) &lcfc, formget_callback_ccsid); + return curl_formget(form, (void *)&lcfc, formget_callback_ccsid); } CURLcode curl_easy_setopt_ccsid(CURL *curl, CURLoption tag, ...) @@ -1174,7 +1172,7 @@ CURLcode curl_easy_setopt_ccsid(CURL *curl, CURLoption tag, ...) case CURLOPT_USERNAME: case CURLOPT_USERPWD: case CURLOPT_XOAUTH2_BEARER: - /* END TRANSLATABLE STRING OPTIONS */ + /* END TRANSLATABLE STRING OPTIONS */ s = va_arg(arg, const char *); ccsid = va_arg(arg, unsigned int); @@ -1225,7 +1223,7 @@ CURLcode curl_easy_setopt_ccsid(CURL *curl, CURLoption tag, ...) copying them. */ result = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, s); if(!result) { - data->set.str_copypostfields = CURL_UNCONST(s); /* Adopt. */ + data->set.str_copypostfields = CURL_UNCONST(s); /* Adopt. */ if(pfsize != -1) data->set.postfieldsize = len; } @@ -1243,9 +1241,9 @@ CURLcode curl_easy_setopt_ccsid(CURL *curl, CURLoption tag, ...) if(bp && bp->data && bp->len && ccsid != NOCONV_CCSID && ccsid != ASCII_CCSID) { - result = dyn_addn_CCSID(&db, bp->data, bp->len, ccsid, ASCII_CCSID); - if(result) - break; + result = dyn_addn_CCSID(&db, bp->data, bp->len, ccsid, ASCII_CCSID); + if(result) + break; blob.data = curlx_dyn_ptr(&db); blob.len = curlx_dyn_len(&db); @@ -1326,9 +1324,10 @@ char *curl_pushheader_byname_ccsid(struct curl_pushheaders *h, return CURL_UNCONST(d); } -static CURLcode -mime_string_call(curl_mimepart *part, const char *string, unsigned int ccsid, - CURLcode (*mimefunc)(curl_mimepart *part, const char *string)) +static CURLcode mime_string_call(curl_mimepart *part, const char *string, + unsigned int ccsid, + CURLcode (*mimefunc)(curl_mimepart *part, + const char *string)) { const char *s; CURLcode result; @@ -1442,8 +1441,8 @@ CURLUcode curl_url_set_ccsid(CURLU *handle, CURLUPart what, const char *part, return result; } -const struct curl_easyoption * -curl_easy_option_by_name_ccsid(const char *name, unsigned int ccsid) +const struct curl_easyoption *curl_easy_option_by_name_ccsid( + const char *name, unsigned int ccsid) { const struct curl_easyoption *option = NULL; @@ -1460,9 +1459,8 @@ curl_easy_option_by_name_ccsid(const char *name, unsigned int ccsid) } /* Return option name in the given ccsid. */ -const char * -curl_easy_option_get_name_ccsid(const struct curl_easyoption *option, - unsigned int ccsid) +const char *curl_easy_option_get_name_ccsid( + const struct curl_easyoption *option, unsigned int ccsid) { const char *name = NULL; diff --git a/projects/OS400/ccsidcurl.h b/projects/OS400/ccsidcurl.h index f38e57967c2c..43312fd8adc2 100644 --- a/projects/OS400/ccsidcurl.h +++ b/projects/OS400/ccsidcurl.h @@ -92,10 +92,9 @@ CURL_EXTERN CURLUcode curl_url_set_ccsid(CURLU *handle, CURLUPart what, const char *part, unsigned int flags, unsigned int ccsid); CURL_EXTERN const struct curl_easyoption *curl_easy_option_by_name_ccsid( - const char *name, unsigned int ccsid); + const char *name, unsigned int ccsid); CURL_EXTERN const char *curl_easy_option_get_name_ccsid( - const struct curl_easyoption *option, - unsigned int ccsid); + const struct curl_easyoption *option, unsigned int ccsid); CURL_EXTERN const char *curl_url_strerror_ccsid(CURLUcode error, unsigned int ccsid); CURL_EXTERN CURLHcode curl_easy_header_ccsid(CURL *easy, const char *name, diff --git a/projects/OS400/curl.inc.in b/projects/OS400/curl.inc.in index 5b21429fed91..39fe4a04e053 100644 --- a/projects/OS400/curl.inc.in +++ b/projects/OS400/curl.inc.in @@ -79,7 +79,7 @@ * d CURL_GLOBAL_SSL... d c X'00000001' - d CURL_GLOBAL_WIN32... + d CURL_GLOBAL_WINSOCK... d c X'00000002' d CURL_GLOBAL_ALL... d c X'00000003' diff --git a/projects/OS400/curlcl.c b/projects/OS400/curlcl.c index 8085307f31ca..76bf873b296a 100644 --- a/projects/OS400/curlcl.c +++ b/projects/OS400/curlcl.c @@ -39,13 +39,13 @@ /* Variable-length string, with 16-bit length. */ struct vary2 { short len; - char string[5000]; + char string[5000]; }; /* Arguments from CL command. */ struct arguments { - char *pgm; /* Program name. */ - struct vary2 *cmdargs; /* Command line arguments. */ + char *pgm; /* Program name. */ + struct vary2 *cmdargs; /* Command line arguments. */ }; static int is_ifs(char c) diff --git a/projects/OS400/curlmain.c b/projects/OS400/curlmain.c index fa2e93d35f47..254b66ea382c 100644 --- a/projects/OS400/curlmain.c +++ b/projects/OS400/curlmain.c @@ -36,17 +36,17 @@ #include /* Do not use qadrt.h since it defines unneeded static procedures. */ -extern void QadrtInit(void); -extern int QadrtFreeConversionTable(void); -extern int QadrtFreeEnviron(void); -extern char * setlocale_a(int, const char *); +extern void QadrtInit(void); +extern int QadrtFreeConversionTable(void); +extern int QadrtFreeEnviron(void); +extern char *setlocale_a(int, const char *); /* The ASCII main program. */ -extern int main_a(int argc, char *argv[]); +extern int main_a(int argc, char *argv[]); /* Global values of original EBCDIC arguments. */ -int ebcdic_argc; -char ** ebcdic_argv; +int ebcdic_argc; +char **ebcdic_argv; int main(int argc, char *argv[]) { @@ -61,7 +61,7 @@ int main(int argc, char *argv[]) char dummybuf[128]; /* To/From codes are 32-byte strings with reserved fields initialized to ZEROs */ - const char tocode[32] = { "IBMCCSID01208" }; /* Use UTF-8. */ + const char tocode[32] = { "IBMCCSID01208" }; /* Use UTF-8. */ const char fromcode[32] = { "IBMCCSID000000000010" }; ebcdic_argc = argc; diff --git a/projects/OS400/os400sys.c b/projects/OS400/os400sys.c index bea84500629d..af97616d5cfb 100644 --- a/projects/OS400/os400sys.c +++ b/projects/OS400/os400sys.c @@ -74,8 +74,8 @@ static char *buffer_undef(localkey_t key, long size); static char *buffer_threaded(localkey_t key, long size); static char *buffer_unthreaded(localkey_t key, long size); -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_key_t thdkey; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_key_t thdkey; static struct buffer_t *locbufs; char *(*Curl_thread_buffer)(localkey_t key, long size) = buffer_undef; diff --git a/projects/vms/build_vms.com b/projects/vms/build_vms.com index d79fdb594079..85f6a110a110 100644 --- a/projects/vms/build_vms.com +++ b/projects/vms/build_vms.com @@ -38,7 +38,7 @@ $! Always link a debug image. $! NOIEEE Do not use IEEE floating point. (Alpha/I64) $! VAX must always use DFLOAT $! NOLARGE Disable large-file support if large file support available. -$! (Non-VAX, VMS >= v7.2.) +$! (Non-VAX, VMS >= 7.2.) $! NOLDAP Disable LDAP support if LDAP is available. $! NOKERBEROS Disable Kerberos support if Kerberos is available. $! LIST Create C compiler listings and linker maps. diff --git a/projects/vms/curl_gnv_build_steps.txt b/projects/vms/curl_gnv_build_steps.txt index 88af34f5802e..edc3bd9003ad 100644 --- a/projects/vms/curl_gnv_build_steps.txt +++ b/projects/vms/curl_gnv_build_steps.txt @@ -20,7 +20,7 @@ Currently building curl using GNV takes longer than building curl via DCL. The GNV procedure actually uses the same configure and makefiles that Unix builds use. -Building curl on OpenVMS using GNV requires GNV v2.1-2 or the updated +Building curl on OpenVMS using GNV requires GNV 2.1-2 or the updated images that are available via anonymous FTP at encompasserve.org in the gnv directory. It also requires the GNV Bash 4.2.45 kit as an update from the same location or from the sourceforge.net GNV project. diff --git a/projects/vms/readme b/projects/vms/readme index 9db0ee38735b..e62104311dfa 100644 --- a/projects/vms/readme +++ b/projects/vms/readme @@ -19,8 +19,8 @@ curl_gnv_build_steps.txt and other useful information. Prerequisites: -OpenVMS v7.0 or later (any platform) -DECC v6.5 or later +OpenVMS 7.0 or later (any platform) +DECC 6.5 or later OpenSSL or HP SSL, if you want SSL support What is Here: diff --git a/scripts/cd2nroff b/scripts/cd2nroff index 02f97bc41efc..2dbc1a316e2a 100755 --- a/scripts/cd2nroff +++ b/scripts/cd2nroff @@ -175,8 +175,6 @@ my %knownprotos = ( 'RTSP' => 1, 'SCP' => 1, 'SFTP' => 1, - 'SMB' => 1, - 'SMBS' => 1, 'SMTP' => 1, 'SMTPS' => 1, 'TELNET' => 1, diff --git a/scripts/mdlinkcheck b/scripts/mdlinkcheck index 4d30e90d392e..3da3faa48246 100755 --- a/scripts/mdlinkcheck +++ b/scripts/mdlinkcheck @@ -189,7 +189,7 @@ sub checkurl { $url =~ s/\+/%2B/g; my @content; - if(open(my $fh, '-|', 'curl', '-ILfsm10', '--retry', '2', '--retry-delay', '5', + if(open(my $fh, '-|', 'curl', '-ILfsm10', '--retry', '2', '--retry-delay', '5', '--retry-max-time', '60', '-A', 'Mozilla/curl.se link-probe', $url)) { @content = <$fh>; close $fh; diff --git a/scripts/spacecheck.pl b/scripts/spacecheck.pl index 44274d0a21b4..af79206179fe 100755 --- a/scripts/spacecheck.pl +++ b/scripts/spacecheck.pl @@ -28,6 +28,12 @@ use File::Basename; +my @executable = ( + '\.(pl|py|sh)$', + '^\.github/scripts/', + '^scripts/', +); + my @tabs = ( '^m4/zz40-xc-ovr\.m4$', 'Makefile\.(am|example)$', @@ -50,15 +56,15 @@ ); my @longline = ( - '\.github/workflows/windows\.yml$', - '^renovate\.json$', + '^\.github/renovate\.json$', + '^\.github/workflows/windows\.yml$', '^docs/DISTROS\.md$', '^projects/Windows/tmpl/.+\.vcxproj$', '^tests/data/test', ); my @non_ascii_allowed = ( - '\xC3\xB6', # UTF-8 for https://codepoints.net/U+00F6 LATIN SMALL LETTER O WITH DIAERESIS + '\xC3\xB6', # UTF-8 for U+00F6 LATIN SMALL LETTER O WITH DIAERESIS ); my $non_ascii_allowed = join(', ', @non_ascii_allowed); @@ -116,6 +122,13 @@ sub eol_detect { my @err = (); + if(!fn_match($filename, @executable)) { + my $mode = (stat($filename))[2]; + if($mode & 0111) { + push @err, sprintf('file has executable bit(s) set: %o', $mode); + } + } + if(length($filename) > $max_path_len) { push @err, sprintf('long (%d > %d) path', length($filename), $max_path_len); } diff --git a/scripts/wcurl b/scripts/wcurl index 5fb68275efb1..d4914ee5aa37 100755 --- a/scripts/wcurl +++ b/scripts/wcurl @@ -29,7 +29,7 @@ # Stop on errors, usage of unset variables and disable glob expansion. set -euf -VERSION="2026.08.30" +VERSION="2026.10.12" PROGRAM_NAME="$(basename "$0")" readonly PROGRAM_NAME @@ -74,7 +74,9 @@ Options: -h, --help: Print this usage message. : Any option supported by curl can be set here. This is not used by wcurl; it is - instead forwarded to the curl invocation. + instead forwarded to the curl invocation. The value is split on whitespace and + quotes are not interpreted; use '--config ' for arguments containing + whitespace. Never build it from untrusted input. : URL to be downloaded. Anything that is not a parameter is considered an URL. Whitespace is percent-encoded and the URL is passed to curl, which @@ -202,13 +204,17 @@ percent_decode() # Print the percent-decoded filename portion of the given URL. get_url_filename() { - # Remove protocol and query string if present. + # Remove protocol, query string and URL fragments if present. hostname_and_path="$(printf %s "${1}" | sed -e 's,^[^/]*//,,' -e 's,[?#].*$,,')" # If what remains contains a slash, there is a path; return it percent-decoded. case "${hostname_and_path}" in # sed to remove everything preceding the last '/', e.g.: "example/something" becomes "something" # sed to also replace '\' with the percent_encoded %5C and ':' with %3A - */*) percent_decode "$(printf %s "${hostname_and_path}" | sed -e 's,^.*/,,' -e 's,\\,%5C,g' -e 's,:,%3A,g')" ;; + */*) + filename="$(percent_decode "$(printf %s "${hostname_and_path}" | sed -e 's,^.*/,,' -e 's,\\,%5C,g' -e 's,:,%3A,g')")" + [ "${filename}" = "-" ] && filename="" + printf %s "${filename}" + ;; esac # No slash means there was only a hostname and no path; return empty string. } diff --git a/src/Makefile.inc b/src/Makefile.inc index 84c0a3a02de7..45ee6a30b8ea 100644 --- a/src/Makefile.inc +++ b/src/Makefile.inc @@ -36,7 +36,7 @@ CURLX_CFILES = \ ../lib/curlx/base64.c \ ../lib/curlx/basename.c \ ../lib/curlx/dynbuf.c \ - ../lib/curlx/fopen.c \ + ../lib/curlx/win32-fopen.c \ ../lib/curlx/multibyte.c \ ../lib/curlx/nonblock.c \ ../lib/curlx/strcopy.c \ @@ -55,7 +55,7 @@ CURLX_HFILES = \ ../lib/curlx/base64.h \ ../lib/curlx/basename.h \ ../lib/curlx/dynbuf.h \ - ../lib/curlx/fopen.h \ + ../lib/curlx/win32-fopen.h \ ../lib/curlx/multibyte.h \ ../lib/curlx/nonblock.h \ ../lib/curlx/strcopy.h \ diff --git a/src/config2setopts.c b/src/config2setopts.c index b6c242dad6cb..736cdd3db1a2 100644 --- a/src/config2setopts.c +++ b/src/config2setopts.c @@ -202,12 +202,11 @@ static CURLcode url_proto_and_rewrite(char **url, return result; } -static CURLcode ssh_setopts(struct OperationConfig *config, CURL *curl, - const char *use_proto) +static CURLcode ssh_setopts(struct OperationConfig *config, CURL *curl) { CURLcode result; - if(use_proto != proto_scp && use_proto != proto_sftp) + if(!proto_scp && !proto_sftp) return CURLE_OK; /* SSH and SSL private key uses same command-line option */ @@ -236,7 +235,25 @@ static CURLcode ssh_setopts(struct OperationConfig *config, CURL *curl, if(!known) return CURLE_OUT_OF_MEMORY; } + else if(!config->hostpubmd5 && !config->hostpubsha256) { + /* couldn't find an existing one, instead set one that MIGHT work as + we don't verify the host using other means */ + struct dynbuf k; + char *home = getenv("HOME"); + curlx_dyn_init(&k, 256); + + /* If the HOME environment variable is missing this renders a path + that is likely to not work but the HOME one also does not exist! + */ + result = curlx_dyn_addf(&k, + "%s" DIR_CHAR ".ssh" DIR_CHAR "known_hosts", + home ? home : ""); + if(result) + return result; + known = curlx_dyn_ptr(&k); + } } + if(known) { result = my_setopt_str(curl, CURLOPT_SSH_KNOWNHOSTS, known); if(result) { @@ -247,12 +264,6 @@ static CURLcode ssh_setopts(struct OperationConfig *config, CURL *curl, /* store it in global to avoid repeated checks */ config->knownhosts = known; } - else if(!config->hostpubmd5 && !config->hostpubsha256) { - errorf("Could not find a known_hosts file"); - return CURLE_FAILED_INIT; - } - else - warnf("Could not find a known_hosts file"); } return CURLE_OK; /* ignore if SHA256 did not work */ } @@ -261,9 +272,9 @@ static long tlsversion(unsigned char mintls, unsigned char maxtls) { long tlsver = 0; - if(!mintls) { /* minimum is at default */ - /* minimum is set to default, which we want to be 1.2 */ - if(maxtls && (maxtls < 3)) + if(!mintls && /* minimum is at default */ + /* minimum is set to default, which we want to be 1.2 */ + maxtls && (maxtls < 3)) { /* max is set lower than 1.2 and minimum is default, change minimum to the same as max */ mintls = maxtls; @@ -409,11 +420,13 @@ static CURLcode ssl_setopts(struct OperationConfig *config, CURL *curl) MY_SETOPT_STR(curl, CURLOPT_SSLKEYTYPE, config->key_type); MY_SETOPT_STR(curl, CURLOPT_PROXY_SSLKEYTYPE, config->proxy_key_type); - /* libcurl default is strict verifyhost -> 1L, verifypeer -> 1L */ - if(config->insecure_ok) { - my_setopt_long(curl, CURLOPT_SSL_VERIFYPEER, 0); - my_setopt_long(curl, CURLOPT_SSL_VERIFYHOST, 0); - } + /* libcurl default is strict verifyhost -> 1L, verifypeer -> 1L. Set + these for every operation so --libcurl output does not retain + --insecure from a preceding --next operation. */ + my_setopt_long_force(curl, CURLOPT_SSL_VERIFYPEER, + config->insecure_ok ? 0 : 1); + my_setopt_long_force(curl, CURLOPT_SSL_VERIFYHOST, + config->insecure_ok ? 0 : 1); if(config->doh_insecure_ok) { my_setopt_long(curl, CURLOPT_DOH_SSL_VERIFYPEER, 0); @@ -495,12 +508,12 @@ static CURLcode ssl_setopts(struct OperationConfig *config, CURL *curl) if(feature_ech) { /* only if enabled in libcurl */ - if(config->ech) /* only if set (optional) */ - MY_SETOPT_STR(curl, CURLOPT_ECH, config->ech); if(config->ech_public) /* only if set (optional) */ MY_SETOPT_STR(curl, CURLOPT_ECH, config->ech_public); if(config->ech_config) /* only if set (optional) */ MY_SETOPT_STR(curl, CURLOPT_ECH, config->ech_config); + if(config->ech) /* only if set (optional) */ + MY_SETOPT_STR(curl, CURLOPT_ECH, config->ech); } if(config->engine) @@ -528,9 +541,10 @@ static CURLcode cookie_setopts(struct OperationConfig *config, CURL *curl) struct dynbuf cookies; struct curl_slist *cl; - /* The maximum size needs to match MAX_NAME in cookie.h */ -#define MAX_COOKIE_LINE 8200 - curlx_dyn_init(&cookies, MAX_COOKIE_LINE); + /* cap the size of the generated cookie header to avoid servers from going + sour on us */ +#define MAX_REQUEST_COOKIE 8200 + curlx_dyn_init(&cookies, MAX_REQUEST_COOKIE); for(cl = config->cookies; cl; cl = cl->next) { if(cl == config->cookies) result = curlx_dyn_add(&cookies, cl->data); @@ -539,7 +553,7 @@ static CURLcode cookie_setopts(struct OperationConfig *config, CURL *curl) ISBLANK(cl->data[0]) ? "" : " ", cl->data); if(result) { warnf("skipped provided cookie, the cookie header " - "would go over %d bytes", MAX_COOKIE_LINE); + "would go over %d bytes", MAX_REQUEST_COOKIE); return result; } } @@ -873,16 +887,25 @@ static CURLcode proxy_setopts(struct OperationConfig *config, CURL *curl) return result; } +static CURLcode check_post_resume(struct OperationConfig *config) +{ + if(config->use_resume && + (config->resume_from || config->resume_from_current)) { + errorf("(%d) HTTP upload cannot be resumed", + CURLE_BAD_FUNCTION_ARGUMENT); + config->synthetic_error = TRUE; + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return CURLE_OK; +} + static CURLcode setopt_post(struct OperationConfig *config, CURL *curl) { CURLcode result = CURLE_OK; switch(config->httpreq) { case TOOL_HTTPREQ_SIMPLEPOST: - if(config->resume_from) { - errorf("cannot mix --continue-at with --data"); - result = CURLE_FAILED_INIT; - } - else { + result = check_post_resume(config); + if(!result) { MY_SETOPT_STR(curl, CURLOPT_POSTFIELDS, curlx_dyn_ptr(&config->postdata)); my_setopt_offt(curl, CURLOPT_POSTFIELDSIZE_LARGE, @@ -893,11 +916,8 @@ static CURLcode setopt_post(struct OperationConfig *config, CURL *curl) /* free previous remainders */ curl_mime_free(config->mimepost); config->mimepost = NULL; - if(config->resume_from) { - errorf("cannot mix --continue-at with --form"); - result = CURLE_FAILED_INIT; - } - else { + result = check_post_resume(config); + if(!result) { result = tool2curlmime(curl, config->mimeroot, &config->mimepost); if(!result) result = my_setopt_mimepost(curl, CURLOPT_MIMEPOST, config->mimepost); @@ -1029,7 +1049,7 @@ static CURLcode protocol_setopts(struct OperationConfig *config, if(result) return result; - result = ssh_setopts(config, curl, use_proto); + result = ssh_setopts(config, curl); if(setopt_bad(result)) return result; @@ -1231,5 +1251,9 @@ CURLcode config2setopts(struct OperationConfig *config, result = mail_and_sasl_setopts(config, curl); if(!setopt_bad(result)) result = misc_setopts(config, curl); + + if(!result && config->synthetic_error) + result = CURLE_FAILED_INIT; + return result; } diff --git a/src/curlinfo.c b/src/curlinfo.c index 87f00032b139..8eee76358ff5 100644 --- a/src/curlinfo.c +++ b/src/curlinfo.c @@ -224,9 +224,10 @@ static const char * const disabled[] = { , "win32-ca-searchpath: " -#if !defined(_WIN32) || \ - (defined(CURL_WINDOWS_UWP) || \ - defined(CURL_DISABLE_CA_SEARCH) || defined(CURL_CA_SEARCH_SAFE)) +#if !defined(_WIN32) || \ + (defined(CURL_WINDOWS_UWP) || \ + defined(CURL_DISABLE_CA_SEARCH) || \ + defined(CURL_CA_SEARCH_SAFE)) "OFF" #else "ON" diff --git a/src/tool_cb_dbg.c b/src/tool_cb_dbg.c index 2a69ce0f1179..ea2ec964b08d 100644 --- a/src/tool_cb_dbg.c +++ b/src/tool_cb_dbg.c @@ -225,18 +225,17 @@ int tool_debug_cb(CURL *handle, curl_infotype type, case CURLINFO_DATA_IN: case CURLINFO_SSL_DATA_IN: case CURLINFO_SSL_DATA_OUT: - if(!traced_data) { - /* if the data is output to a tty and we are sending this debug trace - to stderr or stdout, we do not display the alert about the data not - being shown as the data _is_ shown then not via this function */ - if(!global->isatty || - ((output != tool_stderr) && (output != stdout))) { - if(!newl) - log_line_start(output, timebuf, idsbuf, type); - curl_mfprintf(output, "[%zu bytes data]\n", size); - newl = FALSE; - traced_data = TRUE; - } + if(!traced_data && + /* if the data is output to a tty and we are sending this debug trace + to stderr or stdout, we do not display the alert about the data not + being shown as the data _is_ shown then not via this function */ + (!global->isatty || + ((output != tool_stderr) && (output != stdout)))) { + if(!newl) + log_line_start(output, timebuf, idsbuf, type); + curl_mfprintf(output, "[%zu bytes data]\n", size); + newl = FALSE; + traced_data = TRUE; } break; default: /* nada */ diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c index 014e4b832ed1..bd846062a0de 100644 --- a/src/tool_cb_hdr.c +++ b/src/tool_cb_hdr.c @@ -248,6 +248,28 @@ int tool_write_headers(struct HdrCbData *hdrcbdata, FILE *stream) return rc; } +/* Store a header until the output filename has been decided. */ +static size_t buffer_header(struct HdrCbData *hdrcbdata, + const char *str, size_t cb) +{ + char *clone = curlx_memdup0(str, cb); + if(clone) { + struct curl_slist *old = hdrcbdata->headlist; + hdrcbdata->headlist = curl_slist_append(old, clone); + curlx_free(clone); + if(!hdrcbdata->headlist) { + curl_slist_free_all(old); + return CURL_WRITEFUNC_ERROR; + } + } + else { + curl_slist_free_all(hdrcbdata->headlist); + hdrcbdata->headlist = NULL; + return CURL_WRITEFUNC_ERROR; + } + return cb; +} + /* * Write etag to file when --etag-save option is given. */ @@ -388,24 +410,8 @@ static size_t content_disposition(const char *str, const char *end, if(hdrcbdata->honor_cd_filename && hdrcbdata->config->show_headers) { - /* still awaiting the Content-Disposition header, store the header in - memory. Since it is not null-terminated, we need an extra dance. */ - char *clone = curlx_memdup0(str, cb); - if(clone) { - struct curl_slist *old = hdrcbdata->headlist; - hdrcbdata->headlist = curl_slist_append(old, clone); - curlx_free(clone); - if(!hdrcbdata->headlist) { - curl_slist_free_all(old); - return CURL_WRITEFUNC_ERROR; - } - } - else { - curl_slist_free_all(hdrcbdata->headlist); - hdrcbdata->headlist = NULL; - return CURL_WRITEFUNC_ERROR; - } - return cb; /* done for now */ + /* still awaiting the Content-Disposition header */ + return buffer_header(hdrcbdata, str, cb); } return 0; /* ok */ @@ -426,7 +432,7 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) const char *str = ptr; const size_t cb = size * nmemb; const char *end = ptr + cb; - const char *scheme = NULL; + bool cd_checked = FALSE; if(!per->config) return CURL_WRITEFUNC_ERROR; @@ -444,6 +450,16 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) memset(outs->utf8seq, 0, sizeof(outs->utf8seq)); #endif + /* If the last header was empty, check scheme again as we follow + * protocol switches on redirects */ + if(!per->out_scheme || per->was_last_header_empty) { + char *scheme; + curl_easy_getinfo(per->curl, CURLINFO_SCHEME, &scheme); + per->out_scheme = proto_token(scheme); + per->num_headers = 0; + } + per->was_last_header_empty = (ptr[0] == '\r' || ptr[0] == '\n'); + /* * Write header data when curl option --dump-header (-D) is given. */ @@ -459,9 +475,7 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) } } - curl_easy_getinfo(per->curl, CURLINFO_SCHEME, &scheme); - scheme = proto_token(scheme); - if((scheme == proto_http || scheme == proto_https)) { + if((per->out_scheme == proto_http || per->out_scheme == proto_https)) { long response = 0; curl_easy_getinfo(per->curl, CURLINFO_RESPONSE_CODE, &response); @@ -486,25 +500,28 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) size_t rc = content_disposition(str, end, cb, per, response); if(rc) return rc; + /* remember that this was checked to avoid duplicating it further + down */ + cd_checked = TRUE; } } - if(hdrcbdata->config->writeout) { - const char *value = memchr(ptr, ':', cb); - if(value) { - if(per->was_last_header_empty) - per->num_headers = 0; - per->was_last_header_empty = FALSE; - per->num_headers++; - } - else if(ptr[0] == '\r' || ptr[0] == '\n') - per->was_last_header_empty = TRUE; - } + + if(hdrcbdata->config->writeout && memchr(ptr, ':', cb)) + per->num_headers++; + if(hdrcbdata->config->show_headers && !outs->out_null && - (scheme == proto_http || scheme == proto_https || - scheme == proto_rtsp || scheme == proto_file)) { + (per->out_scheme == proto_http || per->out_scheme == proto_https || + per->out_scheme == proto_rtsp || per->out_scheme == proto_file)) { /* bold headers only for selected protocols */ const char *value = NULL; + /* Informational responses, and ETag headers handled by --etag-save, + bypass the content-disposition handling above. Do not open the output + file until the filename decision is complete. Only for HTTP(S). */ + if(hdrcbdata->honor_cd_filename && !cd_checked && + (per->out_scheme == proto_http || per->out_scheme == proto_https)) + return buffer_header(hdrcbdata, str, cb); + if(!outs->stream && !tool_create_output_file(outs, per->config)) return CURL_WRITEFUNC_ERROR; diff --git a/src/tool_cb_wrt.c b/src/tool_cb_wrt.c index c67e3975d2d1..2a17df0745d3 100644 --- a/src/tool_cb_wrt.c +++ b/src/tool_cb_wrt.c @@ -34,6 +34,42 @@ #define OPENMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) #endif +#define MAX_CLOBBER_ATTEMPTS 9999 + +/* 'fname' contains [path + ] file name */ +static bool clobber_name(struct dynbuf *fbuf, const char *fname, + int next_num) +{ + /* skip the directory, if present */ + const char *slash = strrchr(fname, '/'); + const char *base = fname; + const char *dot; +#ifdef _WIN32 + const char *bslash = strrchr(fname, '\\'); + /* use the one furthest away */ + if(bslash && (!slash || (bslash > slash))) + slash = bslash; +#endif + if(slash) + base = slash + 1; + + dot = strrchr(base, '.'); + curlx_dyn_reset(fbuf); + if(dot && (dot != base) && dot[1]) { + /* there is an extension after the dot and the dot is not the first file + name character */ + int prefix = (int)(dot - fname); + if(curlx_dyn_addf(fbuf, "%.*s-%d.%s", prefix, fname, next_num, dot + 1)) + return FALSE; + } + else { + /* without any extension */ + if(curlx_dyn_addf(fbuf, "%s.%d", fname, next_num)) + return FALSE; + } + return TRUE; +} + /* create/open a local file for writing, return TRUE on success */ bool tool_create_output_file(struct OutStruct *outs, struct OperationConfig *config) @@ -62,14 +98,25 @@ bool tool_create_output_file(struct OutStruct *outs, int next_num = 1; struct dynbuf fbuffer; char *newfile; + int max_num = MAX_CLOBBER_ATTEMPTS; +#ifdef DEBUGBUILD + char *p = curl_getenv("CURL_MAX_CLOBBER"); + if(p) { + const char *pp = p; + curl_off_t val; + if(!curlx_str_number(&pp, &val, 100000)) + max_num = (int)val; + curl_free(p); + } +#endif curlx_dyn_init(&fbuffer, 1025); /* !checksrc! disable ERRNOVAR 1 */ while(fd == -1 && /* have not successfully opened a file */ - (errno == EEXIST || errno == EISDIR) && /* because we keep having files that already exist */ - next_num < 100 /* and we have not reached the retry limit */) { - curlx_dyn_reset(&fbuffer); - if(curlx_dyn_addf(&fbuffer, "%s.%d", fname, next_num)) + (errno == EEXIST || errno == EISDIR) && + /* and we have not reached the retry limit */ + (next_num < max_num)) { + if(!clobber_name(&fbuffer, fname, next_num)) return FALSE; next_num++; do { @@ -256,14 +303,6 @@ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) return bytes; #ifdef DEBUGBUILD - { - char *tty = curl_getenv("CURL_ISATTY"); - if(tty) { - is_tty = TRUE; - curl_free(tty); - } - } - if(config->show_headers) { if(bytes > (size_t)CURL_MAX_HTTP_HEADER) { warnf("Header data size exceeds write limit"); @@ -316,7 +355,6 @@ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) warnf("Binary output can mess up your terminal. " "Use \"--output -\" to tell curl to output it to your terminal " "anyway, or consider \"--output \" to save to a file."); - config->synthetic_error = TRUE; return CURL_WRITEFUNC_ERROR; } } @@ -333,10 +371,9 @@ size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) else #endif { - if(per->hdrcbdata.headlist) { - if(tool_write_headers(&per->hdrcbdata, outs->stream)) - return CURL_WRITEFUNC_ERROR; - } + if(per->hdrcbdata.headlist && + tool_write_headers(&per->hdrcbdata, outs->stream)) + return CURL_WRITEFUNC_ERROR; rc = fwrite(buffer, sz, nmemb, outs->stream); } diff --git a/src/tool_dirhie.c b/src/tool_dirhie.c index 82377a718edc..d4b8947f99be 100644 --- a/src/tool_dirhie.c +++ b/src/tool_dirhie.c @@ -27,17 +27,8 @@ #include "tool_msgs.h" #ifdef UNITTESTS -# define toolx_mkdir(x, y) create_dir_hierarchy_trace_mkdir(x) -#elif defined(_WIN32) -# include -# define toolx_mkdir(x, y) _mkdir(x) -#elif defined(MSDOS) && !defined(__DJGPP__) -# define toolx_mkdir(x, y) mkdir(x) -#else -# define toolx_mkdir mkdir -#endif +#define toolx_mkdir(x, y) create_dir_hierarchy_trace_mkdir(x) -#ifdef UNITTESTS static struct dynbuf mkdir_results; UNITTEST struct dynbuf *create_dir_hierarchy_trace_dynres(void) @@ -51,6 +42,8 @@ static int create_dir_hierarchy_trace_mkdir(const char *dir) curlx_dyn_add(&mkdir_results, dir) || curlx_dyn_add(&mkdir_results, "|") ? -1 : 0; } +#else +#define toolx_mkdir curlx_mkdir #endif static void show_dir_errno(const char *name) @@ -117,7 +110,7 @@ CURLcode create_dir_hierarchy(const char *outfile) size_t seplen = strspn(outfile, PATH_DELIMITERS); size_t len = strcspn(&outfile[seplen], PATH_DELIMITERS); - /* the last path component is the file and it ends with a null byte */ + /* the last path component is the file and it ends with a NUL byte */ if(!outfile[len + seplen]) break; diff --git a/src/tool_doswin.c b/src/tool_doswin.c index be6cb510d075..8e2bff535664 100644 --- a/src/tool_doswin.c +++ b/src/tool_doswin.c @@ -272,6 +272,40 @@ static SANITIZEcode msdosify(char ** const sanitized, const char *file_name, } #endif /* MSDOS */ +/* return the number of bytes of the reserved prefix, zero if none exists */ +static int prefix_reserved(const char *p) +{ + if(curl_strnequal(p, "COM", 3) || curl_strnequal(p, "LPT", 3)) { + const uint8_t *up = (const uint8_t *)p; + uint8_t super; + int len; + if(('1' <= p[3]) && (p[3] <= '9')) + return 4; + + /* to complicate things, Windows considers superscript 1, 2, 3 to be valid + numbers and they can be provided in ISO8859-1 or UTF-8. Avoid either + version. */ + len = (up[3] == 0xc2) ? /* UTF-8 */ 4 : 3; + super = up[len]; + + switch(super) { + case 0xb9: /* superscript '1' */ + case 0xb2: /* superscript '2' */ + case 0xb3: /* superscript '3' */ + return len + 1; + } + return 0; + } + else if(curl_strnequal(p, "CON", 3) || + curl_strnequal(p, "PRN", 3) || + curl_strnequal(p, "AUX", 3) || + curl_strnequal(p, "NUL", 3)) + return 3; + else if(curl_strnequal(p, "CLOCK$", 6)) + return 6; + return 0; +} + /* * Rename file_name if it is a reserved dos device name. * @@ -334,13 +368,7 @@ static SANITIZEcode rename_if_reserved_dos(char ** const sanitized, */ for(p = buffer; p; p = (p == buffer && buffer != base ? base : NULL)) { size_t p_len; - int x = (curl_strnequal(p, "CON", 3) || - curl_strnequal(p, "PRN", 3) || - curl_strnequal(p, "AUX", 3) || - curl_strnequal(p, "NUL", 3)) ? 3 : - curl_strnequal(p, "CLOCK$", 6) ? 6 : - (curl_strnequal(p, "COM", 3) || curl_strnequal(p, "LPT", 3)) ? - (('1' <= p[3] && p[3] <= '9') ? 4 : 3) : 0; + int x = prefix_reserved(p); if(!x) continue; diff --git a/src/tool_easysrc.c b/src/tool_easysrc.c index c2a34c3b2b1e..1c2a3361792a 100644 --- a/src/tool_easysrc.c +++ b/src/tool_easysrc.c @@ -40,6 +40,14 @@ struct slist_wc *easysrc_toohard; /* Unconvertible setopt */ struct slist_wc *easysrc_clean; /* Clean up allocated data */ int easysrc_mime_count; int easysrc_slist_count; +static bool easysrc_performed; + +/* Set once a transfer has been performed, so that the next generated + statement resets the handle first: the tool uses a fresh easy handle per + operation, the generated code reuses one. Deferred rather than emitted + right after curl_easy_perform() so that no reset is generated when nothing + follows. */ +static bool easysrc_pending_reset = FALSE; static const char * const srchead[] = { "/********* Sample code generated by the curl command line tool **********", @@ -91,7 +99,15 @@ static void easysrc_free(void) CURLcode easysrc_add(struct slist_wc **plist, const char *line) { CURLcode result = CURLE_OK; - struct slist_wc *list = slist_wc_append(*plist, line); + struct slist_wc *list; + if(easysrc_pending_reset && (plist == &easysrc_code)) { + /* cleared first: the recursive call adds to easysrc_code as well */ + easysrc_pending_reset = FALSE; + result = easysrc_add(&easysrc_code, "curl_easy_reset(curl);"); + if(result) + return result; + } + list = slist_wc_append(*plist, line); if(!list) { easysrc_free(); result = CURLE_OUT_OF_MEMORY; @@ -121,9 +137,15 @@ CURLcode easysrc_addf(struct slist_wc **plist, const char *fmt, ...) CURLcode easysrc_init(void) { + easysrc_performed = FALSE; return easysrc_add(&easysrc_code, "curl = curl_easy_init();"); } +bool easysrc_is_first(void) +{ + return !easysrc_performed; +} + CURLcode easysrc_perform(void) { CURLcode result = CURLE_OK; @@ -155,13 +177,22 @@ CURLcode easysrc_perform(void) result = easysrc_add(&easysrc_code, "result = curl_easy_perform(curl);"); if(!result) result = easysrc_add(&easysrc_code, ""); + if(!result) + easysrc_performed = TRUE; + + /* Options set for this operation must not leak into the next one */ + if(!result) + easysrc_pending_reset = TRUE; return result; } CURLcode easysrc_cleanup(void) { - CURLcode result = easysrc_add(&easysrc_code, "curl_easy_cleanup(curl);"); + CURLcode result; + /* nothing follows, so no reset is needed before the cleanup */ + easysrc_pending_reset = FALSE; + result = easysrc_add(&easysrc_code, "curl_easy_cleanup(curl);"); if(!result) result = easysrc_add(&easysrc_code, "curl = NULL;"); diff --git a/src/tool_easysrc.h b/src/tool_easysrc.h index f37ce4ba86f5..642ea33e8cc0 100644 --- a/src/tool_easysrc.h +++ b/src/tool_easysrc.h @@ -44,6 +44,7 @@ extern CURLcode easysrc_addf(struct slist_wc **plist, const char *fmt, ...) CURL_PRINTF(2, 3); extern CURLcode easysrc_perform(void); extern CURLcode easysrc_cleanup(void); +extern bool easysrc_is_first(void); void dumpeasysrc(void); #else /* CURL_DISABLE_LIBCURL_OPTION is defined */ @@ -52,6 +53,7 @@ void dumpeasysrc(void); #define easysrc_cleanup() #define dumpeasysrc(x) #define easysrc_perform() CURLE_OK +#define easysrc_is_first() TRUE #endif /* CURL_DISABLE_LIBCURL_OPTION */ diff --git a/src/tool_formparse.c b/src/tool_formparse.c index 450a2b3a994a..eea511d25056 100644 --- a/src/tool_formparse.c +++ b/src/tool_formparse.c @@ -240,10 +240,8 @@ static int tool_mime_stdin_seek(void *instream, curl_off_t offset, int whence) } if(offset < 0) return CURL_SEEKFUNC_CANTSEEK; - if(!sip->data) { - if(curlx_fseek(stdin, offset + sip->origin, SEEK_SET)) - return CURL_SEEKFUNC_CANTSEEK; - } + if(!sip->data && curlx_fseek(stdin, offset + sip->origin, SEEK_SET)) + return CURL_SEEKFUNC_CANTSEEK; sip->curpos = offset; return CURL_SEEKFUNC_OK; } @@ -382,15 +380,13 @@ static char *get_param_word(char **str, char **end_pos, char endchar) if(*ptr == '"') { ++ptr; while(*ptr) { - if(*ptr == '\\') { - if(ptr[1] == '\\' || ptr[1] == '"') { - /* remember the first escape position */ - if(!escape) - escape = ptr; - /* skip escape of back-slash or double-quote */ - ptr += 2; - continue; - } + if(*ptr == '\\' && (ptr[1] == '\\' || ptr[1] == '"')) { + /* remember the first escape position */ + if(!escape) + escape = ptr; + /* skip escape of back-slash or double-quote */ + ptr += 2; + continue; } if(*ptr == '"') { bool trailing_data = FALSE; diff --git a/src/tool_getparam.c b/src/tool_getparam.c index ae3958d8c58f..44825f83f21f 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -1001,12 +1001,10 @@ static ParameterError set_data(cmdline_t cmd, if(cmd == C_JSON) config->jsoned = TRUE; - if(curlx_dyn_len(&config->postdata)) { - /* skip separator append for --json */ - if(!err && (cmd != C_JSON) && - curlx_dyn_addn(&config->postdata, "&", 1)) - err = PARAM_NO_MEM; - } + /* skip separator append for --json */ + if(curlx_dyn_len(&config->postdata) && !err && (cmd != C_JSON) && + curlx_dyn_addn(&config->postdata, "&", 1)) + err = PARAM_NO_MEM; if(!err && curlx_dyn_addn(&config->postdata, postdata, size)) err = PARAM_NO_MEM; @@ -2151,6 +2149,7 @@ static ParameterError opt_bool(struct OperationConfig *config, break; case C_REMOTE_HEADER_NAME: /* --remote-header-name */ config->content_disposition = toggle; + config->suppress_connect_headers = toggle; break; case C_INSECURE: /* --insecure */ config->insecure_ok = toggle; @@ -2693,6 +2692,7 @@ static ParameterError opt_string(struct OperationConfig *config, err = getstr(&config->sasl_authzid, nextarg, DENY_BLANK); break; case C_PROXY_SERVICE_NAME: /* --proxy-service-name */ + case C_SOCKS5_GSSAPI_SERVICE: /* --socks5-gssapi-service */ err = getstr(&config->proxy_service_name, nextarg, DENY_BLANK); break; case C_SERVICE_NAME: /* --service-name */ @@ -2812,10 +2812,8 @@ static ParameterError opt_string(struct OperationConfig *config, break; case C_HOSTPUBMD5: /* --hostpubmd5 */ err = getstr(&config->hostpubmd5, nextarg, DENY_BLANK); - if(!err) { - if(!config->hostpubmd5 || strlen(config->hostpubmd5) != 32) - err = PARAM_BAD_USE; - } + if(!err && (!config->hostpubmd5 || strlen(config->hostpubmd5) != 32)) + err = PARAM_BAD_USE; break; case C_HOSTPUBSHA256: /* --hostpubsha256 */ err = getstr(&config->hostpubsha256, nextarg, DENY_BLANK); @@ -3066,8 +3064,12 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ } if(has_leading_unicode((const unsigned char *)nextarg)) { - warnf("The argument '%s' starts with a Unicode character. " - "Maybe ASCII was intended?", nextarg); + if(a->desc & ARG_CLEAR) + warnf("The argument to --%s starts with a Unicode character. " + "Maybe ASCII was intended?", a->lname); + else + warnf("The argument '%s' starts with a Unicode character. " + "Maybe ASCII was intended?", nextarg); } switch(ARGTYPE(a->desc)) { case ARG_FILE: @@ -3189,10 +3191,8 @@ ParameterError parse_args(int argc, argv_item_t argv[]) } } - if(!err && config->content_disposition) { - if(config->resume_from_current) - err = PARAM_CONTDISP_RESUME_FROM; - } + if(!err && config->content_disposition && config->resume_from_current) + err = PARAM_CONTDISP_RESUME_FROM; if(err && err != PARAM_HELP_REQUESTED && diff --git a/src/tool_help.c b/src/tool_help.c index 597a6a554d3f..aaf69282c10f 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -269,7 +269,7 @@ void tool_help(const char *category) option */ a = NULL; } - else if(!category[2]) + else if(category[1] && !category[2]) a = findshortopt(category[1]); if(!a) { curl_mfprintf(tool_stderr, "Incorrect option name to show help for," diff --git a/src/tool_ipfs.c b/src/tool_ipfs.c index 2abfcfd3db8d..2c58e65e653f 100644 --- a/src/tool_ipfs.c +++ b/src/tool_ipfs.c @@ -122,7 +122,8 @@ CURLcode ipfs_url_rewrite(CURLU *uh, const char *protocol, char **url, goto clean; } - getResult = curl_url_get(uh, CURLUPART_HOST, &cid, CURLU_URLDECODE); + /* keep the CID percent-encoded, it goes into the path (see note below) */ + getResult = curl_url_get(uh, CURLUPART_HOST, &cid, 0); if(getResult || !cid) goto clean; @@ -168,11 +169,16 @@ CURLcode ipfs_url_rewrite(CURLU *uh, const char *protocol, char **url, curl_url_get(gatewayurl, CURLUPART_SCHEME, &gwscheme, CURLU_URLDECODE) || curl_url_get(gatewayurl, CURLUPART_PORT, &gwport, CURLU_URLDECODE | CURLU_DEFAULT_PORT) || - curl_url_get(gatewayurl, CURLUPART_PATH, &gwpath, CURLU_URLDECODE)) + curl_url_get(gatewayurl, CURLUPART_PATH, &gwpath, 0)) goto clean; - /* get the path from user input */ - if(curl_url_get(uh, CURLUPART_PATH, &inputpath, CURLU_URLDECODE)) + /* Get the user path without decoding it, and build the new path below from + these still-encoded parts. Decoding here would turn an encoded slash (%2f) + into a real separator that the parser's earlier dot-segment cleanup never + saw, so a ".." could then climb out of the // namespace + when the final URL is parsed. Keeping every percent-encoded byte encoded + means no new separators appear and nothing new gets removed. */ + if(curl_url_get(uh, CURLUPART_PATH, &inputpath, 0)) goto clean; /* inputpath might be NULL or a valid pointer now */ @@ -191,7 +197,7 @@ CURLcode ipfs_url_rewrite(CURLU *uh, const char *protocol, char **url, protocol, cid, inputpath ? inputpath : ""); if(!pathbuffer || - curl_url_set(uh, CURLUPART_PATH, pathbuffer, CURLU_URLENCODE)) + curl_url_set(uh, CURLUPART_PATH, pathbuffer, 0)) goto clean; /* Free whatever it has now, rewriting is next */ diff --git a/src/tool_operate.c b/src/tool_operate.c index 439932f5b6cd..b5b3d6ad8c92 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -547,6 +547,7 @@ static CURLcode retrycheck(struct OperationConfig *config, notef("Keeping %" CURL_FORMAT_CURL_OFF_T " bytes", outs->bytes); if(fflush(outs->stream)) { errorf("Failed to flush output file stream"); + *retryp = FALSE; return CURLE_WRITE_ERROR; } if(outs->bytes >= CURL_OFF_T_MAX - outs->init) { @@ -554,6 +555,7 @@ static CURLcode retrycheck(struct OperationConfig *config, "%" CURL_FORMAT_CURL_OFF_T " + " "%" CURL_FORMAT_CURL_OFF_T ")", outs->init, outs->bytes); + *retryp = FALSE; return CURLE_WRITE_ERROR; } truncate = FALSE; @@ -577,10 +579,15 @@ static CURLcode retrycheck(struct OperationConfig *config, notef("Throwing away %" CURL_FORMAT_CURL_OFF_T " bytes", outs->bytes); /* truncate file at the position where we started appending */ - if(toolx_ftruncate(fileno(outs->stream), outs->init)) { + if( +#ifdef DEBUGBUILD + getenv("CURL_DBG_TRUNCATE_FAIL") || +#endif + toolx_ftruncate(fileno(outs->stream), outs->init)) { /* when truncate fails, we cannot append as then we create something strange, bail out */ errorf("Failed to truncate file"); + *retryp = FALSE; return CURLE_WRITE_ERROR; } /* now seek to the end of the file, the position where we @@ -589,6 +596,7 @@ static CURLcode retrycheck(struct OperationConfig *config, if(rc) { errorf("Failed seeking to end of file"); + *retryp = FALSE; return CURLE_WRITE_ERROR; } outs->bytes = 0; /* clear for next round */ @@ -693,6 +701,8 @@ static CURLcode post_close_output(struct per_transfer *per, /* Close the outs file */ if(outs->fopened && outs->stream) { rc = curlx_fclose(outs->stream); + outs->stream = NULL; + outs->fopened = FALSE; if(!result && rc) { /* something went wrong in the writing process */ result = CURLE_WRITE_ERROR; @@ -1242,10 +1252,11 @@ static CURLcode setup_input_file(struct OperationConfig *config, if(!config->globoff && !glob_inuse(&state->inglob)) result = glob_url(&state->inglob, u->infile, &state->upnum, err); if(!result && !state->uploadfile) { - if(glob_inuse(&state->inglob)) + if(glob_inuse(&state->inglob) && + !glob_is_literal(&state->inglob)) result = glob_next_url(&state->uploadfile, &state->inglob); else if(!state->upidx) { - /* copy the allocated string */ + /* take ownership of the allocated string */ state->uploadfile = u->infile; u->infile = NULL; } @@ -1305,7 +1316,8 @@ static CURLcode select_next_url(struct State *state, char **url) { CURLcode result = CURLE_OK; - if(glob_inuse(&state->urlglob)) + if(glob_inuse(&state->urlglob) && + !glob_is_literal(&state->urlglob)) result = glob_next_url(url, &state->urlglob); else if(!state->urlidx) { *url = curlx_strdup(u->url); @@ -1337,6 +1349,18 @@ static CURLcode setup_transfer_upload(struct OperationConfig *config, return result; } +/* returns TRUE if the given stream is a terminal */ +static bool stream_isatty(FILE *stream) +{ +#ifdef DEBUGBUILD + /* the test suite sets CURL_ISATTY to make curl act as if the output goes + to a terminal */ + if(getenv("CURL_ISATTY")) + return TRUE; +#endif + return !!isatty(fileno(stream)); +} + /* create a transfer */ static CURLcode create_single(struct OperationConfig *config, CURLSH *share, struct State *state, @@ -1407,6 +1431,7 @@ static CURLcode create_single(struct OperationConfig *config, per->config = config; per->curl = curl; per->urlnum = u->num; + per->out_scheme = NULL; /* default headers output stream is stdout */ heads = &per->heads; @@ -1439,7 +1464,7 @@ static CURLcode create_single(struct OperationConfig *config, return result; if(!outs->out_null && output_expected(per->url, per->uploadfile) && - outs->stream && isatty(fileno(outs->stream))) + outs->stream && stream_isatty(outs->stream)) /* we send the output to a tty, therefore we switch off the progress meter */ per->noprogress = global->noprogress = global->isatty = TRUE; diff --git a/src/tool_operate.h b/src/tool_operate.h index 69b304dfc030..7330f7b778e6 100644 --- a/src/tool_operate.h +++ b/src/tool_operate.h @@ -46,6 +46,7 @@ struct per_transfer { char *url; curl_off_t urlnum; /* the index of the given URL */ char *outfile; + const char *out_scheme; /* scheme of current header output */ int infd; struct ProgressData progressbar; struct OutStruct outs; diff --git a/src/tool_parsecfg.c b/src/tool_parsecfg.c index e7d3a3f87827..a0b75d185a28 100644 --- a/src/tool_parsecfg.c +++ b/src/tool_parsecfg.c @@ -352,7 +352,7 @@ static bool get_line(FILE *input, struct dynbuf *buf, bool *error) return FALSE; /* error */ } - else if(b[rlen-1] == '\n') { + else if(b[rlen - 1] == '\n') { /* end of the line, drop the newline */ size_t len = curlx_dyn_len(buf); if(len) diff --git a/src/tool_setopt.c b/src/tool_setopt.c index 51e22756aa4a..cd41d8d6b87a 100644 --- a/src/tool_setopt.c +++ b/src/tool_setopt.c @@ -631,6 +631,16 @@ CURLcode tool_setopt_long(CURL *curl, const char *name, CURLoption tag, return result; } +CURLcode tool_setopt_long_force(CURL *curl, const char *name, CURLoption tag, + long lval) +{ + CURLcode result = curl_easy_setopt(curl, tag, lval); + if(global->libcurl && !result && (!easysrc_is_first() || lval == 0)) + result = easysrc_addf(&easysrc_code, "curl_easy_setopt(curl, %s, %ldL);", + name, lval); + return result; +} + /* options that set curl_off_t */ CURLcode tool_setopt_offt(CURL *curl, const char *name, CURLoption tag, curl_off_t lval) diff --git a/src/tool_setopt.h b/src/tool_setopt.h index a737fcddf54f..6414c142887b 100644 --- a/src/tool_setopt.h +++ b/src/tool_setopt.h @@ -92,6 +92,8 @@ CURLcode tool_setopt_slist(CURL *curl, const char *name, CURLoption tag, struct curl_slist *list); CURLcode tool_setopt_long(CURL *curl, const char *name, CURLoption tag, long lval); +CURLcode tool_setopt_long_force(CURL *curl, const char *name, CURLoption tag, + long lval); CURLcode tool_setopt_offt(CURL *curl, const char *name, CURLoption tag, curl_off_t lval); CURLcode tool_setopt_str(CURL *curl, struct OperationConfig *config, @@ -100,6 +102,7 @@ CURLcode tool_setopt_str(CURL *curl, struct OperationConfig *config, CURLcode tool_setopt_ptr(CURL *curl, const char *name, CURLoption tag, ...); #define my_setopt_long(x, y, z) tool_setopt_long(x, #y, y, z) +#define my_setopt_long_force(x, y, z) tool_setopt_long_force(x, #y, y, z) #define my_setopt_offt(x, y, z) tool_setopt_offt(x, #y, y, z) #define my_setopt_ptr(x, y, z) tool_setopt_ptr(x, #y, y, z) #define my_setopt_str(x, y, z) tool_setopt_str(x, config, #y, y, z) @@ -125,6 +128,7 @@ CURLcode tool_setopt_ptr(CURL *curl, const char *name, CURLoption tag, ...); /* No --libcurl, so pass options directly to library */ #define my_setopt_long(x, y, z) curl_easy_setopt(x, y, (long)(z)) +#define my_setopt_long_force(x, y, z) curl_easy_setopt(x, y, (long)(z)) #define my_setopt_offt(x, y, z) curl_easy_setopt(x, y, (curl_off_t)(z)) #define my_setopt_ptr(x, y, z) curl_easy_setopt(x, y, z) #define my_setopt_str(x, y, z) curl_easy_setopt(x, y, z) diff --git a/src/tool_setup.h b/src/tool_setup.h index 7e77c114f1c4..7556e6cd4b3d 100644 --- a/src/tool_setup.h +++ b/src/tool_setup.h @@ -47,7 +47,7 @@ extern FILE *tool_stderr; #include "curlx/base64.h" /* for curlx_base64* */ #include "curlx/dynbuf.h" /* for curlx_dyn_*() */ -#include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/win32-fopen.h" /* for curlx_f*() */ #include "curlx/multibyte.h" /* for curlx_convert_*() */ #include "curlx/strcopy.h" /* for curlx_strcopy() */ #include "curlx/strdup.h" /* for curlx_memdup*() */ diff --git a/src/tool_urlglob.c b/src/tool_urlglob.c index 88deedcb4cfd..ddc65b7b8168 100644 --- a/src/tool_urlglob.c +++ b/src/tool_urlglob.c @@ -543,7 +543,13 @@ static CURLcode glob_parse(struct URLGlob *glob, const char *pattern, bool glob_inuse(struct URLGlob *glob) { - return glob->palloc ? TRUE : FALSE; + /* Both parsed patterns and a literal URL make the glob active. */ + return (glob->palloc || glob->literal) ? TRUE : FALSE; +} + +bool glob_is_literal(const struct URLGlob *glob) +{ + return glob->literal; } /* a glob error has been confirmed, this outputs details about it to the set @@ -578,6 +584,12 @@ CURLcode glob_url(struct URLGlob *glob, const char *url, curl_off_t *urlnum, memset(glob, 0, sizeof(struct URLGlob)); curlx_dyn_init(&glob->buf, MAX_CONFIG_LINE_LENGTH); + if(!strpbrk(url, "{}[]")) { + /* no glob syntax: use the URL as-is instead of parsing a copy of it */ + glob->literal = TRUE; + *urlnum = 1; + return CURLE_OK; + } glob->pattern = curlx_malloc(2 * sizeof(struct URLPattern)); if(!glob->pattern) return CURLE_OUT_OF_MEMORY; @@ -598,6 +610,8 @@ void glob_cleanup(struct URLGlob *glob) { size_t i; + glob->literal = FALSE; + glob->beenhere = 0; if(glob->pattern) { for(i = 0; i < glob->pnum; i++) { DEBUGASSERT(glob->pattern[i].type); @@ -622,6 +636,7 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob) size_t i; *globbed = NULL; + DEBUGASSERT(!glob->literal); curlx_dyn_reset(&glob->buf); if(!glob->beenhere) @@ -669,10 +684,9 @@ CURLcode glob_next_url(char **globbed, struct URLGlob *glob) pat = &glob->pattern[i]; switch(pat->type) { case GLOB_SET: - if(pat->c.set.elem) { - if(curlx_dyn_add(&glob->buf, pat->c.set.elem[pat->c.set.idx])) - return CURLE_OUT_OF_MEMORY; - } + if(pat->c.set.elem && + curlx_dyn_add(&glob->buf, pat->c.set.elem[pat->c.set.idx])) + return CURLE_OUT_OF_MEMORY; break; case GLOB_ASCII: { char letter = (char)pat->c.ascii.letter; diff --git a/src/tool_urlglob.h b/src/tool_urlglob.h index e891258aa46e..020f8d9ffd51 100644 --- a/src/tool_urlglob.h +++ b/src/tool_urlglob.h @@ -65,6 +65,7 @@ struct URLPattern { struct URLGlob { struct dynbuf buf; struct URLPattern *pattern; + bool literal; size_t palloc; /* number of pattern entries allocated */ size_t pnum; /* number of patterns used */ char beenhere; @@ -83,5 +84,6 @@ CURLcode glob_match_url(char **output, const char *filename, SANITIZEcode *sc); void glob_cleanup(struct URLGlob *glob); bool glob_inuse(struct URLGlob *glob); +bool glob_is_literal(const struct URLGlob *glob); #endif /* HEADER_CURL_TOOL_URLGLOB_H */ diff --git a/src/tool_util.c b/src/tool_util.c index 76271e7d8a9d..02371e287b07 100644 --- a/src/tool_util.c +++ b/src/tool_util.c @@ -128,26 +128,45 @@ int toolx_ftruncate_djgpp(int fd, curl_off_t where) FILE *tool_execpath(const char *filename, char **pathp) { static char filebuffer[512]; +#ifdef UNICODE + TCHAR filebuffer_t[sizeof(filebuffer)]; +#else + TCHAR *filebuffer_t = filebuffer; +#endif unsigned long len; /* Get the filename of our executable. GetModuleFileName is already declared - * via inclusions done in setup header file. We assume that we are using - * the ASCII version here. - */ - len = GetModuleFileNameA(0, filebuffer, sizeof(filebuffer)); + via inclusions done in setup header file. */ + len = GetModuleFileName(0, filebuffer_t, sizeof(filebuffer)); if(len > 0 && len < sizeof(filebuffer)) { - /* We got a valid filename - get the directory part */ - char *lastdirchar = strrchr(filebuffer, DIR_CHAR[0]); - if(lastdirchar) { - size_t remaining; - *lastdirchar = 0; - /* If we have enough space, build the RC filename */ - remaining = sizeof(filebuffer) - strlen(filebuffer); - if(strlen(filename) < remaining - 1) { - curl_msnprintf(lastdirchar, remaining, "%s%s", DIR_CHAR, filename); - *pathp = filebuffer; - return curlx_fopen(filebuffer, FOPEN_READTEXT); +#ifdef UNICODE + char *fn = curlx_convert_tchar_to_UTF8(filebuffer_t); + if(fn) { + size_t fn_len = strlen(fn); + if(fn_len >= sizeof(filebuffer)) { + curlx_free(fn); + return NULL; } + curlx_strcopy(filebuffer, sizeof(filebuffer), fn, fn_len); + curlx_free(fn); +#endif + { + /* We got a valid filename - get the directory part */ + char *lastdirchar = strrchr(filebuffer, DIR_CHAR[0]); + if(lastdirchar) { + size_t remaining; + *lastdirchar = 0; + /* If we have enough space, build the RC filename */ + remaining = sizeof(filebuffer) - strlen(filebuffer); + if(strlen(filename) < remaining - 1) { + curl_msnprintf(lastdirchar, remaining, "%s%s", DIR_CHAR, filename); + *pathp = filebuffer; + return curlx_fopen(filebuffer, FOPEN_READTEXT); + } + } + } +#ifdef UNICODE } +#endif } return NULL; diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 7a0a20a9d9fc..fc346353ff65 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -228,12 +228,10 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, len = curlx_dyn_len(&buf); if(len) { char *ptr = curlx_dyn_ptr(&buf); - if(ptr[len - 1] != '\n') { - /* add a newline to make things look better */ - if(curlx_dyn_addn(&buf, "\n", 1)) { - error = TRUE; - break; - } + /* add a newline to make things look better */ + if(ptr[len - 1] != '\n' && curlx_dyn_addn(&buf, "\n", 1)) { + error = TRUE; + break; } } } @@ -288,11 +286,9 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, case VAR_INPUT_URLEQUERY: case VAR_INPUT_URLEFRAGMENT: case VAR_INPUT_URLEZONEID: - if(per->url) { - if(!urlpart(per, wovar->id, &strinfo)) { - freestr = strinfo; - valid = TRUE; - } + if(per->url && !urlpart(per, wovar->id, &strinfo)) { + freestr = strinfo; + valid = TRUE; } break; default: @@ -678,13 +674,11 @@ static void output_header(struct per_transfer *per, size_t seplen = 0; size_t vlen = end - ptr; instr = memchr(ptr, ':', vlen); - if(instr) { - /* instructions follow */ - if(!strncmp(&instr[1], "all:", 4)) { - sep = &instr[5]; - seplen = end - sep; - vlen -= (seplen + 5); - } + /* instructions follow */ + if(instr && !strncmp(&instr[1], "all:", 4)) { + sep = &instr[5]; + seplen = end - sep; + vlen -= (seplen + 5); } if(vlen < sizeof(hname)) { memcpy(hname, ptr, vlen); diff --git a/src/var.c b/src/var.c index 464b0c1c2cec..d5015bd72681 100644 --- a/src/var.c +++ b/src/var.c @@ -113,11 +113,9 @@ static ParameterError varfunc(char *c, /* content */ else if(FUNCMATCH(f, FUNC_JSON, FUNC_JSON_LEN)) { f += FUNC_JSON_LEN; curlx_dyn_reset(out); - if(clen) { - if(jsonquoted(c, clen, out, FALSE)) { - err = PARAM_NO_MEM; - break; - } + if(clen && jsonquoted(c, clen, out, FALSE)) { + err = PARAM_NO_MEM; + break; } } else if(FUNCMATCH(f, FUNC_URL, FUNC_URL_LEN)) { @@ -302,11 +300,11 @@ ParameterError varexpand(const char *line, struct dynbuf *out, bool *replaced) } if(value && vlen > 0) { - /* A variable might contain null bytes. Such bytes cannot be shown + /* A variable might contain NUL bytes. Such bytes cannot be shown using normal means, this is an error. */ const char *nb = memchr(value, '\0', vlen); if(nb) { - errorf("variable contains null byte"); + errorf("variable contains NUL byte"); return PARAM_EXPAND_ERROR; } } @@ -354,7 +352,7 @@ static ParameterError addvariable(const char *name, p = curlx_calloc(1, sizeof(struct tool_var) + nlen); if(p) { memcpy(p->name, name, nlen); - /* the null-termination byte is already present from above */ + /* the null-terminator is already present from above */ p->content = contalloc ? content : curlx_memdup0(content, clen); if(p->content) { @@ -423,11 +421,10 @@ ParameterError setvariable(const char *input) if(curlx_str_number(&line, &startoffset, CURL_OFF_T_MAX) || curlx_str_single(&line, '-')) return PARAM_VAR_SYNTAX; - if(curlx_str_single(&line, ']')) { - if(curlx_str_number(&line, &endoffset, CURL_OFF_T_MAX) || - curlx_str_single(&line, ']')) - return PARAM_VAR_SYNTAX; - } + if(curlx_str_single(&line, ']') && + (curlx_str_number(&line, &endoffset, CURL_OFF_T_MAX) || + curlx_str_single(&line, ']'))) + return PARAM_VAR_SYNTAX; if(startoffset > endoffset) return PARAM_VAR_SYNTAX; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 8273f83bb9c7..0c90ccf3ca3a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -66,16 +66,15 @@ EXTRA_DIST = \ nghttpx.conf \ pathhelp.pm \ processhelp.pm \ - requirements.txt \ rtspserver.pl \ runner.pm \ runtests.pl \ secureserver.pl \ serverhelp.pm \ servers.pm \ - smbserver.py \ sshhelp.pm \ sshserver.pl \ + test-linter.pl \ testcurl.pl \ testutil.pm \ tftpserver.pl \ diff --git a/tests/appveyor.pm b/tests/appveyor.pm index b3f8df9b3175..dcd5fe02222a 100644 --- a/tests/appveyor.pm +++ b/tests/appveyor.pm @@ -52,6 +52,8 @@ sub appveyor_create_test_result { $testname =~ s/\\/\\\\/g; $testname =~ s/\"/\\\"/g; $testname =~ s/\'/'"'"'/g; + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $appveyor_baseurl = $ENV{'APPVEYOR_API_URL'}; my $appveyor_result = qx($curl --silent --noproxy '*' \\ --header 'Content-Type: application/json' \\ @@ -77,7 +79,7 @@ sub appveyor_update_test_result { if(!defined $stop) { $stop = $start; } - my $appveyor_duration = sprintf("%.0f", ($stop-$start)*1000); + my $appveyor_duration = sprintf("%.0f", ($stop - $start) * 1000); my $appveyor_outcome; my $appveyor_category; if($error == 2) { @@ -96,6 +98,8 @@ sub appveyor_update_test_result { $appveyor_outcome = 'Failed'; $appveyor_category = 'Error'; } + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $appveyor_baseurl = $ENV{'APPVEYOR_API_URL'}; my $appveyor_result = qx($curl --silent --noproxy '*' --request PUT \\ --header 'Content-Type: application/json' \\ @@ -112,6 +116,8 @@ sub appveyor_update_test_result { '$appveyor_baseurl/api/tests'); print "AppVeyor API result: $appveyor_result\n" if($appveyor_result); if($appveyor_category eq 'Error') { + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; $appveyor_result = qx($curl --silent --noproxy '*' \\ --header 'Content-Type: application/json' \\ --data ' diff --git a/tests/azure.pm b/tests/azure.pm index e449a07fd485..7bb7946c965c 100644 --- a/tests/azure.pm +++ b/tests/azure.pm @@ -54,6 +54,8 @@ sub azure_check_environment { sub azure_create_test_run { my ($curl) = @_; + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; my $azure_run = qx($curl --silent --noproxy "*" \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ @@ -77,6 +79,8 @@ sub azure_create_test_result { $testname =~ s/\\/\\\\/g; $testname =~ s/\"/\\\"/g; $testname =~ s/\'/'"'"'/g; + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $title_testnum = sprintf("%04d", $testnum); my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; my $azure_result = qx($curl --silent --noproxy '*' \\ @@ -108,7 +112,7 @@ sub azure_update_test_result { } my $azure_start = strftime "%Y-%m-%dT%H:%M:%SZ", gmtime $start; my $azure_complete = strftime "%Y-%m-%dT%H:%M:%SZ", gmtime $stop; - my $azure_duration = sprintf("%.0f", ($stop-$start)*1000); + my $azure_duration = sprintf("%.0f", ($stop - $start) * 1000); my $azure_outcome; if($error == 2) { $azure_outcome = 'NotApplicable'; @@ -122,6 +126,8 @@ sub azure_update_test_result { else { $azure_outcome = 'Failed'; } + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; my $azure_result = qx($curl --silent --noproxy '*' --request PATCH \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ @@ -146,6 +152,8 @@ sub azure_update_test_result { sub azure_update_test_run { my ($curl, $azure_run_id) = @_; + local $ENV{CURL_MEMDEBUG}; + delete $ENV{CURL_MEMDEBUG}; my $azure_baseurl = "$ENV{'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI'}$ENV{'SYSTEM_TEAMPROJECTID'}"; my $azure_run = qx($curl --silent --noproxy '*' --request PATCH \\ --header "Authorization: Bearer $ENV{'AZURE_ACCESS_TOKEN'}" \\ diff --git a/tests/cmake/test.sh b/tests/cmake/test.sh index b9bb1fc771e7..40be316000c8 100755 --- a/tests/cmake/test.sh +++ b/tests/cmake/test.sh @@ -57,7 +57,7 @@ fi if [ "${mode}" = 'all' ] || [ "${mode}" = 'add_subdirectory' ]; then rm -rf curl - if ! ln -s "${src}" curl; then + if ! ln -s "${src}" curl 2>/dev/null; then rm -rf curl; mkdir curl; (cd "${src}"; git archive --format=tar HEAD) | tar -x --directory=curl # for MSYS2/Cygwin fi bldc='bld-add_subdirectory' diff --git a/tests/data/DISABLED b/tests/data/DISABLED index d4c90a0df913..f24187750d33 100644 --- a/tests/data/DISABLED +++ b/tests/data/DISABLED @@ -63,3 +63,6 @@ 2047 %endif %endif +# We can't close down the TLS connection nicely enough to make this work +# reliably +4656 diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 3b32fce7bb30..0f4ce61062c2 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -25,9 +25,6 @@ all: install: test: -# The helper script ./scripts/testnum provides a set of available test -# numbers. - # this list is in numerical order TESTCASES = \ test1 \ @@ -489,6 +486,7 @@ TESTCASES = \ test461 \ test462 \ test463 \ + test464 \ test467 \ test468 \ test469 \ @@ -679,6 +677,7 @@ TESTCASES = \ test654 \ test655 \ test656 \ + test657 \ test658 \ test659 \ test660 \ @@ -1700,6 +1699,7 @@ TESTCASES = \ test1676 \ test1677 \ test1678 \ + test1679 \ test1680 \ test1681 \ test1682 \ @@ -1707,8 +1707,16 @@ TESTCASES = \ test1684 \ test1685 \ test1686 \ + test1687 \ + test1688 \ + test1689 \ + test1690 \ + test1691 \ + test1692 \ + test1693 \ \ test1700 \ + test1701 \ test1702 \ test1703 \ test1704 \ @@ -1723,6 +1731,7 @@ TESTCASES = \ test1713 \ test1714 \ test1715 \ + test1716 \ test1720 \ test1721 \ test1722 \ @@ -1731,16 +1740,23 @@ TESTCASES = \ test1725 \ test1740 \ test1741 \ - \ + test1742 \ + test1743 \ + test1744 \ + test1745 \ + test1790 \ + test1791 \ test1800 \ test1801 \ test1802 \ + test1827 \ test1847 \ test1848 \ test1849 \ - test1850 \ test1851 \ - \ + test1862 \ + test1876 \ + test1877 \ test1900 \ test1901 \ test1902 \ @@ -1809,7 +1825,7 @@ TESTCASES = \ test1983 \ test1984 \ test1985 \ - \ + test1989 \ test2000 \ test2001 \ test2002 \ @@ -1899,6 +1915,7 @@ TESTCASES = \ test2092 \ test2093 \ test2094 \ + test2095 \ test2100 \ test2101 \ test2102 \ @@ -1918,6 +1935,8 @@ TESTCASES = \ test2118 \ test2119 \ \ + test2173 \ + \ test2200 \ test2201 \ test2202 \ @@ -1927,6 +1946,14 @@ TESTCASES = \ test2206 \ test2207 \ test2208 \ + test2209 \ + test2210 \ + test2211 \ + test2212 \ + \ + test2270 \ + test2271 \ + test2272 \ \ test2300 \ test2301 \ @@ -1941,8 +1968,9 @@ TESTCASES = \ test2310 \ test2311 \ test2318 \ + test2330 \ test2349 \ - \ + test2380 \ test2397 \ \ test2400 \ @@ -1960,6 +1988,10 @@ TESTCASES = \ test2412 \ test2413 \ test2414 \ + test2415 \ + test2418 \ + test2429 \ + test2453 \ \ test2500 \ test2501 \ @@ -1968,7 +2000,7 @@ TESTCASES = \ test2504 \ test2505 \ test2506 \ - \ + test2582 \ test2600 \ test2601 \ test2602 \ @@ -2001,7 +2033,12 @@ TESTCASES = \ test2721 \ test2722 \ test2723 \ + test2805 \ + test2824 \ test2885 \ + test2918 \ + test2924 \ + test2967 \ test3000 \ test3001 \ test3002 \ @@ -2039,6 +2076,8 @@ TESTCASES = \ test3034 \ test3035 \ test3036 \ + test3037 \ + test3038 \ \ test3100 \ test3101 \ @@ -2047,7 +2086,8 @@ TESTCASES = \ test3104 \ test3105 \ test3106 \ - \ + test3107 \ + test3109 \ test3200 \ test3201 \ test3202 \ @@ -2078,7 +2118,12 @@ TESTCASES = \ test3227 \ test3228 \ test3229 \ - \ + test3230 \ + test3231 \ + test3232 \ + test3233 \ + test3234 \ + test3271 \ test3300 \ test3301 \ test3302 \ @@ -2089,10 +2134,20 @@ TESTCASES = \ \ test3400 \ test3401 \ - \ + test3500 \ + test3555 \ + test3595 \ + test3596 \ + test3611 \ test4000 \ test4001 \ - \ + test4039 \ + test4040 \ + test4644 \ + test4656 \ + test4657 \ + test4781 \ + test4782 \ test5000 \ test5001 \ test5002 \ @@ -2121,7 +2176,9 @@ TESTCASES = \ test5025 \ test5026 \ test5027 \ - test5028 + test5028 \ + test5029 \ + test5030 EXTRA_DIST = \ $(TESTCASES) \ @@ -2141,6 +2198,8 @@ EXTRA_DIST = \ data1463.txt \ data1465.c \ data1481.c \ + data1679.c \ + data1687.c \ data1705-1.md \ data1705-2.md \ data1705-3.md \ diff --git a/tests/data/data1481.c b/tests/data/data1481.c index e7d468e6985c..7d09b351bc8b 100644 --- a/tests/data/data1481.c +++ b/tests/data/data1481.c @@ -14,6 +14,7 @@ int main(int argc, char *argv[]) curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl, CURLOPT_URL, "http://moo/"); curl_easy_setopt(curl, CURLOPT_PROXY, "http://%HOSTIP:%HTTPPORT"); + curl_easy_setopt(curl, CURLOPT_PROXY_SERVICE_NAME, "sockd"); curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); curl_easy_setopt(curl, CURLOPT_SSLVERSION, (long)(CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_3)); diff --git a/tests/data/data1679.c b/tests/data/data1679.c new file mode 100644 index 000000000000..4a98fca4dc4c --- /dev/null +++ b/tests/data/data1679.c @@ -0,0 +1,81 @@ +/********* Sample code generated by the curl command line tool ********** + * All curl_easy_setopt() options are documented at: + * https://curl.se/libcurl/c/curl_easy_setopt.html + ************************************************************************/ +#include + +int main(int argc, char *argv[]) +{ + CURLcode result; + CURL *curl; + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1_2); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + /* Here is a list of options the curl code used that cannot get generated + as source easily. You may choose to either not use them or implement + them yourself. + + CURLOPT_DEBUGFUNCTION was set to a function pointer + CURLOPT_DEBUGDATA was set to an object pointer + CURLOPT_WRITEDATA was set to an object pointer + CURLOPT_WRITEFUNCTION was set to a function pointer + CURLOPT_READDATA was set to an object pointer + CURLOPT_READFUNCTION was set to a function pointer + CURLOPT_SEEKDATA was set to an object pointer + CURLOPT_SEEKFUNCTION was set to a function pointer + CURLOPT_HEADERFUNCTION was set to a function pointer + CURLOPT_HEADERDATA was set to an object pointer + CURLOPT_ERRORBUFFER was set to an object pointer + CURLOPT_STDERR was set to an object pointer + + */ + + result = curl_easy_perform(curl); + + curl_easy_reset(curl); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L); + curl_easy_setopt(curl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1_2); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + /* Here is a list of options the curl code used that cannot get generated + as source easily. You may choose to either not use them or implement + them yourself. + + CURLOPT_DEBUGFUNCTION was set to a function pointer + CURLOPT_DEBUGDATA was set to an object pointer + CURLOPT_WRITEDATA was set to an object pointer + CURLOPT_WRITEFUNCTION was set to a function pointer + CURLOPT_READDATA was set to an object pointer + CURLOPT_READFUNCTION was set to a function pointer + CURLOPT_SEEKDATA was set to an object pointer + CURLOPT_SEEKFUNCTION was set to a function pointer + CURLOPT_HEADERFUNCTION was set to a function pointer + CURLOPT_HEADERDATA was set to an object pointer + CURLOPT_ERRORBUFFER was set to an object pointer + CURLOPT_STDERR was set to an object pointer + + */ + + result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + curl = NULL; + + return (int)result; +} +/**** End of sample code ****/ diff --git a/tests/data/data1687.c b/tests/data/data1687.c new file mode 100644 index 000000000000..94b7a020796d --- /dev/null +++ b/tests/data/data1687.c @@ -0,0 +1,82 @@ +/********* Sample code generated by the curl command line tool ********** + * All curl_easy_setopt() options are documented at: + * https://curl.se/libcurl/c/curl_easy_setopt.html + ************************************************************************/ +#include + +int main(int argc, char *argv[]) +{ + CURLcode result; + CURL *curl; + struct curl_slist *slist1; + + slist1 = NULL; + slist1 = curl_slist_append(slist1, "X-First: yes"); + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + /* Here is a list of options the curl code used that cannot get generated + as source easily. You may choose to either not use them or implement + them yourself. + + CURLOPT_DEBUGFUNCTION was set to a function pointer + CURLOPT_DEBUGDATA was set to an object pointer + CURLOPT_WRITEDATA was set to an object pointer + CURLOPT_WRITEFUNCTION was set to a function pointer + CURLOPT_READDATA was set to an object pointer + CURLOPT_READFUNCTION was set to a function pointer + CURLOPT_SEEKDATA was set to an object pointer + CURLOPT_SEEKFUNCTION was set to a function pointer + CURLOPT_HEADERFUNCTION was set to a function pointer + CURLOPT_HEADERDATA was set to an object pointer + CURLOPT_ERRORBUFFER was set to an object pointer + CURLOPT_STDERR was set to an object pointer + + */ + + result = curl_easy_perform(curl); + + curl_easy_reset(curl); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(curl, CURLOPT_URL, "http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/%VERSION"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + /* Here is a list of options the curl code used that cannot get generated + as source easily. You may choose to either not use them or implement + them yourself. + + CURLOPT_DEBUGFUNCTION was set to a function pointer + CURLOPT_DEBUGDATA was set to an object pointer + CURLOPT_WRITEDATA was set to an object pointer + CURLOPT_WRITEFUNCTION was set to a function pointer + CURLOPT_READDATA was set to an object pointer + CURLOPT_READFUNCTION was set to a function pointer + CURLOPT_SEEKDATA was set to an object pointer + CURLOPT_SEEKFUNCTION was set to a function pointer + CURLOPT_HEADERFUNCTION was set to a function pointer + CURLOPT_HEADERDATA was set to an object pointer + CURLOPT_ERRORBUFFER was set to an object pointer + CURLOPT_STDERR was set to an object pointer + + */ + + result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + curl = NULL; + curl_slist_free_all(slist1); + slist1 = NULL; + + return (int)result; +} +/**** End of sample code ****/ diff --git a/tests/data/test1 b/tests/data/test1 index a6935ab56a57..dfe31fd479cf 100644 --- a/tests/data/test1 +++ b/tests/data/test1 @@ -48,7 +48,7 @@ Accept: */* -Allocations: 135 +Allocations: 140 Maximum allocated: 136000 diff --git a/tests/data/test1001 b/tests/data/test1001 index c1f839670f21..a17725a32cb3 100644 --- a/tests/data/test1001 +++ b/tests/data/test1001 @@ -6,7 +6,7 @@ HTTP HTTP PUT HTTP proxy HTTP Digest auth -Resume +Range Content-Range CUSTOMREQUEST @@ -70,10 +70,10 @@ proxy digest -HTTP POST --digest with PUT and resumed upload and modified method +HTTP Digest range upload with a modified method -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT --range 0-4 -X GET test @@ -85,7 +85,7 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 +Content-Range: bytes 0-4/0 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -94,13 +94,13 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER", response="6af4d89c952f4dd4cc215a6878dc499d" -Content-Range: bytes 2-4/5 +Content-Range: bytes 0-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test1002 b/tests/data/test1002 index eb53aac58cd6..5734cff949ac 100644 --- a/tests/data/test1002 +++ b/tests/data/test1002 @@ -7,7 +7,6 @@ HTTP PUT HTTP proxy HTTP Digest auth Resume -Content-Range CUSTOMREQUEST @@ -69,10 +68,10 @@ proxy digest -HTTP PUT with Digest auth, resumed upload and modified method, twice +HTTP Digest uploads with zero resume offset and custom method -http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload1 -T %LOGDIR/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload2 -T %LOGDIR/%TESTNUMBER -u auser:apasswd --digest -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload1 -T %LOGDIR/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload2 -T %LOGDIR/%TESTNUMBER -u auser:apasswd --digest -x http://%HOSTIP:%HTTPPORT -C 0 -X GET test @@ -84,7 +83,6 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload1 HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -93,16 +91,14 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload1 HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER.upload1", response="198aa9b6acb4b0c71d02a197a5e41f54" -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload2 HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -111,13 +107,12 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER.upload2 HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER.upload2", response="d711f0d2042786d930de635ba0d1a1d0" -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test1041 b/tests/data/test1041 index 4133ac7a809d..961bf04ea411 100644 --- a/tests/data/test1041 +++ b/tests/data/test1041 @@ -6,13 +6,12 @@ HTTP HTTP PUT Resume -Content-Range # Server-side - + HTTP/1.1 200 OK swsclose Date: Tue, 09 Nov 2010 14:49:00 GMT Server: test-server/fake @@ -30,7 +29,7 @@ Content-Type: text/html http -HTTP PUT with resume from end of already-uploaded file +HTTP PUT rejects automatic upload resume 012345678 @@ -45,32 +44,20 @@ HTTP PUT with resume from end of already-uploaded file 012345678 -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -T%LOGDIR/test%TESTNUMBER.txt -C - +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -T%LOGDIR/test%TESTNUMBER.txt -C - --no-progress-meter # Verify data after the test has been "shot" -# curl does not do a HEAD request on the remote file so it has no idea whether -# it can skip part of the file or not. Instead, it sends the entire file. - -PUT /%TESTNUMBER HTTP/1.1 -Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 0-99/100 -User-Agent: curl/%VERSION -Accept: */* -Content-Length: 100 + +43 + + +curl: (43) HTTP upload cannot be resumed + + -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 -012345678 diff --git a/tests/data/test1113 b/tests/data/test1113 index ed606ec667b1..3c3086544dc3 100644 --- a/tests/data/test1113 +++ b/tests/data/test1113 @@ -8,6 +8,7 @@ LIST wildcardmatch ftplistparser flaky +libtest diff --git a/tests/data/test1114 b/tests/data/test1114 index 5391e0c48d3b..d8137d103862 100644 --- a/tests/data/test1114 +++ b/tests/data/test1114 @@ -5,6 +5,7 @@ FTP wildcardmatch ftplistparser +libtest diff --git a/tests/data/test1156 b/tests/data/test1156 index 803feea9b9b1..b44fe6ba69c6 100644 --- a/tests/data/test1156 +++ b/tests/data/test1156 @@ -7,6 +7,7 @@ HTTP GET Content-Range Resume Range +libtest @@ -52,9 +53,6 @@ body http - -lib%TESTNUMBER - HTTP resume/range fail range-error content-range combinations diff --git a/tests/data/test1162 b/tests/data/test1162 index fa0ab768251e..8f242127653e 100644 --- a/tests/data/test1162 +++ b/tests/data/test1162 @@ -8,6 +8,7 @@ LIST wildcardmatch ftplistparser flaky +libtest diff --git a/tests/data/test1163 b/tests/data/test1163 index 1c60bbdbfd3f..19d2b20b7a14 100644 --- a/tests/data/test1163 +++ b/tests/data/test1163 @@ -8,6 +8,7 @@ LIST wildcardmatch ftplistparser flaky +libtest diff --git a/tests/data/test1301 b/tests/data/test1301 index ea8e47c5dcc8..4c82deaad106 100644 --- a/tests/data/test1301 +++ b/tests/data/test1301 @@ -3,14 +3,12 @@ curl_strequal +libtest # Client-side - -lib%TESTNUMBER - curl_strequal tests diff --git a/tests/data/test1308 b/tests/data/test1308 index adb471ae821c..adf3a65051fd 100644 --- a/tests/data/test1308 +++ b/tests/data/test1308 @@ -5,6 +5,7 @@ curl_formadd curl_formget FORM +libtest @@ -17,9 +18,6 @@ form-api formpost tests - -lib%TESTNUMBER - %LOGDIR/test-%TESTNUMBER diff --git a/tests/data/test1394 b/tests/data/test1394 index 500512c0ea2b..373cc8ebd087 100644 --- a/tests/data/test1394 +++ b/tests/data/test1394 @@ -2,7 +2,7 @@ -unittest +tunittest @@ -14,9 +14,6 @@ unittest unit test for parse_cert_parameter() - -tool%TESTNUMBER - diff --git a/tests/data/test1396 b/tests/data/test1396 index 88062c82985c..e7f0b5d0c30e 100644 --- a/tests/data/test1396 +++ b/tests/data/test1396 @@ -4,14 +4,12 @@ curl_easy_escape curl_easy_unescape +libtest # Client-side - -lib%TESTNUMBER - curl_easy_escape and curl_easy_unescape diff --git a/tests/data/test1398 b/tests/data/test1398 index dde51f699407..10db85a4011d 100644 --- a/tests/data/test1398 +++ b/tests/data/test1398 @@ -3,14 +3,12 @@ curl_msnprintf +libtest # Client-side - -lib%TESTNUMBER - curl_msnprintf tests diff --git a/tests/data/test1425 b/tests/data/test1425 index 915ea58870b8..cb47efeaa574 100644 --- a/tests/data/test1425 +++ b/tests/data/test1425 @@ -42,7 +42,7 @@ CURL_ISATTY=yes Refuse binary output to terminal -http://%HOSTIP:%HTTPPORT/%TESTNUMBER +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --no-styled-output diff --git a/tests/data/test1426 b/tests/data/test1426 index 02669a06a0b8..ebd500806fa2 100644 --- a/tests/data/test1426 +++ b/tests/data/test1426 @@ -42,7 +42,7 @@ CURL_ISATTY=yes Okay binary output to terminal -http://%HOSTIP:%HTTPPORT/%TESTNUMBER --output - +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --output - --no-styled-output diff --git a/tests/data/test1428 b/tests/data/test1428 index 858d2cd9f1d5..b548a54d4b46 100644 --- a/tests/data/test1428 +++ b/tests/data/test1428 @@ -49,7 +49,7 @@ http-proxy HTTP over proxy-tunnel with --proxy-header and --header -http://test.%TESTNUMBER:%HTTPPORT/we/want/that/page/%TESTNUMBER -p -x %HOSTIP:%PROXYPORT --user 'iam:my:;self' --header "header-type: server" --proxy-header "header-type: proxy" +http://test.%TESTNUMBER:%HTTPPORT/we/want/that/page/%TESTNUMBER -p -x %HOSTIP:%PROXYPORT --user 'iam:my:;self' --header "header-type: server" --proxy-header "header-type: proxy" --proxy-header " : ignored" proxy diff --git a/tests/data/test1451 b/tests/data/test1451 index d0fdf56f7243..6843d5db165c 100644 --- a/tests/data/test1451 +++ b/tests/data/test1451 @@ -2,37 +2,18 @@ -SMB +mprintf +libtest -# Server-side - - -Basic SMB test complete - - - # Client-side - -smb - -smb +win32 -Basic SMB request +mprintf %I length modifier unit tests - --u 'curltest:curltest' smb://%HOSTIP:%SMBPORT/TESTS/%TESTNUMBER - - -# Verify data after the test has been "shot" - - -Basic SMB test complete - - diff --git a/tests/data/test1481 b/tests/data/test1481 index 8a0d31078533..3991d75a4f8d 100644 --- a/tests/data/test1481 +++ b/tests/data/test1481 @@ -37,7 +37,7 @@ SSL SSL_CERT_FILE -http://moo/ --libcurl %LOGDIR/test%TESTNUMBER.c --tls-max 1.3 --proxy-tlsv1 -x http://%HOSTIP:%HTTPPORT +http://moo/ --libcurl %LOGDIR/test%TESTNUMBER.c --tls-max 1.3 --proxy-tlsv1 -x http://%HOSTIP:%HTTPPORT --socks5-gssapi-service sockd diff --git a/tests/data/test1485 b/tests/data/test1485 index 55f9a968f62b..0afe45c1004d 100644 --- a/tests/data/test1485 +++ b/tests/data/test1485 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -29,9 +30,6 @@ Content-Length: 7 http - -lib%TESTNUMBER - get curlinfo on last header in callback diff --git a/tests/data/test1494 b/tests/data/test1494 index cd661eb38093..4a7e25071382 100644 --- a/tests/data/test1494 +++ b/tests/data/test1494 @@ -11,7 +11,7 @@ DELAY # Server-side -HTTP/1.1 200 funky chunky! +HTTP/1.1 200 funky chunky! swsclose Server: fakeit/0.9 fakeitbad/1.0 Transfer-Encoding: identity, identity Content-Length: 19 diff --git a/tests/data/test1500 b/tests/data/test1500 index 6f5bfa034f77..e518964370a5 100644 --- a/tests/data/test1500 +++ b/tests/data/test1500 @@ -5,6 +5,7 @@ HTTP HTTP GET multi +libtest @@ -31,9 +32,6 @@ http http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_multi_wait diff --git a/tests/data/test1501 b/tests/data/test1501 index 2c6bd8f1f9c0..2a47c2fdede1 100644 --- a/tests/data/test1501 +++ b/tests/data/test1501 @@ -7,6 +7,7 @@ RETR multi LIST DELAY +libtest @@ -25,9 +26,6 @@ DELAY TYPE 2 ftp - -lib%TESTNUMBER - ftp diff --git a/tests/data/test1502 b/tests/data/test1502 index f1004ba06ce1..3508aeb43db0 100644 --- a/tests/data/test1502 +++ b/tests/data/test1502 @@ -6,6 +6,7 @@ HTTP HTTP GET multi CURLOPT_RESOLVE +libtest @@ -31,9 +32,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - HTTP multi with CURLOPT_RESOLVE, cleanup sequence UA diff --git a/tests/data/test1503 b/tests/data/test1503 index 758e12d93001..7b4059909884 100644 --- a/tests/data/test1503 +++ b/tests/data/test1503 @@ -6,6 +6,7 @@ HTTP HTTP GET multi CURLOPT_RESOLVE +libtest diff --git a/tests/data/test1504 b/tests/data/test1504 index 35a624db65ed..b592be76cca7 100644 --- a/tests/data/test1504 +++ b/tests/data/test1504 @@ -6,6 +6,7 @@ HTTP HTTP GET multi CURLOPT_RESOLVE +libtest diff --git a/tests/data/test1505 b/tests/data/test1505 index 7925f185bbfc..14015daa5f26 100644 --- a/tests/data/test1505 +++ b/tests/data/test1505 @@ -6,6 +6,7 @@ HTTP HTTP GET multi CURLOPT_RESOLVE +libtest diff --git a/tests/data/test1506 b/tests/data/test1506 index 7801fead66ce..2aced6248ad2 100644 --- a/tests/data/test1506 +++ b/tests/data/test1506 @@ -5,6 +5,7 @@ HTTP multi verbose logs +libtest @@ -52,9 +53,6 @@ http verbose-strings - -lib%TESTNUMBER - HTTP GET connection cache limit (CURLMOPT_MAXCONNECTS) diff --git a/tests/data/test1507 b/tests/data/test1507 index 807793d27a75..b684a1ef1e24 100644 --- a/tests/data/test1507 +++ b/tests/data/test1507 @@ -4,6 +4,7 @@ SMTP multi +libtest @@ -16,9 +17,6 @@ multi smtp - -lib%TESTNUMBER - # based on bug report #1184 diff --git a/tests/data/test1508 b/tests/data/test1508 index 72f6dee075c9..afcd37201095 100644 --- a/tests/data/test1508 +++ b/tests/data/test1508 @@ -4,14 +4,12 @@ HTTP multi +libtest # Client-side - -lib%TESTNUMBER - Close a multi handle without using it diff --git a/tests/data/test1509 b/tests/data/test1509 index 7e57cd0fad9b..158323b9b036 100644 --- a/tests/data/test1509 +++ b/tests/data/test1509 @@ -7,6 +7,7 @@ HTTP GET HTTP CONNECT HTTP proxy proxytunnel +libtest @@ -51,9 +52,6 @@ http http-proxy # tool is what to use instead of 'curl' - -lib%TESTNUMBER - proxy diff --git a/tests/data/test1510 b/tests/data/test1510 index 90771e6979ee..b6678c91927d 100644 --- a/tests/data/test1510 +++ b/tests/data/test1510 @@ -5,6 +5,7 @@ HTTP verbose logs flaky +libtest @@ -49,9 +50,6 @@ file contents should appear once for each file http - -lib%TESTNUMBER - HTTP GET connection cache limit (CURLOPT_MAXCONNECTS) diff --git a/tests/data/test1511 b/tests/data/test1511 index ad41ffaaf21a..e4db6517c925 100644 --- a/tests/data/test1511 +++ b/tests/data/test1511 @@ -4,6 +4,7 @@ HTTP CURLOPT_TIMECONDITION +libtest @@ -51,9 +52,6 @@ http http - -lib%TESTNUMBER - HTTP GET time conditions in repeated requests diff --git a/tests/data/test1512 b/tests/data/test1512 index d62d5c1717d2..6b5292e09d31 100644 --- a/tests/data/test1512 +++ b/tests/data/test1512 @@ -4,6 +4,7 @@ HTTP GLOBAL DNS CACHE +libtest @@ -48,9 +49,6 @@ file contents should appear once for each file http - -lib%TESTNUMBER - GLOBAL CACHE test over two easy performs diff --git a/tests/data/test1513 b/tests/data/test1513 index b84183846df1..1d6f62b31804 100644 --- a/tests/data/test1513 +++ b/tests/data/test1513 @@ -4,6 +4,7 @@ PROGRESSFUNCTION DELAY +libtest @@ -23,9 +24,6 @@ wait 10 http - -lib%TESTNUMBER - return failure immediately from progress callback diff --git a/tests/data/test1514 b/tests/data/test1514 index 0cc190010400..0bdc0f860df3 100644 --- a/tests/data/test1514 +++ b/tests/data/test1514 @@ -6,6 +6,7 @@ HTTP HTTP POST Content-Length chunked Transfer-Encoding +libtest @@ -25,9 +26,6 @@ Connection: close http - -lib%TESTNUMBER - HTTP POST with read callback and unknown data size diff --git a/tests/data/test1515 b/tests/data/test1515 index 2ad5ac0797b8..e85f618d335e 100644 --- a/tests/data/test1515 +++ b/tests/data/test1515 @@ -6,6 +6,7 @@ HTTP multi resolve +libtest @@ -42,9 +43,6 @@ hello http - -lib%TESTNUMBER - caching of manual libcurl DNS entries after DNS cache timeout diff --git a/tests/data/test1516 b/tests/data/test1516 index 02f2c6d84f17..5dce1737b69b 100644 --- a/tests/data/test1516 +++ b/tests/data/test1516 @@ -6,6 +6,7 @@ HTTP multi resolve +libtest diff --git a/tests/data/test1517 b/tests/data/test1517 index 6b53c3955d00..c2be9f859cd8 100644 --- a/tests/data/test1517 +++ b/tests/data/test1517 @@ -7,6 +7,7 @@ POST POST callback slow callback early response +libtest # This test checks to make sure curl can call recv() without failing after a @@ -44,9 +45,6 @@ This is a virtual description of server virtual error. http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP POST, server responds before completed send diff --git a/tests/data/test1518 b/tests/data/test1518 index 096765e870e7..10824d9ff7e8 100644 --- a/tests/data/test1518 +++ b/tests/data/test1518 @@ -3,6 +3,7 @@ HTTP +libtest # This reproduces issue #3340 @@ -27,9 +28,6 @@ Connection: close http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Extract Location: with broken URL diff --git a/tests/data/test1519 b/tests/data/test1519 index 7dd225285071..865af37daed8 100644 --- a/tests/data/test1519 +++ b/tests/data/test1519 @@ -3,6 +3,7 @@ HTTP +libtest # This reproduces issue #3340 diff --git a/tests/data/test1520 b/tests/data/test1520 index c26c08866fb2..2383ac457987 100644 --- a/tests/data/test1520 +++ b/tests/data/test1520 @@ -4,6 +4,7 @@ SMTP +libtest @@ -12,9 +13,6 @@ SMTP smtp - -lib%TESTNUMBER - SMTP with CRLF-dot-CRLF in upload payload diff --git a/tests/data/test1521 b/tests/data/test1521 index e86125b2e755..3e87ae60dad9 100644 --- a/tests/data/test1521 +++ b/tests/data/test1521 @@ -3,14 +3,12 @@ curl_easy_setopt +libtest # Client-side - -lib%TESTNUMBER - Test all curl_easy_setopt and curl_easy_getinfo options diff --git a/tests/data/test1522 b/tests/data/test1522 index 581ecf6dc969..d099dde58702 100644 --- a/tests/data/test1522 +++ b/tests/data/test1522 @@ -3,6 +3,7 @@ CURLINFO_SIZE_UPLOAD +libtest @@ -28,9 +29,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - CURLINFO_SIZE_UPLOAD with small SO_SNDBUF diff --git a/tests/data/test1523 b/tests/data/test1523 index 6fb4c6b7d038..61c9e85fd95a 100644 --- a/tests/data/test1523 +++ b/tests/data/test1523 @@ -4,6 +4,7 @@ CURLINFO_LOW_SPEED_LIMIT DELAY +libtest @@ -27,9 +28,6 @@ writedelay: 1000 http - -lib%TESTNUMBER - CURLINFO_LOW_SPEED_LIMIT diff --git a/tests/data/test1525 b/tests/data/test1525 index c090feb5d3bb..9700386d127f 100644 --- a/tests/data/test1525 +++ b/tests/data/test1525 @@ -8,6 +8,7 @@ HTTP CONNECT HTTP proxy proxytunnel CURLOPT_PROXYHEADER +libtest @@ -43,9 +44,6 @@ ETag: "21025-dc7-39462498" http http-proxy - -lib%TESTNUMBER - CURLOPT_PROXYHEADER is ignored CURLHEADER_UNIFIED diff --git a/tests/data/test1526 b/tests/data/test1526 index 6c78989fa36d..20309e213b62 100644 --- a/tests/data/test1526 +++ b/tests/data/test1526 @@ -8,6 +8,7 @@ HTTP CONNECT HTTP proxy proxytunnel CURLOPT_PROXYHEADER +libtest @@ -45,9 +46,6 @@ ETag: "21025-dc7-39462498" http http-proxy - -lib%TESTNUMBER - CURLOPT_PROXYHEADER: separate host/proxy headers diff --git a/tests/data/test1527 b/tests/data/test1527 index 731aea2ac91e..29f9c755d1e3 100644 --- a/tests/data/test1527 +++ b/tests/data/test1527 @@ -7,6 +7,7 @@ HTTP PUT HTTP CONNECT HTTP proxy proxytunnel +libtest @@ -44,9 +45,6 @@ ETag: "21025-dc7-39462498" http http-proxy - -lib%TESTNUMBER - Same headers with CURLOPT_HEADEROPT == CURLHEADER_UNIFIED diff --git a/tests/data/test1528 b/tests/data/test1528 index a2994988bd28..422ba3925ac9 100644 --- a/tests/data/test1528 +++ b/tests/data/test1528 @@ -7,6 +7,7 @@ HTTP GET HTTP CONNECT HTTP proxy proxytunnel +libtest @@ -35,9 +36,6 @@ stop http http-proxy - -lib%TESTNUMBER - Separately specified proxy/server headers sent in a proxy GET diff --git a/tests/data/test1529 b/tests/data/test1529 index f31f70fb4491..24f4302d1ec3 100644 --- a/tests/data/test1529 +++ b/tests/data/test1529 @@ -5,6 +5,7 @@ HTTP HTTP GET HTTP proxy +libtest @@ -23,9 +24,6 @@ We-are: good http http-proxy - -lib%TESTNUMBER - HTTP request-injection in URL sent over proxy diff --git a/tests/data/test1530 b/tests/data/test1530 index dba6bce5c5a3..dc2b480bcde2 100644 --- a/tests/data/test1530 +++ b/tests/data/test1530 @@ -3,6 +3,7 @@ CURLOPT_OPENSOCKETFUNCTION +libtest @@ -10,9 +11,6 @@ CURLOPT_OPENSOCKETFUNCTION http - -lib%TESTNUMBER - CURLOPT_OPENSOCKETFUNCTION returns bad socket diff --git a/tests/data/test1531 b/tests/data/test1531 index d85ccf49b70f..62675ea7f6fa 100644 --- a/tests/data/test1531 +++ b/tests/data/test1531 @@ -3,6 +3,7 @@ CURLOPT_POSTFIELDS +libtest @@ -13,9 +14,6 @@ http http - -lib%TESTNUMBER - CURLOPT_POSTFIELDS with binary data set after multi_add_handle diff --git a/tests/data/test1532 b/tests/data/test1532 index b11938bec131..2ab148eae60a 100644 --- a/tests/data/test1532 +++ b/tests/data/test1532 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -22,9 +23,6 @@ Content-Length: 0 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Test CURLINFO_RESPONSE_CODE diff --git a/tests/data/test1533 b/tests/data/test1533 index 759c237bd47a..9580fa84838c 100644 --- a/tests/data/test1533 +++ b/tests/data/test1533 @@ -5,6 +5,7 @@ HTTP HTTP POST CURLOPT_KEEP_SENDING_ON_ERROR +libtest @@ -27,9 +28,6 @@ Early Response http - -lib%TESTNUMBER - HTTP with CURLOPT_KEEP_SENDING_ON_ERROR and an early error response diff --git a/tests/data/test1534 b/tests/data/test1534 index 4e534a1ee8c7..0ab8b7052ded 100644 --- a/tests/data/test1534 +++ b/tests/data/test1534 @@ -5,6 +5,7 @@ HTTP HTTP GET CURLINFO_FILETIME +libtest @@ -24,9 +25,6 @@ Content-Length: 0 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - CURLINFO_FILETIME init and reset diff --git a/tests/data/test1535 b/tests/data/test1535 index b5805d8915a6..fdf2b4e0efb9 100644 --- a/tests/data/test1535 +++ b/tests/data/test1535 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -23,9 +24,6 @@ Content-Length: 0 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Test CURLINFO_RESPONSE_CODE CURLINFO_PROTOCOL diff --git a/tests/data/test1536 b/tests/data/test1536 index c76e7593a73a..2fb7ae49455f 100644 --- a/tests/data/test1536 +++ b/tests/data/test1536 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -23,9 +24,6 @@ Content-Length: 0 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Test CURLINFO_RESPONSE_CODE CURLINFO_SCHEME diff --git a/tests/data/test1537 b/tests/data/test1537 index 1c48d3661367..8326b3401587 100644 --- a/tests/data/test1537 +++ b/tests/data/test1537 @@ -3,6 +3,7 @@ URL escape +libtest @@ -12,9 +13,6 @@ URL escape # Client-side - -lib%TESTNUMBER - libcurl URL escape/unescape tests diff --git a/tests/data/test1538 b/tests/data/test1538 index bdfe9478edd4..2c9dd8f0033a 100644 --- a/tests/data/test1538 +++ b/tests/data/test1538 @@ -4,6 +4,7 @@ strerror verbose logs +libtest @@ -16,9 +17,6 @@ verbose logs verbose-strings - -lib%TESTNUMBER - libcurl strerror API call tests diff --git a/tests/data/test1539 b/tests/data/test1539 index 4a5b8e6d0039..fc7922b6bef0 100644 --- a/tests/data/test1539 +++ b/tests/data/test1539 @@ -6,6 +6,7 @@ HTTP HTTP POST Content-Length chunked Transfer-Encoding +libtest diff --git a/tests/data/test1540 b/tests/data/test1540 index b9b05f3a46d2..c23f75036e22 100644 --- a/tests/data/test1540 +++ b/tests/data/test1540 @@ -8,6 +8,7 @@ CURLPAUSE_RECV chunked Transfer-Encoding Trailer: DELAY +libtest @@ -45,9 +46,6 @@ writedelay: 10 http - -lib%TESTNUMBER - chunked with trailers and pausing the receive diff --git a/tests/data/test1541 b/tests/data/test1541 index 2f030cc904b1..b12e6e202917 100644 --- a/tests/data/test1541 +++ b/tests/data/test1541 @@ -7,6 +7,7 @@ HTTP GET chunked Transfer-Encoding Trailer: DELAY +libtest @@ -52,9 +53,6 @@ writedelay: 10 http - -lib%TESTNUMBER - CURLINFO timer check diff --git a/tests/data/test1542 b/tests/data/test1542 index 2738fbb44068..640e90f8c0e8 100644 --- a/tests/data/test1542 +++ b/tests/data/test1542 @@ -7,6 +7,7 @@ connection reuse persistent connection CURLOPT_MAXLIFETIME_CONN verbose logs +libtest @@ -28,9 +29,6 @@ http verbose-strings - -lib%TESTNUMBER - connection reuse with CURLOPT_MAXLIFETIME_CONN diff --git a/tests/data/test1543 b/tests/data/test1543 index 2433447c6ac0..223bbc183b66 100644 --- a/tests/data/test1543 +++ b/tests/data/test1543 @@ -4,6 +4,7 @@ HTTP CURLOPT_CURLU +libtest # Similar to 1518 but using CURLOPT_CURLU diff --git a/tests/data/test1545 b/tests/data/test1545 index 9007daf2bef4..8fdfe54b6fac 100644 --- a/tests/data/test1545 +++ b/tests/data/test1545 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # Server-side @@ -19,9 +20,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - use curl_formadd() data twice with unreadable file diff --git a/tests/data/test1549 b/tests/data/test1549 index 7aaa2d9835d5..44c102ffa7ec 100644 --- a/tests/data/test1549 +++ b/tests/data/test1549 @@ -4,6 +4,7 @@ HTTP cookies +libtest @@ -32,9 +33,6 @@ cookies Receive expired cookies and CURLINFO_COOKIELIST - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1550 b/tests/data/test1550 index 67fe2fafafef..78a27d144050 100644 --- a/tests/data/test1550 +++ b/tests/data/test1550 @@ -3,6 +3,7 @@ multi +libtest @@ -13,9 +14,6 @@ multi # Client-side # tool is what to use instead of 'curl' - -lib%TESTNUMBER - verify setting pipelining blocklisting options diff --git a/tests/data/test1551 b/tests/data/test1551 index 0f3296847274..ce5524a27f34 100644 --- a/tests/data/test1551 +++ b/tests/data/test1551 @@ -3,6 +3,7 @@ multi +libtest @@ -35,9 +36,6 @@ redirected http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - re-run redirected transfer without setting URL again diff --git a/tests/data/test1552 b/tests/data/test1552 index 3b1a57f86f0f..b4b7a7bb4355 100644 --- a/tests/data/test1552 +++ b/tests/data/test1552 @@ -5,6 +5,7 @@ IMAP Clear Text FETCH +libtest @@ -34,9 +35,6 @@ imap IMAP multi transfer error without curl_multi_remove_handle # tool is what to use instead of 'curl' - -lib%TESTNUMBER - 'imap://%HOSTIP:%IMAPPORT/%TESTNUMBER/;MAILINDEX=1' diff --git a/tests/data/test1553 b/tests/data/test1553 index 84f383085225..aca45aabb977 100644 --- a/tests/data/test1553 +++ b/tests/data/test1553 @@ -5,6 +5,7 @@ IMAP Clear Text FETCH +libtest @@ -37,9 +38,6 @@ imap IMAP cleanup with non-existing hostname # tool is what to use instead of 'curl' - -lib%TESTNUMBER - # this MUST use a hostname that does not resolve diff --git a/tests/data/test1554 b/tests/data/test1554 index 38ea1feaa387..93174472f32a 100644 --- a/tests/data/test1554 +++ b/tests/data/test1554 @@ -5,6 +5,7 @@ HTTP HTTP GET shared connections +libtest @@ -28,8 +29,6 @@ run 1: foobar and so on fun! [- Mutex unlock CONNECT -] Mutex lock CONNECT [- Mutex unlock CONNECT --] Mutex lock CONNECT -[- Mutex unlock CONNECT run 1: foobar and so on fun! -] Mutex lock CONNECT [- Mutex unlock CONNECT @@ -41,8 +40,6 @@ run 1: foobar and so on fun! [- Mutex unlock CONNECT -] Mutex lock CONNECT [- Mutex unlock CONNECT --] Mutex lock CONNECT -[- Mutex unlock CONNECT run 1: foobar and so on fun! -] Mutex lock CONNECT [- Mutex unlock CONNECT @@ -54,8 +51,6 @@ run 1: foobar and so on fun! [- Mutex unlock CONNECT -] Mutex lock CONNECT [- Mutex unlock CONNECT --] Mutex lock CONNECT -[- Mutex unlock CONNECT run 1: foobar and so on fun! -] Mutex lock CONNECT [- Mutex unlock CONNECT @@ -76,9 +71,6 @@ http HTTP with shared connection cache - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1555 b/tests/data/test1555 index 1212531686f1..c945c1ede50c 100644 --- a/tests/data/test1555 +++ b/tests/data/test1555 @@ -4,6 +4,7 @@ RECURSIVE_API_CALL DELAY +libtest @@ -23,9 +24,6 @@ wait 10 http - -lib%TESTNUMBER - verify api is protected against calls from callbacks diff --git a/tests/data/test1556 b/tests/data/test1556 index 30a9c4007edb..6679243cde64 100644 --- a/tests/data/test1556 +++ b/tests/data/test1556 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -31,9 +32,6 @@ Long: %repeat[100001 x A]% http - -lib%TESTNUMBER - send long HTTP headers to header callback diff --git a/tests/data/test1557 b/tests/data/test1557 index 9b4ed73fa732..ec61a50d00bf 100644 --- a/tests/data/test1557 +++ b/tests/data/test1557 @@ -4,6 +4,7 @@ multi crash +libtest @@ -11,9 +12,6 @@ crash - -lib%TESTNUMBER - Remove easy handle in pending connections, no dangling entry diff --git a/tests/data/test1558 b/tests/data/test1558 index 9070a6dd7177..07d552ffe810 100644 --- a/tests/data/test1558 +++ b/tests/data/test1558 @@ -4,6 +4,7 @@ curl_easy_getinfo CURLINFO_PROTOCOL +libtest @@ -14,9 +15,6 @@ CURLINFO_PROTOCOL file - -lib%TESTNUMBER - CURLINFO_PROTOCOL for file:// transfer diff --git a/tests/data/test1559 b/tests/data/test1559 index 884c490a4c93..249ac7672836 100644 --- a/tests/data/test1559 +++ b/tests/data/test1559 @@ -4,6 +4,7 @@ CURLOPT_URL verbose logs +libtest @@ -17,9 +18,6 @@ verbose logs http verbose-strings - -lib%TESTNUMBER - Set excessive URL lengths diff --git a/tests/data/test1560 b/tests/data/test1560 index a710c8a683b5..30f3677a0397 100644 --- a/tests/data/test1560 +++ b/tests/data/test1560 @@ -2,9 +2,9 @@ -unittest urlapi url +libtest @@ -26,9 +26,6 @@ ftp URL API - -lib%TESTNUMBER - diff --git a/tests/data/test1564 b/tests/data/test1564 index 6d9bd745d2b5..c5dd0466ec64 100644 --- a/tests/data/test1564 +++ b/tests/data/test1564 @@ -4,6 +4,7 @@ multi wakeup +libtest @@ -16,9 +17,6 @@ wakeup wakeup - -lib%TESTNUMBER - wakeup before poll with no easy handles diff --git a/tests/data/test1565 b/tests/data/test1565 index 78fe7f1f807d..002ffeb09f3c 100644 --- a/tests/data/test1565 +++ b/tests/data/test1565 @@ -7,6 +7,7 @@ HTTP GET multi multi-threaded wakeup +libtest @@ -28,9 +29,6 @@ wakeup http - -lib%TESTNUMBER - wakeup from another thread diff --git a/tests/data/test1567 b/tests/data/test1567 index 86198e3246cf..9040528db1fd 100644 --- a/tests/data/test1567 +++ b/tests/data/test1567 @@ -6,6 +6,7 @@ HTTP URL API CURLOPT_CURLU CURLOPT_FOLLOWLOCATION +libtest @@ -36,9 +37,6 @@ redirected http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - re-run redirected transfer without setting CURLU URL again diff --git a/tests/data/test1568 b/tests/data/test1568 index b9b5ddc1770c..7102df0267a8 100644 --- a/tests/data/test1568 +++ b/tests/data/test1568 @@ -7,6 +7,7 @@ HTTP HTTP GET HTTP Digest auth +libtest @@ -62,9 +63,6 @@ digest HTTP with Digest authorization on custom CURLOPT_PORT - -lib%TESTNUMBER - http://%HOSTIP/%TESTNUMBER %HTTPPORT diff --git a/tests/data/test1569 b/tests/data/test1569 index faa6968e3887..e77c2fed3990 100644 --- a/tests/data/test1569 +++ b/tests/data/test1569 @@ -5,6 +5,7 @@ FTP PASV RETR +libtest # Server-side @@ -30,9 +31,6 @@ ftp FTP first type=A then regular URL - -lib%TESTNUMBER - "ftp://%HOSTIP:%FTPPORT/%TESTNUMBER;type=A" ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test1570 b/tests/data/test1570 index 38b297842bfb..41a0976de911 100644 --- a/tests/data/test1570 +++ b/tests/data/test1570 @@ -5,6 +5,7 @@ FTP PASV RETR +libtest # Server-side diff --git a/tests/data/test1571 b/tests/data/test1571 index 61b9029b1a61..d86b5bb0b18d 100644 --- a/tests/data/test1571 +++ b/tests/data/test1571 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side @@ -61,9 +62,6 @@ http http - -lib%TESTNUMBER - CURLFOLLOW_OBEYCODE with custom POST method, 302 => GET diff --git a/tests/data/test1572 b/tests/data/test1572 index e63bf49b0794..dfea962eb0e4 100644 --- a/tests/data/test1572 +++ b/tests/data/test1572 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1573 b/tests/data/test1573 index 14a6ca956022..452d707acbf5 100644 --- a/tests/data/test1573 +++ b/tests/data/test1573 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1574 b/tests/data/test1574 index d0f65be9bffc..72a931bcdc5b 100644 --- a/tests/data/test1574 +++ b/tests/data/test1574 @@ -4,6 +4,7 @@ CURLFOLLOW_FIRSTONLY CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1575 b/tests/data/test1575 index dc2d86e09f3d..494cb42ae2e6 100644 --- a/tests/data/test1575 +++ b/tests/data/test1575 @@ -4,6 +4,7 @@ CURLFOLLOW_FIRSTONLY CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1576 b/tests/data/test1576 index c6b395d58fa0..a16ee6bcb884 100644 --- a/tests/data/test1576 +++ b/tests/data/test1576 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side @@ -61,9 +62,6 @@ http http - -lib%TESTNUMBER - CURLFOLLOW_OBEYCODE with custom PUT method, 302 => custom diff --git a/tests/data/test1577 b/tests/data/test1577 index ca47483fc649..84c66bd81bc7 100644 --- a/tests/data/test1577 +++ b/tests/data/test1577 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1578 b/tests/data/test1578 index 2a48d50fd4fd..260a436d7816 100644 --- a/tests/data/test1578 +++ b/tests/data/test1578 @@ -4,6 +4,7 @@ CURLFOLLOW_FIRSTONLY CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1579 b/tests/data/test1579 index 29ffc76229ad..e40fe1f6a57d 100644 --- a/tests/data/test1579 +++ b/tests/data/test1579 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test158 b/tests/data/test158 index a6b1b94ea258..56afb9cd3d98 100644 --- a/tests/data/test158 +++ b/tests/data/test158 @@ -4,6 +4,7 @@ HTTP HTTP POST +Resume @@ -25,10 +26,10 @@ Mime http -HTTP multipart formpost with only a 100 reply +HTTP multipart formpost with zero resume offset and only a 100 reply -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -F name=daniel +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -F name=daniel -C 0 diff --git a/tests/data/test1580 b/tests/data/test1580 index 4dc10a1f9eff..6ac8d2c136ee 100644 --- a/tests/data/test1580 +++ b/tests/data/test1580 @@ -4,6 +4,7 @@ CURLFOLLOW_FIRSTONLY CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1581 b/tests/data/test1581 index 458d8c9a5cd6..adb453738e1d 100644 --- a/tests/data/test1581 +++ b/tests/data/test1581 @@ -4,6 +4,7 @@ CURLFOLLOW_OBEYCODE CURLOPT_FOLLOWLOCATION +libtest # Server-side diff --git a/tests/data/test1582 b/tests/data/test1582 index 6c5e55d2298d..46f552dca336 100644 --- a/tests/data/test1582 +++ b/tests/data/test1582 @@ -4,6 +4,7 @@ HTTPS GSS-API +libtest @@ -34,9 +35,6 @@ https Negotiate over HTTPS with channel binding - -lib%TESTNUMBER - https://%HOSTIP:%HTTPSPORT/ diff --git a/tests/data/test1587 b/tests/data/test1587 index 880652793532..c00fdd16d667 100644 --- a/tests/data/test1587 +++ b/tests/data/test1587 @@ -4,6 +4,7 @@ HTTPS CURLINFO_TLS_SESSION +libtest @@ -33,9 +34,6 @@ https CURLINFO_TLS_SESSION and CURLINFO_TLS_SSL_PTR with OpenSSL - -lib%TESTNUMBER - https://%HOSTIP:%HTTPSPORT/ diff --git a/tests/data/test1588 b/tests/data/test1588 index 30ec8ca91282..c33f96d1cb58 100644 --- a/tests/data/test1588 +++ b/tests/data/test1588 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy HTTP proxy Digest auth multi +libtest @@ -59,9 +60,6 @@ Nice proxy auth sir! http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test1589 b/tests/data/test1589 index 527edb54cab4..07ce45371bb8 100644 --- a/tests/data/test1589 +++ b/tests/data/test1589 @@ -5,6 +5,7 @@ HTTP HTTP proxy HTTP proxy Digest auth +libtest @@ -58,9 +59,6 @@ http http-proxy # tool is what to use instead of 'curl' - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test1590 b/tests/data/test1590 index dd660eb30f79..a6f5cdefa8a7 100644 --- a/tests/data/test1590 +++ b/tests/data/test1590 @@ -5,6 +5,7 @@ IMAP Clear Text FETCH +libtest diff --git a/tests/data/test1591 b/tests/data/test1591 index a6f72c3ac124..91b1b7980e71 100644 --- a/tests/data/test1591 +++ b/tests/data/test1591 @@ -6,6 +6,7 @@ HTTP HTTP PUT CURLOPT_HTTPTRAILER_FUNCTION CURLOPT_HTTPTRAILER_DATA +libtest @@ -28,9 +29,6 @@ http HTTP PUT with trailers at the end - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/bzz/%TESTNUMBER diff --git a/tests/data/test1592 b/tests/data/test1592 index 36e7fc8ab104..78553c70fdcc 100644 --- a/tests/data/test1592 +++ b/tests/data/test1592 @@ -8,14 +8,12 @@ resolve speedcheck flaky timing-dependent +libtest # Client-side - -lib%TESTNUMBER - HTTP request, remove handle while resolving, do not block diff --git a/tests/data/test1593 b/tests/data/test1593 index ef4b21f2d1c4..cc924d5bcb05 100644 --- a/tests/data/test1593 +++ b/tests/data/test1593 @@ -7,6 +7,7 @@ HTTP GET HTTP replaced headers CURLOPT_TIMECONDITION If-Modified-Since +libtest @@ -27,9 +28,6 @@ http HTTP custom header overrides CURLOPT_TIMECONDITION - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1594 b/tests/data/test1594 index 8c8b51c56ca4..d741a4e1b8ae 100644 --- a/tests/data/test1594 +++ b/tests/data/test1594 @@ -7,6 +7,7 @@ HTTP GET HTTP replaced headers CURLOPT_TIMECONDITION If-Modified-Since +libtest @@ -28,9 +29,6 @@ http HTTP Retry-After header parsing and extraction - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1595 b/tests/data/test1595 index 996a0cd40974..6e9598ed5383 100644 --- a/tests/data/test1595 +++ b/tests/data/test1595 @@ -7,6 +7,7 @@ HTTP GET HTTP replaced headers CURLOPT_TIMECONDITION If-Modified-Since +libtest diff --git a/tests/data/test1596 b/tests/data/test1596 index 202b5e40f54d..7fe3200a0b7c 100644 --- a/tests/data/test1596 +++ b/tests/data/test1596 @@ -7,6 +7,7 @@ HTTP GET HTTP replaced headers CURLOPT_TIMECONDITION If-Modified-Since +libtest diff --git a/tests/data/test1597 b/tests/data/test1597 index 7f18cf41b748..7dcc7c1d2a9f 100644 --- a/tests/data/test1597 +++ b/tests/data/test1597 @@ -3,6 +3,7 @@ CURLOPT_PROTOCOLS_STR +libtest @@ -14,9 +15,6 @@ CURLOPT_PROTOCOLS_STR CURLOPT_PROTOCOLS_STR - -lib%TESTNUMBER - diff --git a/tests/data/test1598 b/tests/data/test1598 index b241677ed67a..e8626e252542 100644 --- a/tests/data/test1598 +++ b/tests/data/test1598 @@ -6,6 +6,7 @@ HTTP HTTP POST CURLOPT_HTTPTRAILER_FUNCTION CURLOPT_HTTPTRAILER_DATA +libtest @@ -28,9 +29,6 @@ http HTTP POST with trailers at the end - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/bzz/%TESTNUMBER diff --git a/tests/data/test1599 b/tests/data/test1599 index 054a1b68de36..d9e80d626b2a 100644 --- a/tests/data/test1599 +++ b/tests/data/test1599 @@ -4,6 +4,7 @@ FTP netrc +libtest @@ -26,9 +27,6 @@ FTP with netrc using no user but control code in password ftp://%HOSTIP:%FTPPORT/%TESTNUMBER %LOGDIR/netrc%TESTNUMBER - -lib%TESTNUMBER - default passwor?dlogin anonymou\ ' password login anonymous passwor?d.'macdef diff --git a/tests/data/test1604 b/tests/data/test1604 index 79bcd0818ae8..9bf67e43c575 100644 --- a/tests/data/test1604 +++ b/tests/data/test1604 @@ -2,7 +2,7 @@ -unittest +tunittest @@ -14,8 +14,5 @@ unittest Test Windows/MS-DOS filename sanitization - -tool%TESTNUMBER - diff --git a/tests/data/test1605 b/tests/data/test1605 index 81431adb733c..5aab454e7a70 100644 --- a/tests/data/test1605 +++ b/tests/data/test1605 @@ -2,15 +2,13 @@ -unittest +curl_easy_escape +libtest # Client-side - -unittest - Test negative data lengths as input to libcurl functions diff --git a/tests/data/test1616 b/tests/data/test1616 index c0719b091f9f..7b3c76b44ee2 100644 --- a/tests/data/test1616 +++ b/tests/data/test1616 @@ -2,18 +2,105 @@ -unittest -uint_hash +HTTP +HTTP GET +HTTP proxy +HTTP proxy Digest auth +libtest +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 12 +Connection: close + +proxied one + + + +HTTP/1.1 407 Proxy Authentication Required +Proxy-Authenticate: Digest realm="attacker", nonce="1053604598" +Content-Length: 7 +Connection: close + +direct + + + +HTTP/1.1 407 Proxy Authentication Required +Proxy-Authenticate: Digest realm="tunnel-attacker", nonce="1053604598" +Content-Length: 9 +Connection: close + +tunneled + + + +HTTP/1.1 200 Mighty fine indeed + + + + +proxied one +direct +proxied one +tunneled +proxied one + + + # Client-side + +http + -unittest +!SSPI +crypto +proxy +digest -uint_hash create/add/destroy testing, exercising clean functions +HTTP 407 from a non-proxied host must not set proxy Digest state + +http://test.remote.example.com/%TESTNUMBER0001 %HOSTIP %HTTPPORT %TESTNUMBER + + +# Verify data after the test has been "shot" + + +GET http://test.remote.example.com/%TESTNUMBER0001 HTTP/1.1 +Host: test.remote.example.com +Accept: */* +Proxy-Connection: Keep-Alive + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + +GET http://test.remote.example.com/%TESTNUMBER0001 HTTP/1.1 +Host: test.remote.example.com +Accept: */* +Proxy-Connection: Keep-Alive + +CONNECT test.remote.example.com:%HTTPPORT HTTP/1.1 +Host: test.remote.example.com:%HTTPPORT +Proxy-Connection: Keep-Alive + +GET /%TESTNUMBER0004 HTTP/1.1 +Host: test.remote.example.com:%HTTPPORT +Accept: */* + +GET http://test.remote.example.com/%TESTNUMBER0001 HTTP/1.1 +Host: test.remote.example.com +Accept: */* +Proxy-Connection: Keep-Alive + + + diff --git a/tests/data/test1621 b/tests/data/test1621 index 19f9edb5a96b..ecee477da4a6 100644 --- a/tests/data/test1621 +++ b/tests/data/test1621 @@ -2,7 +2,7 @@ -unittest +tunittest stripcredentials @@ -15,8 +15,5 @@ unittest unit tests for stripcredentials from URL - -tool%TESTNUMBER - diff --git a/tests/data/test1622 b/tests/data/test1622 index 59895a60c660..534202bddd0c 100644 --- a/tests/data/test1622 +++ b/tests/data/test1622 @@ -2,7 +2,7 @@ -unittest +tunittest tool-progress @@ -15,9 +15,6 @@ unittest tool time and size progress output - -tool%TESTNUMBER - diff --git a/tests/data/test1623 b/tests/data/test1623 index 000c08a2ef32..73cf40d0765d 100644 --- a/tests/data/test1623 +++ b/tests/data/test1623 @@ -2,7 +2,7 @@ -unittest +tunittest tool-sizeparser @@ -15,9 +15,6 @@ unittest the size parser for --limit-rate - -tool%TESTNUMBER - diff --git a/tests/data/test1627 b/tests/data/test1627 index 95fd47d83fb2..94079f9c5b49 100644 --- a/tests/data/test1627 +++ b/tests/data/test1627 @@ -18,7 +18,7 @@ Curl_get_scheme unit test -547 invokes +541 invokes diff --git a/tests/data/test1647 b/tests/data/test1647 index a87487fa9f0c..fe6411d8e5d6 100644 --- a/tests/data/test1647 +++ b/tests/data/test1647 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy HTTP proxy Digest auth multi +libtest @@ -49,9 +50,6 @@ https-proxy https # tool is what to use instead of 'curl' - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test1648 b/tests/data/test1648 index 623f3c9a81ae..a8c7908c0da1 100644 --- a/tests/data/test1648 +++ b/tests/data/test1648 @@ -6,6 +6,7 @@ HTTP HTTP GET HTTP proxy HTTP proxy auth +libtest @@ -29,9 +30,6 @@ And you should ignore this data. http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - proxy diff --git a/tests/data/test1649 b/tests/data/test1649 index d2fd7799bd87..2ec097782ab9 100644 --- a/tests/data/test1649 +++ b/tests/data/test1649 @@ -4,6 +4,7 @@ HTTP Referer +libtest @@ -26,9 +27,6 @@ hello http - -lib%TESTNUMBER - Set referer first then NULL it diff --git a/tests/data/test1662 b/tests/data/test1662 index fce2e8161060..4cfa2b940b31 100644 --- a/tests/data/test1662 +++ b/tests/data/test1662 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest @@ -45,9 +46,6 @@ http HTTP formpost from callback and a redirect and switch to GET - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1664 b/tests/data/test1664 index ccb0579be775..a8f2bffb14f8 100644 --- a/tests/data/test1664 +++ b/tests/data/test1664 @@ -77,14 +77,6 @@ curlx_str_singlespace 7: (" ") 5, line 0 8: ("") 5, line 0 -curlx_str_single -0: ("a") 0, line 1 -1: ("aa") 0, line 1 -2: ("A") 5, line 0 -3: ("b") 5, line 0 -4: ("\") 5, line 0 -5: (" ") 5, line 0 -6: ("") 5, line 0 curlx_str_number 0: ("1") 0, [1] line 1 1: ("10000") 7, [0] line 0 diff --git a/tests/data/test1678 b/tests/data/test1678 index c0cada0ba79f..6aa869fd331e 100644 --- a/tests/data/test1678 +++ b/tests/data/test1678 @@ -3,6 +3,7 @@ ssls-import +libtest @@ -11,9 +12,6 @@ ssls-import SSLS-EXPORT - -lib%TESTNUMBER - SSL session import tests diff --git a/tests/data/test1679 b/tests/data/test1679 new file mode 100644 index 000000000000..618d28ae787b --- /dev/null +++ b/tests/data/test1679 @@ -0,0 +1,63 @@ + + + + +HTTP +--libcurl +--next +--insecure + + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Connection: close + + + + + + +http +SSL +--libcurl + + +http + + +--libcurl resets SSL verification after --next + + +SSL_CERT_FILE + + +http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER --libcurl %LOGDIR/test%TESTNUMBER.c --insecure --next http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER + + + + + +GET /we/want/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /we/want/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# CURLOPT_INTERLEAVEDATA requires RTSP protocol +$_ = '' if /CURLOPT_INTERLEAVEDATA/ +$_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ + + +%includetext %SRCDIR/data/data%TESTNUMBER.c% + + + diff --git a/tests/data/test1683 b/tests/data/test1683 index 5a01be74dc8b..ff86f6017fd0 100644 --- a/tests/data/test1683 +++ b/tests/data/test1683 @@ -30,7 +30,11 @@ http http +Debug + +CURL_MAX_CLOBBER=100 + http://%HOSTIP:%HTTPPORT/%TESTNUMBER -o %LOGDIR/exist%TESTNUMBER --no-clobber diff --git a/tests/data/test1686 b/tests/data/test1686 index 2d419ad60834..79ac376b5d2c 100644 --- a/tests/data/test1686 +++ b/tests/data/test1686 @@ -4,6 +4,7 @@ HTTP Digest +libtest @@ -42,9 +43,6 @@ http HTTP Digest to different origins and switching credentials - -lib%TESTNUMBER - %HOSTIP %HTTPPORT diff --git a/tests/data/test1687 b/tests/data/test1687 new file mode 100644 index 000000000000..128970f70deb --- /dev/null +++ b/tests/data/test1687 @@ -0,0 +1,75 @@ + + + + +HTTP +HTTP GET +--libcurl +--next + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Thu, 29 Jul 2008 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 +Connection: close + + + + +# Client-side + + +--libcurl + + +http + + +--libcurl resets the handle between --next operations + + +SSL_CERT_FILE + + +http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER -H "X-First: yes" --libcurl %LOGDIR/test%TESTNUMBER.c --next http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /we/want/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +X-First: yes + +GET /we/want/%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# CURLOPT_SSL_VERIFYPEER, SSL_VERIFYHOST, SSLVERSION, SSH_KNOWNHOSTS and +# HTTP_VERSION vary with configurations, CURLOPT_INTERLEAVEDATA requires +# RTSP protocol - ignore them +$_ = '' if /CURLOPT_SSL_VERIFYPEER/ +$_ = '' if /CURLOPT_SSL_VERIFYHOST/ +$_ = '' if /CURLOPT_SSLVERSION/ +$_ = '' if /CURLOPT_SSH_KNOWNHOSTS/ +$_ = '' if /CURLOPT_HTTP_VERSION/ +$_ = '' if /CURLOPT_HTTP09_ALLOWED/ +$_ = '' if /CURLOPT_INTERLEAVEDATA/ +$_ = '' if /CURLOPT_TIMEOUT_MS/ + + +%includetext %SRCDIR/data/data%TESTNUMBER.c% + + + diff --git a/tests/data/test1688 b/tests/data/test1688 new file mode 100644 index 000000000000..256b81373e63 --- /dev/null +++ b/tests/data/test1688 @@ -0,0 +1,58 @@ + + + + +RTSP +OPTIONS +libtest + + + +# Server-side + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 1 +Public: DESCRIBE, OPTIONS, SETUP, TEARDOWN, PLAY, PAUSE +Content-Length: 18 + +No-More: sessions + + + +# Client-side + + +rtsp + + +lib567 + + +RTSP OPTIONS response with a body, CURLOPT_NOBODY set + + +rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +OPTIONS rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER RTSP/1.0 +CSeq: 1 +User-Agent: test567 +Test-Number: 567 + + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 1 +Public: DESCRIBE, OPTIONS, SETUP, TEARDOWN, PLAY, PAUSE +Content-Length: 18 + + + + diff --git a/tests/data/test1689 b/tests/data/test1689 new file mode 100644 index 000000000000..a1e6f5dd528a --- /dev/null +++ b/tests/data/test1689 @@ -0,0 +1,68 @@ + + + + +HTTP +HTTP GET +--styled-output + + + +# +# Server-side + + +HTTP/1.1 301 Moved Permanently +Date: Tue, 09 Nov 2010 14:49:00 GMT +Location: /moved/here +Content-Length: 6 +Connection: close + +-foo- + + + +# +# Client-side + + +http + +# this test needs to be a Debug build in order to fake it being a TTY output + +Debug +!win32 + + +VTE_VERSION=4802 +CURL_ISATTY=yes + + +--styled-output makes header names bold and Location a link + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --show-headers + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +HTTP/1.1 301 Moved Permanently +%hex[%1b]hex%[1mDate%hex[%1b]hex%[0m: Tue, 09 Nov 2010 14:49:00 GMT +%hex[%1b]hex%[1mLocation%hex[%1b]hex%[0m: %hex[%1b]hex%]8;;http://%HOSTIP:%HTTPPORT/moved/here%hex[%1b%5c]hex%/moved/here +%hex[%1b]hex%]8;;%hex[%1b%5c]hex%%hex[%1b]hex%[1mContent-Length%hex[%1b]hex%[0m: 6 +%hex[%1b]hex%[1mConnection%hex[%1b]hex%[0m: close + +-foo- + + + diff --git a/tests/data/test1690 b/tests/data/test1690 new file mode 100644 index 000000000000..f3d712faebd7 --- /dev/null +++ b/tests/data/test1690 @@ -0,0 +1,66 @@ + + + + +HTTP +HTTP GET +--styled-output + + + +# +# Server-side + + +HTTP/1.1 301 Moved Permanently +Date: Tue, 09 Nov 2010 14:49:00 GMT +Location: /moved/here +Content-Length: 6 +Connection: close + +-foo- + + + +# +# Client-side + + +http + +# this test needs to be a Debug build in order to fake it being a TTY output + +Debug + + +CURL_ISATTY=yes + + +--no-styled-output writes plain headers to a terminal + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --show-headers --no-styled-output + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +HTTP/1.1 301 Moved Permanently +Date: Tue, 09 Nov 2010 14:49:00 GMT +Location: /moved/here +Content-Length: 6 +Connection: close + +-foo- + + + diff --git a/tests/data/test1691 b/tests/data/test1691 new file mode 100644 index 000000000000..d84bff7fc8de --- /dev/null +++ b/tests/data/test1691 @@ -0,0 +1,49 @@ + + + + +IPFS + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 21 +Connection: close +Content-Type: text/plain + +Hello curl from IPFS + + + +# Client-side + + +ipfs + + +http + + +IPFS path with encoded slashes and dotdot stays under the gateway + + +--ipfs-gateway http://%HOSTIP:%HTTPPORT/private/gateway ipfs://bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u/%2f..%2f..%2f..%2f..%2f..%2fadmin/config + + + +# Verify data after the test has been "shot" + + +GET /private/gateway/ipfs/bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u/%2F..%2F..%2F..%2F..%2F..%2Fadmin/config HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1692 b/tests/data/test1692 new file mode 100644 index 000000000000..74e7db98bdc4 --- /dev/null +++ b/tests/data/test1692 @@ -0,0 +1,49 @@ + + + + +IPNS + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 21 +Connection: close +Content-Type: text/plain + +Hello curl from IPFS + + + +# Client-side + + +ipfs + + +http + + +IPNS path with encoded slashes and dotdot stays under the gateway + + +--ipfs-gateway http://%HOSTIP:%HTTPPORT/private/gateway ipns://bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u/%2f..%2f..%2f..%2f..%2f..%2fadmin/config + + + +# Verify data after the test has been "shot" + + +GET /private/gateway/ipns/bafybeidecnvkrygux6uoukouzps5ofkeevoqland7kopseiod6pzqvjg7u/%2F..%2F..%2F..%2F..%2F..%2Fadmin/config HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1693 b/tests/data/test1693 new file mode 100644 index 000000000000..c005ce9e6167 --- /dev/null +++ b/tests/data/test1693 @@ -0,0 +1,19 @@ + + + + +unittest +MQTT + + + + + +unittest +mqtt + + +MQTT partial sends, would-block, polling and packet ordering + + + diff --git a/tests/data/test1701 b/tests/data/test1701 new file mode 100644 index 000000000000..e4e3d044302a --- /dev/null +++ b/tests/data/test1701 @@ -0,0 +1,75 @@ + + + + +HTTP +HTTP GET +DELAY + + +# Server-side + +# the first response is exactly 60 bytes so that with writedelay the server +# sends it in three 20 byte writes and the unsolicited second response only +# starts arriving after the first transfer is complete and the connection +# has been returned to the pool + +HTTP/1.1 200 OK +Funny-head: yesyes +Content-Length: 6 + +first +HTTP/1.1 200 OK +Content-Length: 6 + +stray + + +HTTP/1.1 200 OK +Content-Length: 7 + +second + + +writedelay: 150 + + + +# Client-side + + +http + + +HTTP/1.1 idle connection with unsolicited data is not reused + +# --rate 1/s starts the second request 1000 ms after the first one started, +# which is about 700 ms after the first response completed and the +# connection went back to the pool, inside the one second window where the +# pool used to skip the alive check. The stray response has fully arrived +# about 750 ms in. + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002 --rate 1/s + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +first +second + + + diff --git a/tests/data/test1716 b/tests/data/test1716 new file mode 100644 index 000000000000..fad97f7037be --- /dev/null +++ b/tests/data/test1716 @@ -0,0 +1,54 @@ + + + + +HTTP +HTTP GET +retry + + + +# Server-side + + +HTTP/1.1 503 Service Unavailable +Content-Length: 6 +Connection: close + +-foo- + + + +# Client-side + + +http + + +Debug + + +CURL_DBG_TRUNCATE_FAIL=1 + + +--retry when the output file cannot be truncated + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --retry 2 --retry-delay 0 + + + +# Verify data after the test has been "shot" + + +23 + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test1720 b/tests/data/test1720 index 22edb19bafa5..1cbcb967d3de 100644 --- a/tests/data/test1720 +++ b/tests/data/test1720 @@ -2,7 +2,7 @@ -unittest +tunittest @@ -14,9 +14,6 @@ unittest create_dir_hierarchy() - -tool%TESTNUMBER - diff --git a/tests/data/test1742 b/tests/data/test1742 new file mode 100644 index 000000000000..fa440570856f --- /dev/null +++ b/tests/data/test1742 @@ -0,0 +1,60 @@ + + + + +HTTP +HTTP GET +SOCKS5 +libtest + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 +Connection: close + +-foo- + + + +-foo- + + +method 2 +user silly +password person +nmethods_min 1 +nmethods_max 1 + + + +# Client-side + + +proxy + + +http +socks5 + + +SOCKS5 CURLAUTH_ONLY does not offer the no authentication method + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER %HOSTIP:%SOCKSPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test1743 b/tests/data/test1743 new file mode 100644 index 000000000000..beb430356bf6 --- /dev/null +++ b/tests/data/test1743 @@ -0,0 +1,47 @@ + + + + +HTTP +HTTP GET +SOCKS5 +libtest + + + +# Server-side + + + + +method 0 + + + +# Client-side + + +proxy + + +http +socks5 + + +lib1742 + + +SOCKS5 CURLAUTH_ONLY rejects a proxy that selects no authentication + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER %HOSTIP:%SOCKSPORT + + + +# Verify data after the test has been "shot" + + +97 + + + diff --git a/tests/data/test1744 b/tests/data/test1744 new file mode 100644 index 000000000000..85fd6920e438 --- /dev/null +++ b/tests/data/test1744 @@ -0,0 +1,58 @@ + + + + +HTTP +HTTP GET +SOCKS5 +libtest + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 + +-foo- + + + +-foo- + + +method 0 + + + +# Client-side + + +proxy + + +http +socks5 + + +SOCKS5 CURLAUTH_ONLY does not reuse an unauthenticated connection + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER %HOSTIP:%SOCKSPORT + + + +# Verify data after the test has been "shot" + + +97 + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test1745 b/tests/data/test1745 new file mode 100644 index 000000000000..d06aca443b5d --- /dev/null +++ b/tests/data/test1745 @@ -0,0 +1,59 @@ + + + + +FTP +PASV +RETR +SOCKS5 +libtest + + + +# Server-side + + +silly content + + +method 0 +method2 2 +user silly +password person + + + +# Client-side + + +proxy + + +ftp +socks5 + + +SOCKS5 CURLAUTH_ONLY ignores auth done for the FTP data socket + + +ftp://%HOSTIP:%FTPPORT/%TESTNUMBER %HOSTIP:%SOCKSPORT + + + +# Verify data after the test has been "shot" + + +97 + + +USER anonymous +PASS ftp@example.com +PWD +EPSV +TYPE I +SIZE %TESTNUMBER +RETR %TESTNUMBER +QUIT + + + diff --git a/tests/data/test1790 b/tests/data/test1790 new file mode 100644 index 000000000000..80bf7ca5f8c7 --- /dev/null +++ b/tests/data/test1790 @@ -0,0 +1,48 @@ + + + + +FTP +FTPS +PASV +LIST + + + +# Server-side + + +REPLY "PROT P" 534 Policy denies SSL + + + +# Client-side + + +SSL + + +ftps + + +FTPS with rejected PROT P + + +--insecure ftps://%HOSTIP:%FTPSPORT/ + + + +# Verify data after the test has been "shot" + +# 64 == CURLE_USE_SSL_FAILED + +64 + + +USER anonymous +PASS ftp@example.com +PBSZ 0 +PROT P + + + diff --git a/tests/data/test1791 b/tests/data/test1791 new file mode 100644 index 000000000000..dccafa70c1e6 --- /dev/null +++ b/tests/data/test1791 @@ -0,0 +1,30 @@ + + + + +CURLOPT_POSTFIELDSIZE_LARGE +duphandle +libtest + + + +# Server-side + + + +# Client-side + + + +blank COPYPOSTFIELDS and duphandle + + + +localhost + + + +# Verify data after the test has been "shot" + + + diff --git a/tests/data/test1827 b/tests/data/test1827 new file mode 100644 index 000000000000..46c67db70425 --- /dev/null +++ b/tests/data/test1827 @@ -0,0 +1,51 @@ + + + + +HTTPS +HTTP GET +HTTP CONNECT +HTTP proxy + + +# Server-side + + +HTTP/1.1 407 Needs proxy authentication +HTTP/1.1 200 is not the answer: nonono +Server: test-server/fake swsclose yesyes +Proxy-Authenticate: Basic "oh please" + + + + +# Client-side + + +http + + +proxy + + +HTTP CONNECT with faked status line header + + +http://test.anything.really.com:%TESTNUMBER --proxy %HOSTIP:%HTTPPORT --proxytunnel + + + +# Verify data after the test has been "shot" + + +7 + + +CONNECT test.anything.really.com:%TESTNUMBER HTTP/1.1 +Host: test.anything.really.com:%TESTNUMBER +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + + + + diff --git a/tests/data/test1862 b/tests/data/test1862 new file mode 100644 index 000000000000..b9a29f6376d7 --- /dev/null +++ b/tests/data/test1862 @@ -0,0 +1,73 @@ + + + + +HTTP +HSTS + + + + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Length: 6 +Connection: close +Strict-Transport-Security: max-age=1152921504606846976 + +-foo- + + + + + +http + + +HSTS +Debug + + +# November 11, 2030 + +CURL_HSTS_HTTP=yes +CURL_TIME=1920582000 + + + +HSTS with extreme max-age + + +http://this.hsts.example:%HTTPPORT/%TESTNUMBER --hsts %LOGDIR/input%TESTNUMBER --resolve this.hsts.example:%HTTPPORT:%HOSTIP + + +# Your HSTS cache. https://curl.se/docs/hsts.html +# This file was generated by libcurl! Edit at your own risk. +somehere.example "20330525 03:33:20" +elsewhere.example.com "20330727 03:33:20" + + +test-duphandle + + + + +# we let it CONNECT to the server to confirm HSTS but deny from there + +GET /1862 HTTP/1.1 +Host: this.hsts.example:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# Your HSTS cache. https://curl.se/docs/hsts.html +# This file was generated by libcurl! Edit at your own risk. +somehere.example "20330525 03:33:20" +elsewhere.example.com "20330727 03:33:20" +this.hsts.example "20321109 23:00:00" + + + + diff --git a/tests/data/test1876 b/tests/data/test1876 new file mode 100644 index 000000000000..4fc3b9fd0252 --- /dev/null +++ b/tests/data/test1876 @@ -0,0 +1,67 @@ + + + + +netrc + + + +# Server-side + + +HTTP/1.1 301 redir +Content-Length: 0 +Content-Type: text/html +Location: %TESTNUMBER0002 + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Content-Type: text/html + + + + + +# Client-side + + +http + + +verify no credentials present in effective URL after redirect + + +machine %HOSTIP +login netrcuser +password netrcSECRET123 + + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --netrc-optional --netrc-file %LOGDIR/netrc -L -w 'EFFECTIVE_URL=%{url_effective}\n' + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Basic %b64[netrcuser:netrcSECRET123]b64% +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Basic %b64[netrcuser:netrcSECRET123]b64% +User-Agent: curl/%VERSION +Accept: */* + + + +EFFECTIVE_URL=http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002 + + + diff --git a/tests/data/test1877 b/tests/data/test1877 new file mode 100644 index 000000000000..508fe53c8a5a --- /dev/null +++ b/tests/data/test1877 @@ -0,0 +1,54 @@ + + + + +netrc +CURLINFO_REDIRECT_URL + + + +# Server-side + + +HTTP/1.1 301 redir +Content-Length: 0 +Content-Type: text/html +Location: %TESTNUMBER0002 + + + + +# Client-side + + +http + + +verify no credentials present in redirect URL after redirect + + +machine %HOSTIP +login netrcuser +password netrcSECRET123 + + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --netrc-optional --netrc-file %LOGDIR/netrc -w 'REDIRECT_URL=%{redirect_url}\n' + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Authorization: Basic %b64[netrcuser:netrcSECRET123]b64% +User-Agent: curl/%VERSION +Accept: */* + + + +REDIRECT_URL=http://%HOSTIP:%HTTPPORT/%TESTNUMBER0002 + + + diff --git a/tests/data/test1900 b/tests/data/test1900 index 08eb8f304d6e..f6597410edd7 100644 --- a/tests/data/test1900 +++ b/tests/data/test1900 @@ -4,6 +4,7 @@ HTTP HSTS +libtest @@ -21,9 +22,6 @@ http HSTS curl_easy_duphandle - -lib%TESTNUMBER - http://%HOSTIP:%NOLISTENPORT/not-there/%TESTNUMBER diff --git a/tests/data/test1901 b/tests/data/test1901 index e1e356dd8e52..4dfb330305a1 100644 --- a/tests/data/test1901 +++ b/tests/data/test1901 @@ -5,6 +5,7 @@ HTTP HTTP POST CURLOPT_READFUNCTION +libtest @@ -28,9 +29,6 @@ http Chunked HTTP POST from callback with CURLOPT_POSTFIELDSIZE set - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/boom diff --git a/tests/data/test1902 b/tests/data/test1902 index a794fd4e58e7..0dee174549f1 100644 --- a/tests/data/test1902 +++ b/tests/data/test1902 @@ -3,6 +3,7 @@ cookies +libtest @@ -15,9 +16,6 @@ set COOKIEFILE and COOKIEJAR but make no transfer cookies - -lib%TESTNUMBER - %LOGDIR/cookie%TESTNUMBER diff --git a/tests/data/test1903 b/tests/data/test1903 index 788197742126..205bd6ca0ef0 100644 --- a/tests/data/test1903 +++ b/tests/data/test1903 @@ -5,6 +5,7 @@ HTTP cookies CURLOPT_COOKIEFILE +libtest @@ -30,9 +31,6 @@ http CURLOPT_COOKIEFILE then reset then set again - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER %LOGDIR/cookies%TESTNUMBER %LOGDIR/cookiesout%TESTNUMBER diff --git a/tests/data/test1905 b/tests/data/test1905 index c47ccee00b99..9ee2e5f65f94 100644 --- a/tests/data/test1905 +++ b/tests/data/test1905 @@ -5,6 +5,7 @@ HTTP cookies FLUSH +libtest @@ -30,9 +31,6 @@ http CURLOPT_COOKIELIST set to "FLUSH" of a shared cookie object - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER %LOGDIR/cookies%TESTNUMBER diff --git a/tests/data/test1906 b/tests/data/test1906 index cd2bfd93d7a7..dc30d80ef0ae 100644 --- a/tests/data/test1906 +++ b/tests/data/test1906 @@ -4,6 +4,7 @@ CURLOPT_CURLU CURLOPT_PORT +libtest @@ -27,9 +28,6 @@ http CURLOPT_CURLU and CURLOPT_PORT - -lib%TESTNUMBER - # The tool does two requests, the first sets CURLOPT_PORT to 1 # the second resets the port again and expects that request to work. diff --git a/tests/data/test1907 b/tests/data/test1907 index aa6fb205abe4..493fda6dae23 100644 --- a/tests/data/test1907 +++ b/tests/data/test1907 @@ -3,6 +3,7 @@ CURLINFO_EFFECTIVE_URL +libtest @@ -27,9 +28,6 @@ http CURLINFO_EFFECTIVE_URL with non-scheme URL - -lib%TESTNUMBER - %HOSTIP:%HTTPPORT/hello/../%TESTNUMBER diff --git a/tests/data/test1908 b/tests/data/test1908 index bd564ae20089..fc2c38ad3f10 100644 --- a/tests/data/test1908 +++ b/tests/data/test1908 @@ -3,6 +3,7 @@ CURLINFO_EFFECTIVE_URL +libtest @@ -47,9 +48,6 @@ alt-svc cache save after resetting the handle # make Debug-curl accept Alt-Svc over plain HTTP CURL_ALTSVC_HTTP="yeah" - -lib%TESTNUMBER - %HOSTIP:%HTTPPORT/%TESTNUMBER %LOGDIR/altsvc-%TESTNUMBER diff --git a/tests/data/test1910 b/tests/data/test1910 index afd40bac1df4..35dace54344a 100644 --- a/tests/data/test1910 +++ b/tests/data/test1910 @@ -5,6 +5,7 @@ HTTP HTTP Basic CURLOPT_FOLLOWLOCATION +libtest @@ -39,9 +40,6 @@ http HTTP credentials with newline and redirect - -lib%TESTNUMBER - %HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1911 b/tests/data/test1911 index 8ba5c1ab3f81..3fb994dc11ba 100644 --- a/tests/data/test1911 +++ b/tests/data/test1911 @@ -3,6 +3,7 @@ curl_easy_option +libtest @@ -15,9 +16,6 @@ curl_easy_option verify that curl_easy_setopt() rejects too long string inputs - -lib%TESTNUMBER - diff --git a/tests/data/test1912 b/tests/data/test1912 index 86775e1ee3ac..39ee057a05ac 100644 --- a/tests/data/test1912 +++ b/tests/data/test1912 @@ -4,6 +4,7 @@ curl_easy_option typecheck +libtest @@ -16,9 +17,6 @@ typecheck Cross validate that gcc-typecheck macros match the option types. - -lib%TESTNUMBER - diff --git a/tests/data/test1913 b/tests/data/test1913 index c3624de8a580..8bc4612bd128 100644 --- a/tests/data/test1913 +++ b/tests/data/test1913 @@ -4,6 +4,7 @@ FTP CURLOPT_NOBODY +libtest @@ -24,9 +25,6 @@ ftp FTP with NOBODY set, getting a missing file - -lib%TESTNUMBER - ftp://%HOSTIP:%FTPPORT/not-there/%TESTNUMBER diff --git a/tests/data/test1914 b/tests/data/test1914 index 92dc657dc32a..54b569d78eb4 100644 --- a/tests/data/test1914 +++ b/tests/data/test1914 @@ -5,6 +5,7 @@ FTP CURLOPT_NOBODY CURLOPT_FILETIME +libtest diff --git a/tests/data/test1915 b/tests/data/test1915 index 8f9089b2946d..09880d1cc269 100644 --- a/tests/data/test1915 +++ b/tests/data/test1915 @@ -5,6 +5,7 @@ HTTP HSTS CURLOPT_HSTSREADFUNCTION +libtest @@ -22,9 +23,6 @@ http HSTS read/write callbacks - -lib%TESTNUMBER - http://%HOSTIP:%NOLISTENPORT/not-there/%TESTNUMBER diff --git a/tests/data/test1916 b/tests/data/test1916 index 0a56251d2478..93f38369d048 100644 --- a/tests/data/test1916 +++ b/tests/data/test1916 @@ -4,6 +4,7 @@ MQTT MQTT PUBLISH +libtest @@ -25,9 +26,6 @@ mqtt MQTT PUBLISH with no POSTFIELDSIZE set - -lib%TESTNUMBER - "mqtt://%HOSTIP:%MQTTPORT/%20" diff --git a/tests/data/test1917 b/tests/data/test1917 index d1a79d2d9b1a..a7417b7125cc 100644 --- a/tests/data/test1917 +++ b/tests/data/test1917 @@ -4,6 +4,7 @@ MQTT MQTT PUBLISH +libtest diff --git a/tests/data/test1918 b/tests/data/test1918 index fda890822432..bb238531b526 100644 --- a/tests/data/test1918 +++ b/tests/data/test1918 @@ -5,6 +5,7 @@ curl_easy_option curl_easy_option_by_name curl_easy_option_by_id +libtest @@ -17,9 +18,6 @@ curl_easy_option_by_id curl_easy_option_by_name() and curl_easy_option_by_id() - -lib%TESTNUMBER - diff --git a/tests/data/test1919 b/tests/data/test1919 index c18962e73079..86b014820de6 100644 --- a/tests/data/test1919 +++ b/tests/data/test1919 @@ -3,6 +3,7 @@ connection reuse +libtest @@ -26,9 +27,6 @@ http set CURLOPT_XOAUTH2_BEARER and do connection reuse - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1920 b/tests/data/test1920 index 3471025b416f..cd1cc7adf588 100644 --- a/tests/data/test1920 +++ b/tests/data/test1920 @@ -3,6 +3,7 @@ cookies +libtest @@ -35,9 +36,6 @@ http cookies - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/ %LOGDIR/cookie%TESTNUMBER diff --git a/tests/data/test1921 b/tests/data/test1921 index 15a3fe1ae7b9..4333b89dc89e 100644 --- a/tests/data/test1921 +++ b/tests/data/test1921 @@ -3,6 +3,7 @@ urlapi +libtest @@ -12,9 +13,6 @@ urlapi Set a URL without scheme, then redirect with default scheme - -lib%TESTNUMBER - - diff --git a/tests/data/test1922 b/tests/data/test1922 index 9c35ccff6f18..d6b7d020b632 100644 --- a/tests/data/test1922 +++ b/tests/data/test1922 @@ -6,6 +6,7 @@ HTTP HTTP proxy HSTS curl_easy_duphandle +libtest @@ -51,9 +52,6 @@ CURL_HSTS_HTTP=yes curl_easy_duphandle copies HSTS cache, auto upgrading HTTP to HTTPS. - -lib%TESTNUMBER - - %HOSTIP %HTTPPORT %PROXYPORT diff --git a/tests/data/test1933 b/tests/data/test1933 index cda12d19a121..37821ce433fb 100644 --- a/tests/data/test1933 +++ b/tests/data/test1933 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 with one provider and auth cred via URL - -lib%TESTNUMBER - http://xxx:yyy@127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1934 b/tests/data/test1934 index 9c52a0d0bddd..89f6affd3035 100644 --- a/tests/data/test1934 +++ b/tests/data/test1934 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 with two providers - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1935 b/tests/data/test1935 index f92c7e8ea2d3..1542b0299261 100644 --- a/tests/data/test1935 +++ b/tests/data/test1935 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 with two providers and region - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1936 b/tests/data/test1936 index e43eb0be6916..d0dc0b493e39 100644 --- a/tests/data/test1936 +++ b/tests/data/test1936 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 with two providers, region and service - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1937 b/tests/data/test1937 index 2043053e59db..cb34c897633e 100644 --- a/tests/data/test1937 +++ b/tests/data/test1937 @@ -6,6 +6,7 @@ HTTP HTTP POST CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -46,9 +47,6 @@ aws HTTP POST with AWS_SIGV4 - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1938 b/tests/data/test1938 index a2ea20fc9662..21978f2a1ae3 100644 --- a/tests/data/test1938 +++ b/tests/data/test1938 @@ -6,6 +6,7 @@ HTTP HTTP POST CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -46,9 +47,6 @@ aws HTTP POST with AWS_SIGV4 for binary data - -lib%TESTNUMBER - http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1939 b/tests/data/test1939 index d94a9f7c5c89..ff945d270971 100644 --- a/tests/data/test1939 +++ b/tests/data/test1939 @@ -4,6 +4,7 @@ CONNECT curl_easy_cleanup +libtest @@ -41,9 +42,6 @@ http-proxy curl_easy_cleanup without curl_multi_remove_handle - in CONNECT - -lib%TESTNUMBER - https://%HOSTIP:%HTTPPORT/%TESTNUMBER http://%HOSTIP:%PROXYPORT diff --git a/tests/data/test1940 b/tests/data/test1940 index e8f7d44e9594..9544f372a4cc 100644 --- a/tests/data/test1940 +++ b/tests/data/test1940 @@ -3,6 +3,7 @@ curl_easy_header +libtest @@ -42,9 +43,6 @@ http curl_easy_header - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1941 b/tests/data/test1941 index 4532403bc9fd..5c2796df5553 100644 --- a/tests/data/test1941 +++ b/tests/data/test1941 @@ -4,6 +4,7 @@ curl_easy_header CONNECT +libtest diff --git a/tests/data/test1942 b/tests/data/test1942 index 2dd1968ef6c8..d125c69f1bbb 100644 --- a/tests/data/test1942 +++ b/tests/data/test1942 @@ -4,6 +4,7 @@ curl_easy_header CONNECT +libtest diff --git a/tests/data/test1943 b/tests/data/test1943 index 8cae4eb01fdf..a6e01c6e084a 100644 --- a/tests/data/test1943 +++ b/tests/data/test1943 @@ -4,6 +4,7 @@ curl_easy_header CONNECT +libtest diff --git a/tests/data/test1944 b/tests/data/test1944 index 1b0dd469c156..6d73cc9a0787 100644 --- a/tests/data/test1944 +++ b/tests/data/test1944 @@ -3,6 +3,7 @@ curl_easy_header +libtest diff --git a/tests/data/test1945 b/tests/data/test1945 index f085007c6b5d..4199e4d44d2e 100644 --- a/tests/data/test1945 +++ b/tests/data/test1945 @@ -4,6 +4,7 @@ curl_easy_header CONNECT +libtest @@ -18,6 +19,7 @@ Content-Length: 0 Set-Cookie: onecookie=data; Set-Cookie: secondcookie=2data; Set-Cookie: cookie3=data3; +set-cookie: lowercase=data4; Location: /%TESTNUMBER0002 @@ -44,9 +46,6 @@ http-proxy curl_easy_nextheader with server + CONNECT - -lib%TESTNUMBER - http://hello:%HTTPPORT/%TESTNUMBER %HOSTIP:%PROXYPORT @@ -68,9 +67,10 @@ Proxy-Connection: Keep-Alive Server == test with trailing space (1/2) Content-Type == text/html (0/1) Content-Length == 0 (0/1) - Set-Cookie == onecookie=data; (0/3) - Set-Cookie == secondcookie=2data; (1/3) - Set-Cookie == cookie3=data3; (2/3) + Set-Cookie == onecookie=data; (0/4) + Set-Cookie == secondcookie=2data; (1/4) + Set-Cookie == cookie3=data3; (2/4) + set-cookie == lowercase=data4; (3/4) Location == /19450002 (0/1) diff --git a/tests/data/test1946 b/tests/data/test1946 index 571fbc7fc3ab..5931522459de 100644 --- a/tests/data/test1946 +++ b/tests/data/test1946 @@ -3,6 +3,7 @@ curl_easy_header +libtest diff --git a/tests/data/test1947 b/tests/data/test1947 index 9519809c586d..1c834417359d 100644 --- a/tests/data/test1947 +++ b/tests/data/test1947 @@ -3,6 +3,7 @@ curl_easy_nextheader +libtest @@ -53,9 +54,6 @@ http curl_easy_nextheader on second request after first did redirects - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER http://%HOSTIP:%HTTPPORT/%TESTNUMBER0003 diff --git a/tests/data/test1948 b/tests/data/test1948 index ac926c0057f6..badc295d25e6 100644 --- a/tests/data/test1948 +++ b/tests/data/test1948 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP PUT +libtest @@ -43,9 +44,6 @@ http CURLOPT_POST after CURLOPT_UPLOAD reusing handle - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test1955 b/tests/data/test1955 index 82349c25395b..2bbede0ea180 100644 --- a/tests/data/test1955 +++ b/tests/data/test1955 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 without X-Xxx-Content-Sha256 - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1956 b/tests/data/test1956 index f3ad8b859a14..df3dfa8b54c3 100644 --- a/tests/data/test1956 +++ b/tests/data/test1956 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 with X-Xxx-Content-Sha256 of empty payload - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1957 b/tests/data/test1957 index 77b48b238897..22272501443b 100644 --- a/tests/data/test1957 +++ b/tests/data/test1957 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 with X-Xxx-Content-Sha256 of arbitrary payload - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1958 b/tests/data/test1958 index 672adfec1f77..b40086caab79 100644 --- a/tests/data/test1958 +++ b/tests/data/test1958 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +libtest @@ -44,9 +45,6 @@ aws HTTP AWS_SIGV4 with X-Xxx-Content-Sha256 with whitespace - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1959 b/tests/data/test1959 index 27a778c305ac..24798a2034b7 100644 --- a/tests/data/test1959 +++ b/tests/data/test1959 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 with long value for X-Xxx-Content-Sha256 - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1960 b/tests/data/test1960 index d8d8fd6671f7..f14f4ab375ff 100644 --- a/tests/data/test1960 +++ b/tests/data/test1960 @@ -5,6 +5,7 @@ HTTP CURLOPT_SOCKOPTFUNCTION CURL_SOCKOPT_ALREADY_CONNECTED +libtest @@ -32,9 +33,6 @@ http application hands over already connected socket - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/file %HOSTIP %HTTPPORT diff --git a/tests/data/test1964 b/tests/data/test1964 index fc636308fbb3..77fbaa27a703 100644 --- a/tests/data/test1964 +++ b/tests/data/test1964 @@ -4,6 +4,7 @@ HTTP CURLOPT_AWS_SIGV4 +libtest @@ -43,9 +44,6 @@ aws HTTP AWS_SIGV4 with X-Xxx-Date header set manually - -lib%TESTNUMBER - http://xxx:yyy@127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1965 b/tests/data/test1965 index 195ce9c5cf9a..cffa490b1c03 100644 --- a/tests/data/test1965 +++ b/tests/data/test1965 @@ -4,6 +4,7 @@ urlapi curl_url_set +libtest @@ -11,9 +12,6 @@ curl_url_set curl_url_set() different schemes - -lib%TESTNUMBER - # Verify data after the test has been "shot" diff --git a/tests/data/test1967 b/tests/data/test1967 index c012a70ba63c..fb3f3e33793d 100644 --- a/tests/data/test1967 +++ b/tests/data/test1967 @@ -4,6 +4,7 @@ HTTP urlapi +libtest @@ -12,9 +13,6 @@ urlapi curl_url_set() a URL without guessing a scheme - -lib%TESTNUMBER - http://%HOSTIP:%NOLISTENPORT/not-there/%TESTNUMBER diff --git a/tests/data/test1970 b/tests/data/test1970 index 8dae28c75339..c3c33e09fed0 100644 --- a/tests/data/test1970 +++ b/tests/data/test1970 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 for AWS S3: UPLOAD with INFILESIZE=0 - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1971 b/tests/data/test1971 index c303018c72df..bfbec1ac5476 100644 --- a/tests/data/test1971 +++ b/tests/data/test1971 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -38,9 +39,6 @@ aws HTTP AWS_SIGV4 for AWS S3: UPLOAD with unknown INFILESIZE - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1972 b/tests/data/test1972 index d128dccf452c..91cc8f1df741 100644 --- a/tests/data/test1972 +++ b/tests/data/test1972 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -48,9 +49,6 @@ CURL_ENTROPY=12345678 HTTP AWS_SIGV4 for AWS S3: MIMEPOST - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1973 b/tests/data/test1973 index c2527df686f4..3262b8b9617a 100644 --- a/tests/data/test1973 +++ b/tests/data/test1973 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 for AWS S3: POSTFIELDS - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1974 b/tests/data/test1974 index 20a10a87056d..7d6e977ee6ca 100644 --- a/tests/data/test1974 +++ b/tests/data/test1974 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -45,9 +46,6 @@ aws HTTP AWS_SIGV4 for AWS S3: GET - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1975 b/tests/data/test1975 index e5d6272eda0b..151ad6bbe95d 100644 --- a/tests/data/test1975 +++ b/tests/data/test1975 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -38,9 +39,6 @@ aws HTTP AWS_SIGV4 for AWS S3: UPLOAD with given x-amz-content-sha256 - -lib%TESTNUMBER - http://exam.ple.com:9000/aws_sigv4/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1977 b/tests/data/test1977 index bcd6ed31d445..5432aad4c1f7 100644 --- a/tests/data/test1977 +++ b/tests/data/test1977 @@ -4,6 +4,7 @@ CURLOPT_CURLU CURLINFO_EFFECTIVE_URL +libtest @@ -27,9 +28,6 @@ http CURLOPT_CURLU and CURLINFO_EFFECTIVE_URL - -lib%TESTNUMBER - # The test does three transfers to check how CURLINFO_EFFECTIVE_URL is reported # when CURLOPT_CURLU changes between transfers. (Bug #15984) diff --git a/tests/data/test1978 b/tests/data/test1978 index 4113900fca53..9a5f1bfaa817 100644 --- a/tests/data/test1978 +++ b/tests/data/test1978 @@ -5,6 +5,7 @@ HTTP CURLOPT_AWS_SIGV4 aws-sigv4 +libtest @@ -33,9 +34,6 @@ aws HTTP AWS_SIGV4 canonical request duplicate header test - -lib%TESTNUMBER - http://xxx:yyy@127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test1985 b/tests/data/test1985 index 78be4df0ea51..ba382777d316 100644 --- a/tests/data/test1985 +++ b/tests/data/test1985 @@ -3,6 +3,7 @@ Digest +libtest @@ -41,9 +42,6 @@ Digest response when not asked, then use Digest crypto digest - -lib%TESTNUMBER - http://firsthost:%HTTPPORT/%TESTNUMBER http://secondhost:%HTTPPORT/%TESTNUMBER0001 %HTTPPORT %HOSTIP diff --git a/tests/data/test1989 b/tests/data/test1989 new file mode 100644 index 000000000000..721f3655be28 --- /dev/null +++ b/tests/data/test1989 @@ -0,0 +1,15 @@ + + + + +libtest +urlapi + + + + + +curl_url_dup preserves guessed_scheme + + + diff --git a/tests/data/test2007 b/tests/data/test2007 index db82ec3105c0..9e5db4a8d24c 100644 --- a/tests/data/test2007 +++ b/tests/data/test2007 @@ -20,8 +20,13 @@ sftp SFTP quote rmdir command with trailing junk ---hostpubmd5 %SSHSRVMD5 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt -Q "mkdir /tmp/test trailing_garbage" +--hostpubsha256 %SSHSRVSHA256 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt -Q "mkdir /tmp/test trailing_garbage" + +# Needed for MSYS2 to not treat the base64 hash argument as a POSIX path list +# that has to be converted to Windows paths +MSYS2_ARG_CONV_EXCL=* + diff --git a/tests/data/test2023 b/tests/data/test2023 index a57aaeed8ca8..c759a2433094 100644 --- a/tests/data/test2023 +++ b/tests/data/test2023 @@ -5,6 +5,7 @@ HTTP HTTP GET HTTP Basic auth +libtest # Server-side @@ -106,9 +107,6 @@ Finally, this is the real page! http - -lib2023 - HTTP authorization retry (Basic) diff --git a/tests/data/test2024 b/tests/data/test2024 index af60388ba7dc..dd0c24b287e3 100644 --- a/tests/data/test2024 +++ b/tests/data/test2024 @@ -6,6 +6,7 @@ HTTP HTTP GET HTTP Basic auth HTTP Digest auth +libtest # Server-side diff --git a/tests/data/test2025 b/tests/data/test2025 index 3d1db3f4e519..d4359e414237 100644 --- a/tests/data/test2025 +++ b/tests/data/test2025 @@ -7,6 +7,7 @@ HTTP GET HTTP Basic auth HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2026 b/tests/data/test2026 index 9fc1c6bb9625..5bdbf877b826 100644 --- a/tests/data/test2026 +++ b/tests/data/test2026 @@ -6,6 +6,7 @@ HTTP HTTP GET HTTP Basic auth HTTP Digest auth +libtest # Server-side diff --git a/tests/data/test2027 b/tests/data/test2027 index dfcefa302a16..0d9b1c826ce6 100644 --- a/tests/data/test2027 +++ b/tests/data/test2027 @@ -5,6 +5,7 @@ HTTP HTTP GET HTTP Digest auth +libtest # Server-side diff --git a/tests/data/test2028 b/tests/data/test2028 index f61d2a642ca9..aabc5fc5f01a 100644 --- a/tests/data/test2028 +++ b/tests/data/test2028 @@ -7,6 +7,7 @@ HTTP GET HTTP Digest auth HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2029 b/tests/data/test2029 index 361aa7cb6b84..beebdcbe314a 100644 --- a/tests/data/test2029 +++ b/tests/data/test2029 @@ -7,6 +7,7 @@ HTTP GET HTTP Basic auth HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2030 b/tests/data/test2030 index 5027fd45da4b..bb43567628c5 100644 --- a/tests/data/test2030 +++ b/tests/data/test2030 @@ -7,6 +7,7 @@ HTTP GET HTTP Digest auth HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2031 b/tests/data/test2031 index 09e1f7734dfc..deae884120e9 100644 --- a/tests/data/test2031 +++ b/tests/data/test2031 @@ -6,6 +6,7 @@ HTTP HTTP GET HTTP NTLM auth NTLM +libtest # Server-side diff --git a/tests/data/test2032 b/tests/data/test2032 index 2bddcb5fc0c7..7a2e753ebbcc 100644 --- a/tests/data/test2032 +++ b/tests/data/test2032 @@ -9,6 +9,7 @@ HTTP NTLM auth NTLM flaky timing-dependent +libtest # Server-side @@ -75,9 +76,6 @@ SSL http - -lib2032 - NTLM connection mapping diff --git a/tests/data/test2058 b/tests/data/test2058 index 29c5c6456b5f..d4be197e1a3d 100644 --- a/tests/data/test2058 +++ b/tests/data/test2058 @@ -6,8 +6,6 @@ HTTP HTTP PUT HTTP proxy HTTP Digest auth -Resume -Content-Range CUSTOMREQUEST @@ -70,10 +68,10 @@ proxy digest -HTTP Digest with PUT, resumed upload, modified method and SHA-256 +HTTP Digest upload with a modified method and SHA-256 -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -X GET test @@ -85,7 +83,6 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -94,13 +91,12 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER", response="fbed69f9f3fd304c8f1acb1a43eb32688b933c0e28055c16b926cbcec070aeed", algorithm=SHA-256 -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test2059 b/tests/data/test2059 index 71dc55f05963..f7a1af4390e0 100644 --- a/tests/data/test2059 +++ b/tests/data/test2059 @@ -6,8 +6,6 @@ HTTP HTTP PUT HTTP proxy HTTP Digest auth -Resume -Content-Range CUSTOMREQUEST @@ -70,10 +68,10 @@ proxy digest -HTTP Digest PUT, resumed upload, modified method, SHA-256 and userhash +HTTP Digest upload with a modified method, SHA-256 and userhash -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -X GET test @@ -85,7 +83,6 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -94,13 +91,12 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="fddc3bc7b753b73ab0848fd83cb20cbbca971258eb8d20c941dd5e0b010d66be", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER", response="22d200df1fd02a9d3a7269ef5bbb5bf8f16f184a74907df9b64a3755489c0b42", algorithm=SHA-256, userhash=true -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test2060 b/tests/data/test2060 index a623cd354b36..0a49dc1eca7f 100644 --- a/tests/data/test2060 +++ b/tests/data/test2060 @@ -6,8 +6,6 @@ HTTP HTTP PUT HTTP proxy HTTP Digest auth -Resume -Content-Range CUSTOMREQUEST @@ -70,10 +68,10 @@ proxy sha512-256 -HTTP Digest with PUT, resumed upload, SHA-512-256, userhash +HTTP Digest upload with a modified method and SHA-512-256 -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -C 2 -X GET +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -u auser:apasswd --digest -T %LOGDIR/%TESTNUMBER -x http://%HOSTIP:%HTTPPORT -X GET test @@ -85,7 +83,6 @@ test GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive @@ -94,13 +91,12 @@ Content-Length: 0 GET http://%HOSTIP:%HTTPPORT/%TESTNUMBER HTTP/1.1 Host: %HOSTIP:%HTTPPORT Authorization: Digest username="auser", realm="testrealm", nonce="1053604144", uri="/%TESTNUMBER", response="691867f4a06c79fd0a175c1857e3df7015f6fff3ce8676497d2f1f805b5a8eca", algorithm=SHA-512-256 -Content-Range: bytes 2-4/5 User-Agent: curl/%VERSION Accept: */* Proxy-Connection: Keep-Alive -Content-Length: 3 +Content-Length: 5 -st +test diff --git a/tests/data/test2072 b/tests/data/test2072 index efad82012a8b..9b1599ed0f81 100644 --- a/tests/data/test2072 +++ b/tests/data/test2072 @@ -21,15 +21,15 @@ moo file + +!win32 + file:// with Unix path for the case of extra slashes file:////%FILE_PWD/%LOGDIR/test%TESTNUMBER.txt - -%PERL -e "print 'Test requires a Unix system' if($^O eq 'MSWin32' || $^O eq 'cygwin' || $^O eq 'dos' || $^O eq 'msys');" - foo bar diff --git a/tests/data/test2082 b/tests/data/test2082 index cf0a28428b68..789f3e93afe8 100644 --- a/tests/data/test2082 +++ b/tests/data/test2082 @@ -3,6 +3,7 @@ HTTP +libtest @@ -27,9 +28,6 @@ http Pre-request callback for HTTP - -lib2082 - %HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2083 b/tests/data/test2083 index 2bd93f1ac743..9d2c2a067a9d 100644 --- a/tests/data/test2083 +++ b/tests/data/test2083 @@ -3,6 +3,7 @@ FTP +libtest diff --git a/tests/data/test2084 b/tests/data/test2084 index 4ac5fbee75d1..ffd19d237681 100644 --- a/tests/data/test2084 +++ b/tests/data/test2084 @@ -3,6 +3,7 @@ HTTP +libtest diff --git a/tests/data/test2085 b/tests/data/test2085 index 5f26b50cfa92..c74616ee38c5 100644 --- a/tests/data/test2085 +++ b/tests/data/test2085 @@ -4,6 +4,7 @@ HTTP followlocation +libtest diff --git a/tests/data/test2086 b/tests/data/test2086 index 5336ddaba628..9cdac32bdd58 100644 --- a/tests/data/test2086 +++ b/tests/data/test2086 @@ -4,6 +4,7 @@ HTTP IPv6 +libtest diff --git a/tests/data/test2095 b/tests/data/test2095 new file mode 100644 index 000000000000..cb1736fcb3d5 --- /dev/null +++ b/tests/data/test2095 @@ -0,0 +1,56 @@ + + + + +HTTPS +HTTP GET +Client Auth +libtest + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 7 + +MooMoo + + + +# Client-side + + +SSL +!Schannel +!GnuTLS +local-http + + +https-mtls + + +HTTPS with in-memory client auth using blob options + + +https://localhost:%HTTPS-MTLSPORT/%TESTNUMBER %CERTDIR/certs/test-ca.crt %CERTDIR/certs/test-client-cert.crt %CERTDIR/certs/test-client-cert.key + + +%LIBTESTS lib%TESTNUMBER check + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: localhost:%HTTPS-MTLSPORT +User-Agent: CURLOPT_SSLCERT_BLOB +Accept: */* + + + + diff --git a/tests/data/test2106 b/tests/data/test2106 index 40d2dc8b62b6..6df6ab7b3b9f 100644 --- a/tests/data/test2106 +++ b/tests/data/test2106 @@ -30,7 +30,7 @@ chunky-trailer: he%hex[%00]hex%llo%CR http -HTTP chunked response with a nul byte in the trailer +HTTP chunked response with a NUL byte in the trailer http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2107 b/tests/data/test2107 index 8c4404410997..0a177b893982 100644 --- a/tests/data/test2107 +++ b/tests/data/test2107 @@ -33,7 +33,7 @@ http-proxy proxy -HTTP CONNECT response with a nul byte in a header +HTTP CONNECT response with a NUL byte in a header http://%HOSTIP:%HTTPPORT/%TESTNUMBER -p -x http://%HOSTIP:%PROXYPORT diff --git a/tests/data/test2108 b/tests/data/test2108 index 481d09357e73..4cac52eede21 100644 --- a/tests/data/test2108 +++ b/tests/data/test2108 @@ -18,7 +18,7 @@ REPLY PASS 230 logged\x00 in ftp -FTP rejects a nul byte in a server response line +FTP rejects a NUL byte in a server response line ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test2109 b/tests/data/test2109 index 323ea144c4d2..1c7cc70351b7 100644 --- a/tests/data/test2109 +++ b/tests/data/test2109 @@ -38,7 +38,7 @@ mqtt://%HOSTIP:%MQTTPORT/aa%00bb s/^(.* 00044d5154540402003c000c6375726c).*/$1/ -# the topic is decoded and the embedded nul rejected before SUBSCRIBE is sent +# the topic is decoded and the embedded NUL byte rejected before SUBSCRIBE is sent client CONNECT 18 00044d5154540402003c000c6375726c server CONNACK 2 20020000 diff --git a/tests/data/test2118 b/tests/data/test2118 index a3a18e750198..9b2eff8846c0 100644 --- a/tests/data/test2118 +++ b/tests/data/test2118 @@ -5,6 +5,7 @@ HTTPS HTTP GET PEM certificate +libtest @@ -23,9 +24,6 @@ local-http https test-localhost.nn.pem - -lib%TESTNUMBER - VERIFYHOST with VERIFYPEER disabled rejects a mismatching certificate diff --git a/tests/data/test2173 b/tests/data/test2173 new file mode 100644 index 000000000000..c264c9387ac8 --- /dev/null +++ b/tests/data/test2173 @@ -0,0 +1,68 @@ + + + + +HTTP +HTTP GET +HTTP CONNECT +HTTP Basic auth +proxytunnel + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake swsclose +Content-Type: text/html +Funny-head: yesyes +Content-Length: 9 + +contents + + +HTTP/1.1 401 Tricking You +WWW-Authenticate: Basic + + + +HTTP/1.1 401 Tricking You +WWW-Authenticate: Basic + + + + +# Client-side + + +http +http-proxy + + +HTTP over proxy-tunnel with site wrong 401 response + + +http://test.%TESTNUMBER:%HTTPPORT/we/want/that/page/%TESTNUMBER -p -x %HOSTIP:%PROXYPORT --user 'iam:my:;self' --anyauth + + +proxy + + + +# Verify data after the test has been "shot" + + +CONNECT test.%TESTNUMBER:%HTTPPORT HTTP/1.1 +Host: test.%TESTNUMBER:%HTTPPORT +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + + + + + +7 + + + diff --git a/tests/data/test2209 b/tests/data/test2209 new file mode 100644 index 000000000000..b4842c0c4651 --- /dev/null +++ b/tests/data/test2209 @@ -0,0 +1,46 @@ + + + + +MQTT +MQTT PUBLISH + + + + + + + + + + +mqtt +Debug + + +mqtt + + +MQTT PUBLISH with partial writes + + +CURL_DBG_SOCK_WPARTIAL=50 + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER -d something --max-time 10 + + + + +# Strip the random part of the client ID. + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client PUBLISH f 000432323039736f6d657468696e67 +client DISCONNECT 0 e000 + + + diff --git a/tests/data/test2210 b/tests/data/test2210 new file mode 100644 index 000000000000..9f3d309aeff6 --- /dev/null +++ b/tests/data/test2210 @@ -0,0 +1,49 @@ + + + + +MQTT +MQTT SUBSCRIBE + + + + + +hello + + + + + +mqtt +Debug + + +mqtt + + +MQTT SUBSCRIBE with partial writes + + +CURL_DBG_SOCK_WPARTIAL=50 + + +mqtt://%HOSTIP:%MQTTPORT/%TESTNUMBER --max-time 10 + + + + +# Strip the random part of the client ID. + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client SUBSCRIBE 9 000100043232313000 +server SUBACK 3 9003000100 +server PUBLISH c 300c00043232313068656c6c6f0a +server DISCONNECT 0 e000 + + + diff --git a/tests/data/test2211 b/tests/data/test2211 new file mode 100644 index 000000000000..a06d673e0677 --- /dev/null +++ b/tests/data/test2211 @@ -0,0 +1,48 @@ + + + + +MQTT +MQTT PUBLISH +MQTTS + + + + + + + + + + +mqtts +Debug + + +mqtts + + +MQTTS PUBLISH with partial writes + + +# Split MQTT packets, including DISCONNECT, without fragmenting TLS handshakes. +CURL_SMALLSENDS=1 + + +mqtts://%HOSTIP:%MQTTSPORT/%TESTNUMBER -d something --insecure --max-time 10 + + + + +# Strip the random part of the client ID. + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client PUBLISH f 000432323131736f6d657468696e67 +client DISCONNECT 0 e000 + + + diff --git a/tests/data/test2212 b/tests/data/test2212 new file mode 100644 index 000000000000..907927780b19 --- /dev/null +++ b/tests/data/test2212 @@ -0,0 +1,51 @@ + + + + +MQTT +MQTT SUBSCRIBE +MQTTS + + + + + +hello + + + + + +mqtts +Debug + + +mqtts + + +MQTTS SUBSCRIBE with partial writes + + +# Split MQTT packets without fragmenting TLS handshakes. +CURL_SMALLSENDS=1 + + +mqtts://%HOSTIP:%MQTTSPORT/%TESTNUMBER --insecure --max-time 10 + + + + +# Strip the random part of the client ID. + +s/^(.* 00044d5154540402003c000c6375726c).*/$1/ + + +client CONNECT 18 00044d5154540402003c000c6375726c +server CONNACK 2 20020000 +client SUBSCRIBE 9 000100043232313200 +server SUBACK 3 9003000100 +server PUBLISH c 300c00043232313268656c6c6f0a +server DISCONNECT 0 e000 + + + diff --git a/tests/data/test1850 b/tests/data/test2270 similarity index 57% rename from tests/data/test1850 rename to tests/data/test2270 index 6a3e4d409b0a..a5628d91e42a 100644 --- a/tests/data/test1850 +++ b/tests/data/test2270 @@ -2,38 +2,43 @@ -SMB +HTTP +HTTP POST +HTTP MIME +Resume +libtest # Server-side -Basic SMB test complete +HTTP/1.1 200 OK swsclose + # Client-side - -smb - -smb +Mime +form-api + +http + -Two SMB requests in the same command line +reject HTTP form and MIME upload resume --u 'curltest:curltest' smb://%HOSTIP:%SMBPORT/TESTS/%TESTNUMBER smb://%HOSTIP:%SMBPORT/TESTS/%TESTNUMBER +http://%HOSTIP:%HTTPPORT/%TESTNUMBER # Verify data after the test has been "shot" - -Basic SMB test complete -Basic SMB test complete - + + + diff --git a/tests/data/test2271 b/tests/data/test2271 new file mode 100644 index 000000000000..f24b6c532a0c --- /dev/null +++ b/tests/data/test2271 @@ -0,0 +1,38 @@ + + + + +HTTP +HTTP POST +Resume +error detection + + + +# Server-side + + + +# Client-side + + +http + + +reject automatic HTTP data upload resume + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -d foobar -C - + + + +# Verify data after the test has been "shot" + + +43 + + +curl: (43) HTTP upload cannot be resumed + + + diff --git a/tests/data/test2272 b/tests/data/test2272 new file mode 100644 index 000000000000..043c4676cc48 --- /dev/null +++ b/tests/data/test2272 @@ -0,0 +1,42 @@ + + + + +HTTP +HTTP POST +HTTP MIME +Resume +error detection + + + +# Server-side + + + +# Client-side + + +Mime + + +http + + +reject automatic HTTP form upload resume + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -F name=value -C - + + + +# Verify data after the test has been "shot" + + +43 + + +curl: (43) HTTP upload cannot be resumed + + + diff --git a/tests/data/test2301 b/tests/data/test2301 index 4767063105ca..0a31d54e1df0 100644 --- a/tests/data/test2301 +++ b/tests/data/test2301 @@ -3,6 +3,7 @@ WebSockets +libtest @@ -40,9 +41,6 @@ http WebSockets via callback (raw mode) + curl_ws_send() - -lib%TESTNUMBER - ws://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2302 b/tests/data/test2302 index 396ba16a7ecc..9a158680c08a 100644 --- a/tests/data/test2302 +++ b/tests/data/test2302 @@ -3,6 +3,7 @@ WebSockets +libtest @@ -40,9 +41,6 @@ http WebSockets via callback (frame mode) + curl_ws_send() - -lib%TESTNUMBER - ws://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2303 b/tests/data/test2303 index 884123dfba7c..6c3cae335635 100644 --- a/tests/data/test2303 +++ b/tests/data/test2303 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2304 b/tests/data/test2304 index aab6dc822b48..7ba4ba5ebfc0 100644 --- a/tests/data/test2304 +++ b/tests/data/test2304 @@ -3,6 +3,7 @@ WebSockets +libtest @@ -39,9 +40,6 @@ http WebSockets curl_ws_recv() with closed connection - -lib%TESTNUMBER - ws://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2306 b/tests/data/test2306 index eec01e97106d..c46b0346f47e 100644 --- a/tests/data/test2306 +++ b/tests/data/test2306 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # This reproduces the #11101 issue, when the second response comes back @@ -39,9 +40,6 @@ Date: Wed, 10 May 2023 14:58:08 GMT http # tool to run - -lib%TESTNUMBER - HTTP GET reused handle with first header folded diff --git a/tests/data/test2308 b/tests/data/test2308 index a3770ca5a8ed..bc015325c0cd 100644 --- a/tests/data/test2308 +++ b/tests/data/test2308 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # This reproduces the #13669 issue @@ -29,9 +30,6 @@ Funny-head: yesyes http # tool to run - -lib%TESTNUMBER - verify return code when write callback returns error diff --git a/tests/data/test2309 b/tests/data/test2309 index 1549787b0da1..c0a27375aeab 100644 --- a/tests/data/test2309 +++ b/tests/data/test2309 @@ -4,6 +4,7 @@ netrc HTTP +libtest # Server-side @@ -37,9 +38,6 @@ proxy HTTP with .netrc using duped easy handle - -lib%TESTNUMBER - http://github.com %LOGDIR/netrc%TESTNUMBER http://%HOSTIP:%HTTPPORT/ diff --git a/tests/data/test2330 b/tests/data/test2330 new file mode 100644 index 000000000000..ea79efb3cf9e --- /dev/null +++ b/tests/data/test2330 @@ -0,0 +1,45 @@ + + + + +FTP +timeout +multi_socket +libtest + + + + + +DELAY USER 5 + + + + + +ftp + + +ftp + + +ftp + + +FTP server response timeout with the socket action API + + +cli_ev_download + +# The test's own deadline must not drive libcurl's timeout handling. + +-n 1 -t 1000 -m 3000 ftp://%HOSTIP:%FTPPORT/%TESTNUMBER + + + + + +28 + + + diff --git a/tests/data/test2380 b/tests/data/test2380 new file mode 100644 index 000000000000..d069695665ca --- /dev/null +++ b/tests/data/test2380 @@ -0,0 +1,21 @@ + + + + +libtest +curl_mprintf errors + + + + + +curl_mprintf errors on out of space + + +%LOGDIR/write2380 + + + + + + diff --git a/tests/data/test2397 b/tests/data/test2397 index 1a0996febb38..42e2acfa9a97 100644 --- a/tests/data/test2397 +++ b/tests/data/test2397 @@ -4,6 +4,7 @@ CURLINFO_REFERER CURLOPT_REFERER +libtest @@ -27,10 +28,6 @@ hej http - -lib%TESTNUMBER - - Get CURLINFO_REFERER set CURLOPT_REFERER diff --git a/tests/data/test2402 b/tests/data/test2402 index 269bc4b1006a..d1f89419b5ea 100644 --- a/tests/data/test2402 +++ b/tests/data/test2402 @@ -6,6 +6,7 @@ HTTP HTTP/2 multi verbose logs +libtest @@ -55,9 +56,6 @@ verbose-strings http/2 - -lib%TESTNUMBER - HTTP GET multiple files over HTTP/2 using HTTPS diff --git a/tests/data/test2404 b/tests/data/test2404 index 101290cf7529..010ac79bf359 100644 --- a/tests/data/test2404 +++ b/tests/data/test2404 @@ -6,6 +6,7 @@ HTTP HTTP/2 multi verbose logs +libtest @@ -55,9 +56,6 @@ verbose-strings http/2 - -lib%TESTNUMBER - HTTP/2 using STREAM_WEIGHTs diff --git a/tests/data/test2405 b/tests/data/test2405 index 6ccb6ba394d4..f394c48ed566 100644 --- a/tests/data/test2405 +++ b/tests/data/test2405 @@ -4,6 +4,7 @@ multi HTTP +libtest @@ -34,9 +35,6 @@ wakeup http - -lib%TESTNUMBER - checking curl_multi_waitfds functionality diff --git a/tests/data/test2407 b/tests/data/test2407 index b9f17bb22fe1..cd0e6fce606e 100644 --- a/tests/data/test2407 +++ b/tests/data/test2407 @@ -5,6 +5,7 @@ multi HTTP HTTP/2 +libtest diff --git a/tests/data/test2412 b/tests/data/test2412 index e0320e2ce4b6..b26811dddec4 100644 --- a/tests/data/test2412 +++ b/tests/data/test2412 @@ -3,6 +3,7 @@ multi +libtest @@ -33,9 +34,6 @@ wakeup http - -lib%TESTNUMBER - checking curl_multi_fdset on nothing to do diff --git a/tests/data/test2414 b/tests/data/test2414 index 197beffe8f9c..ce72d494af28 100644 --- a/tests/data/test2414 +++ b/tests/data/test2414 @@ -4,6 +4,7 @@ multi wakeup +libtest @@ -14,9 +15,6 @@ wakeup - -lib%TESTNUMBER - wakeup, perform and poll needs to exit diff --git a/tests/data/test2415 b/tests/data/test2415 new file mode 100644 index 000000000000..4619caeac268 --- /dev/null +++ b/tests/data/test2415 @@ -0,0 +1,36 @@ + + + + +HTTP +HTTP GET +globbing + + +# Server-side + +# Client-side + + +http + + +reference a named glob with a literal URL + + +"%HOSTIP:%HTTPPORT/test" -o "somewhere/#%LTfoo%GT" + + + +# Verify data after the test has been "shot" + + +curl: (43) no glob exists with this name in position 16: +somewhere/#%LTfoo%GT + ^ + + +43 + + + diff --git a/tests/data/test2418 b/tests/data/test2418 new file mode 100644 index 000000000000..7c9e15f5f7f1 --- /dev/null +++ b/tests/data/test2418 @@ -0,0 +1,53 @@ + + + + +HTTP +aws-sigv4 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +Debug +aws + + +aws-sigv4 for s3express does not encode the path again + + +"http://fake.fake.fake:8000/%TESTNUMBER/a%23b.txt" -u user:secret --aws-sigv4 "aws:amz:us-east-2:s3express" -H "x-amz-content-sha256: UNSIGNED-PAYLOAD" --connect-to fake.fake.fake:8000:%HOSTIP:%HTTPPORT + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER/a%23b.txt HTTP/1.1 +Host: fake.fake.fake:8000 +Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/s3express/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=5ba09dfca3e962e9dabf641f56c81293536c4832e39ccdb0080a30355ff38d64 +X-Amz-Date: 19700101T000000Z +User-Agent: curl/%VERSION +Accept: */* +x-amz-content-sha256: UNSIGNED-PAYLOAD + + + + diff --git a/tests/data/test2429 b/tests/data/test2429 new file mode 100644 index 000000000000..1565758888b9 --- /dev/null +++ b/tests/data/test2429 @@ -0,0 +1,47 @@ + + + + +netrc + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 +Content-Type: text/html + +-foo- + + + +# Client-side + + +http + + +use netrc file with only comments + + +# only a comment line + + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --netrc --netrc-file %LOGDIR/netrc + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test2453 b/tests/data/test2453 new file mode 100644 index 000000000000..e8cca5adeed0 --- /dev/null +++ b/tests/data/test2453 @@ -0,0 +1,50 @@ + + + + +HTTP +HTTP GET +CURLMINFO_XFERS_DONE +curl_multi_info_read +libtest + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 0 +Connection: close + + + + +# Client-side + + +http + + +http + + +CURLMINFO_XFERS_DONE tracks unread completion messages + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test2502 b/tests/data/test2502 index ad55c553f9bc..a41483585db1 100644 --- a/tests/data/test2502 +++ b/tests/data/test2502 @@ -6,6 +6,7 @@ HTTP HTTP/3 multi verbose logs +libtest @@ -54,9 +55,6 @@ verbose-strings http/3 - -lib%TESTNUMBER - HTTP GET multiple over HTTP/3 diff --git a/tests/data/test2504 b/tests/data/test2504 index 7bb4a81ed7e6..4af2cf0eff09 100644 --- a/tests/data/test2504 +++ b/tests/data/test2504 @@ -4,6 +4,7 @@ HTTP cookies +libtest @@ -25,9 +26,6 @@ file contents should appear once for each file http - -lib%TESTNUMBER - custom Host with cookie, handle reuse, no custom Host: diff --git a/tests/data/test2505 b/tests/data/test2505 index 8fac590b3706..111e10232ed0 100644 --- a/tests/data/test2505 +++ b/tests/data/test2505 @@ -5,6 +5,7 @@ HTTP referer autoreferer +libtest @@ -35,9 +36,6 @@ file contents should appear once for each file http - -lib%TESTNUMBER - verify CURLOPT_AUTOREFERER switched off diff --git a/tests/data/test2506 b/tests/data/test2506 index 9c65002496e8..6f051331cc9b 100644 --- a/tests/data/test2506 +++ b/tests/data/test2506 @@ -4,6 +4,7 @@ HTTP cookies +libtest @@ -32,9 +33,6 @@ http proxy - -lib%TESTNUMBER - netrc with redirect using proxy diff --git a/tests/data/test2582 b/tests/data/test2582 new file mode 100644 index 000000000000..614bb187d6ba --- /dev/null +++ b/tests/data/test2582 @@ -0,0 +1,54 @@ + + + + +cookies + + + + + +HTTP/1.1 200 OK +Content-Length: 0 + + + + + +# Client-side + + +PSL +cookies + + +http + + +Get cookies from jar using single-component domain name + + +--resolve my-machine:%HTTPPORT:%HOSTIP -b "%LOGDIR/dump%TESTNUMBER" http://my-machine:%HTTPPORT/%TESTNUMBER + + + +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +my-machine%TABFALSE%TAB/%TABFALSE%TAB0%TABworld%TABcrazy + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: my-machine:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* +Cookie: world=crazy + + + + diff --git a/tests/data/test2700 b/tests/data/test2700 index 99399dd6c9ac..2c558911366d 100644 --- a/tests/data/test2700 +++ b/tests/data/test2700 @@ -3,6 +3,7 @@ WebSockets +libtest @@ -17,9 +18,6 @@ ws http - -lib2700 - ws://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test2701 b/tests/data/test2701 index c413c7ab91b9..de33e7004300 100644 --- a/tests/data/test2701 +++ b/tests/data/test2701 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2702 b/tests/data/test2702 index 989d1dfa77a9..55c1a214e8aa 100644 --- a/tests/data/test2702 +++ b/tests/data/test2702 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2703 b/tests/data/test2703 index f39d91ecb41e..4d26dbdb0fa6 100644 --- a/tests/data/test2703 +++ b/tests/data/test2703 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2704 b/tests/data/test2704 index f29699f4e065..3541d1f1594e 100644 --- a/tests/data/test2704 +++ b/tests/data/test2704 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2705 b/tests/data/test2705 index f0cb84346007..035da57ccbfe 100644 --- a/tests/data/test2705 +++ b/tests/data/test2705 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2706 b/tests/data/test2706 index 38c196f27e3b..3d10c7dca0a7 100644 --- a/tests/data/test2706 +++ b/tests/data/test2706 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2707 b/tests/data/test2707 index 6e85911c2a8e..d6878c694a38 100644 --- a/tests/data/test2707 +++ b/tests/data/test2707 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2708 b/tests/data/test2708 index 97563e43bd58..1f3d6ef3b380 100644 --- a/tests/data/test2708 +++ b/tests/data/test2708 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2709 b/tests/data/test2709 index e3a3bb49ee1d..bd62fb6b42d6 100644 --- a/tests/data/test2709 +++ b/tests/data/test2709 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2710 b/tests/data/test2710 index ba8b0124fdc6..f58f9e33ff37 100644 --- a/tests/data/test2710 +++ b/tests/data/test2710 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2711 b/tests/data/test2711 index d0bc298b0c95..fbff03221b9d 100644 --- a/tests/data/test2711 +++ b/tests/data/test2711 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2712 b/tests/data/test2712 index b19058d0494b..9e2e603b2348 100644 --- a/tests/data/test2712 +++ b/tests/data/test2712 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2713 b/tests/data/test2713 index 6c50bc72fe06..2ea63895300d 100644 --- a/tests/data/test2713 +++ b/tests/data/test2713 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2714 b/tests/data/test2714 index 654004966f31..ef543ad2a20c 100644 --- a/tests/data/test2714 +++ b/tests/data/test2714 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2715 b/tests/data/test2715 index 4cf4febb3532..5d8fcd9c2494 100644 --- a/tests/data/test2715 +++ b/tests/data/test2715 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2716 b/tests/data/test2716 index 989e0a1ec90e..49dd99eabf61 100644 --- a/tests/data/test2716 +++ b/tests/data/test2716 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2717 b/tests/data/test2717 index 876f5849bacb..2c457a018553 100644 --- a/tests/data/test2717 +++ b/tests/data/test2717 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2718 b/tests/data/test2718 index aa44e21a8e0c..39c324c819e9 100644 --- a/tests/data/test2718 +++ b/tests/data/test2718 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2719 b/tests/data/test2719 index bfdd49b10467..afc431deb92b 100644 --- a/tests/data/test2719 +++ b/tests/data/test2719 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2720 b/tests/data/test2720 index 3a6c30d06e25..38bab0285752 100644 --- a/tests/data/test2720 +++ b/tests/data/test2720 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2721 b/tests/data/test2721 index a3260bad02ee..8e1ff1b3eb71 100644 --- a/tests/data/test2721 +++ b/tests/data/test2721 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2722 b/tests/data/test2722 index 61dbee612c36..65607d0eb3b5 100644 --- a/tests/data/test2722 +++ b/tests/data/test2722 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2723 b/tests/data/test2723 index 6fbac2bc8837..156ef00e08d3 100644 --- a/tests/data/test2723 +++ b/tests/data/test2723 @@ -3,6 +3,7 @@ WebSockets +libtest diff --git a/tests/data/test2805 b/tests/data/test2805 new file mode 100644 index 000000000000..20c17f63fe4a --- /dev/null +++ b/tests/data/test2805 @@ -0,0 +1,58 @@ + + + + +FTP +LIST +wildcardmatch +ftplistparser +libtest + + + +# Directory content is generated by the ftp server itself + +# Client-side + + +ftp + + +FTP wildcards with percent sign in file name + + +ftp://%HOSTIP:%FTPPORT/fully_simulated/percen%74/*.txt + + + +############################################ +# Verify data after the test has been "shot" + + +0 + + + +USER anonymous +PASS ftp@example.com +PWD +CWD fully_simulated +CWD percent +EPSV +TYPE A +LIST +EPSV +TYPE I +RETR someothertext.txt +EPSV +RETR some%2etxt +QUIT + + +match: 'someothertext.txt' against pattern '*.txt' +match: 'some%2etxt' against pattern '*.txt' +Some junk ;-) This file does not really exist. +percent in file name. + + + diff --git a/tests/data/test2824 b/tests/data/test2824 new file mode 100644 index 000000000000..dbc06d5217a0 --- /dev/null +++ b/tests/data/test2824 @@ -0,0 +1,96 @@ + + + + +HTTP +HTTP GET +HTTP CONNECT +HTTP proxy +HTTP proxy Digest auth +chunked Transfer-Encoding + + + +# Server-side + + +connection-monitor + + +# this is returned first since we get no proxy-auth + +HTTP/1.1 407 Authorization Required to proxy me my dear swsbounce +Proxy-Authenticate: Digest realm="weirdorealm", nonce="12345" +Proxy-Connection: Keep-Alive +Transfer-Encoding: chunked + +20 +And you should ignore this data. +FA0 +%repeat[4000 x X]% +0 + + + +# then this is returned when we get proxy-auth + +HTTP/1.1 407 Authorization Required to proxy me my dear swsbounce +Proxy-Authenticate: Basic +Content-Length: 0 + + + + +HTTP/1.1 403 Denied +Content-Length: 0 + + + + + +# Client-side + + +http + + +SSL +proxy +digest +!SSPI + + +HTTP proxy CONNECT 2 407s, chunked-encoded and 0-length + + +http://test.remote.example.com.%TESTNUMBER:1024/path/%TESTNUMBER0003 --proxy http://%HOSTIP:%HTTPPORT --proxy-user testuser:testpass --proxy-anyauth --proxytunnel --connect-timeout 10 + + + +# Verify data after the test has been "shot" + + +CONNECT test.remote.example.com.%TESTNUMBER:1024 HTTP/1.1 +Host: test.remote.example.com.%TESTNUMBER:1024 +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + +CONNECT test.remote.example.com.%TESTNUMBER:1024 HTTP/1.1 +Host: test.remote.example.com.%TESTNUMBER:1024 +Proxy-Authorization: Digest username="testuser", realm="weirdorealm", nonce="12345", uri="test.remote.example.com.%TESTNUMBER:1024", response="9d235aa1a02467aca00179549f1d2a62" +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + +CONNECT test.remote.example.com.%TESTNUMBER:1024 HTTP/1.1 +Host: test.remote.example.com.%TESTNUMBER:1024 +Proxy-Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M= +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + +[DISCONNECT] + + +7 + + + diff --git a/tests/data/test2918 b/tests/data/test2918 new file mode 100644 index 000000000000..e166d5dd898f --- /dev/null +++ b/tests/data/test2918 @@ -0,0 +1,55 @@ + + + + +HTTP +HTTP GET +Transfer-Encoding +--raw + + +# Server-side + + +# Similar to test 319 but receives headers in a different order + +HTTP/1.1 200 OK swsclose +Date: Mon, 29 Nov 2004 21:56:53 GMT +Server: Apache/1.3.31 (Debian GNU/Linux) mod_gzip/1.3.26.1a PHP/4.3.9-1 mod_ssl/2.8.20 OpenSSL/0.9.7d mod_perl/1.29 +Content-Type: text/html; charset=ISO-8859-1 +Content-Length: 4400 +Transfer-Encoding: gobbledigook + +This is data +that is encoded +but the client +does not know +how to decode + + + + +# Client-side + + +http + + +Ignore content-length with Transfer-Encoding + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER --raw + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/data/test2924 b/tests/data/test2924 new file mode 100644 index 000000000000..283043f4f0a0 --- /dev/null +++ b/tests/data/test2924 @@ -0,0 +1,52 @@ + + + + +CURLOPT_HTTP_TRANSFER_DECODING +chunked Transfer-Encoding +libtest + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 123456789 +Transfer-Encoding: chunked +Content-Type: text/html +Funny-head: yesyes + +10%CR +chunked data fun%CR +0%CR +%CR + + + +# Client-side + + + +http + + +CURLOPT_HTTP_TRANSFER_DECODING, chunked + Content-Length + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test2967 b/tests/data/test2967 new file mode 100644 index 000000000000..6b72807fe2d7 --- /dev/null +++ b/tests/data/test2967 @@ -0,0 +1,65 @@ + + + + +SMTP +SASL +SASL AUTH DIGEST-MD5 +RFC2831 +RFC4954 + + + +# Server-side + + +AUTH DIGEST-MD5 +REPLY AUTH 334 %b64[realm="cu\"rl",nonce="5300d17a1d695bd411e4cdf96f9548c23ced6175",algorithm=md5-sess,qop="auth"]b64% +REPLY %b64[username="us\"er",realm="cu\"rl",nonce="5300d17a1d695bd411e4cdf96f9548c23ced6175",cnonce="34333231353332313633323137333231",nc="00000001",digest-uri="smtp\",injected=1,x=\"/127.0.0.1",response=c6f01f4f75dcb83068b66c8b4f347400,qop=auth]b64% 334 %b64[rspauth=9ea859cb9d90c37ca30d49d35378630c]b64% +REPLY 235 Authenticated + + + +# Client-side + + +smtp + + +!SSPI +Debug +crypto +digest + + +CURL_ENTROPY=12345678 + + +SMTP DIGEST-MD5 with set service name + + +mail body + + +smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt recipient@example.com --mail-from sender@example.com -u "us\"er:secret" -T - --service-name 'smtp",injected=1,x="' + + + +# Verify data after the test has been "shot" + + +EHLO %TESTNUMBER +AUTH DIGEST-MD5 +%b64[username="us\"er",realm="cu\"rl",nonce="5300d17a1d695bd411e4cdf96f9548c23ced6175",cnonce="34333231353332313633323137333231",nc="00000001",digest-uri="smtp\",injected=1,x=\"/127.0.0.1",response=c6f01f4f75dcb83068b66c8b4f347400,qop=auth]b64% + +MAIL FROM:%LTsender@example.com%GT +RCPT TO:%LTrecipient@example.com%GT +DATA +QUIT + + +mail body +. + + + diff --git a/tests/data/test3010 b/tests/data/test3010 index f56ca1496c40..8da249ba87e1 100644 --- a/tests/data/test3010 +++ b/tests/data/test3010 @@ -5,6 +5,7 @@ HTTP HTTP Basic RETRY-AFTER +libtest @@ -39,9 +40,6 @@ http HTTP retry-after reset - -lib%TESTNUMBER - %HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test3021 b/tests/data/test3021 index 1aa973a68843..cddd63fdd73f 100644 --- a/tests/data/test3021 +++ b/tests/data/test3021 @@ -27,7 +27,7 @@ SFTP correct sha256 host key --hostpubsha256 %SSHSRVSHA256 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt -# Needed for MSYS2 to not treat the argument as a POSIX path list +# Needed for MSYS2 to not treat the base64 hash argument as a POSIX path list # that has to be converted to Windows paths MSYS2_ARG_CONV_EXCL=* diff --git a/tests/data/test3022 b/tests/data/test3022 index 6d692a817ef3..817f2bbe3d86 100644 --- a/tests/data/test3022 +++ b/tests/data/test3022 @@ -27,7 +27,7 @@ SCP correct sha256 host key --hostpubsha256 %SSHSRVSHA256 --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: scp://%HOSTIP:%SSHPORT%SCP_PWD/%LOGDIR/file%TESTNUMBER.txt -# Needed for MSYS2 to not treat the argument as a POSIX path list +# Needed for MSYS2 to not treat the base64 hash argument as a POSIX path list # that has to be converted to Windows paths MSYS2_ARG_CONV_EXCL=* diff --git a/tests/data/test3025 b/tests/data/test3025 index 6b44017d626b..4440f2dd50f5 100644 --- a/tests/data/test3025 +++ b/tests/data/test3025 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -25,9 +26,6 @@ http CURLOPT_HTTP200ALIASES - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test3026 b/tests/data/test3026 index d9a636f22380..933a0fcd0fb5 100644 --- a/tests/data/test3026 +++ b/tests/data/test3026 @@ -5,6 +5,7 @@ curl_global_init thread-safe slow +libtest @@ -23,9 +24,6 @@ threaded-resolver curl_global_init thread-safety - -lib%TESTNUMBER - # Verify data after the test has been "shot" diff --git a/tests/data/test3027 b/tests/data/test3027 index d6a79cf7e99f..49bd0dc012fb 100644 --- a/tests/data/test3027 +++ b/tests/data/test3027 @@ -5,6 +5,7 @@ FTP CURLOPT_FILETIME MDTM fail +libtest @@ -28,9 +29,6 @@ ftp Get a file via FTP but 550 after MDTM command - -lib3027 - ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test3033 b/tests/data/test3033 index d633b218e5c2..a4f7ed7dd320 100644 --- a/tests/data/test3033 +++ b/tests/data/test3033 @@ -34,9 +34,6 @@ http CURLOPT_FRESH_CONNECT=2 - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test3034 b/tests/data/test3034 index 5a30a9b55942..92f5682fd44e 100644 --- a/tests/data/test3034 +++ b/tests/data/test3034 @@ -5,6 +5,7 @@ CURLOPT_READFUNCTION curl_easy_reset rewind +libtest @@ -22,9 +23,6 @@ Location: /%TESTNUMBER http - -lib%TESTNUMBER - Test reset resolves rewind failure diff --git a/tests/data/test3037 b/tests/data/test3037 new file mode 100644 index 000000000000..be29b991c12a --- /dev/null +++ b/tests/data/test3037 @@ -0,0 +1,62 @@ + + + + +HTTP +HTTP GET +103 +Early Hints +-J +--show-headers + + + +# Server-side + + +HTTP/1.1 103 Early Hints + +HTTP/1.1 200 OK +Content-Length: 6 +Content-Disposition: attachment; filename="victim%TESTNUMBER" +Connection: close + +12345 + + + +# Client-side + + +http + + +http + + +Do not open output for 103 Early Hints with -J and --show-headers + + +original + + +http://%HOSTIP:%HTTPPORT/victim%TESTNUMBER -O -J -i --output-dir %LOGDIR + + + + + +GET /victim%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +23 + + +original + + + diff --git a/tests/data/test3038 b/tests/data/test3038 new file mode 100644 index 000000000000..ff51458f54bc --- /dev/null +++ b/tests/data/test3038 @@ -0,0 +1,64 @@ + + + + +HTTP +HTTP GET +ETag +--etag-save +-J +--show-headers + + + +# Server-side + + +HTTP/1.1 200 OK +ETag: "tag%TESTNUMBER" +Content-Length: 6 +Content-Disposition: attachment; filename="victim%TESTNUMBER" +Connection: close + +12345 + + + +# Client-side + + +http + + +http + + +Do not open output for ETag with -J, --show-headers, --etag-save + + +original + + +http://%HOSTIP:%HTTPPORT/victim%TESTNUMBER -O -J -i --etag-save %LOGDIR/etag%TESTNUMBER --output-dir %LOGDIR + + + + + +GET /victim%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +23 + + +original + + +"tag%TESTNUMBER" + + + diff --git a/tests/data/test31 b/tests/data/test31 index c885b7c9faed..49e9a0c2ffe2 100644 --- a/tests/data/test31 +++ b/tests/data/test31 @@ -39,7 +39,7 @@ Set-Cookie: secure=very1 ; secure; domain=test31.curl; path=/secure9/; %CR Set-Cookie: httpo1=value1 ; domain=test31.curl; path=/p1/; httponly%CR Set-Cookie: httpo2=value2 ; domain=test31.curl; path=/p2/; httponly%CR Set-Cookie: httpo3=value3 ; httponly; domain=test31.curl; path=/p3/;%CR -Set-Cookie: httpo4=value4 ; httponly; domain=test31.curl; path=/p4/; %CR +Set-Cookie: httpo4=value4 ; httponly =rubbish; domain=test31.curl; path=/p4/; %CR Set-Cookie: httponly=myvalue1 ; domain=test31.curl; path=/p4/; httponly%CR Set-Cookie: httpandsec=myvalue2 ; domain=test31.curl; path=/p4/; httponly; secure%CR Set-Cookie: httpandsec2=myvalue3; domain=test31.curl; path=/p4/; httponly; secure%CR diff --git a/tests/data/test3100 b/tests/data/test3100 index 9afd58730690..0a4b19724a8a 100644 --- a/tests/data/test3100 +++ b/tests/data/test3100 @@ -4,6 +4,7 @@ RTSP RTSP Basic auth +libtest @@ -54,9 +55,6 @@ u=http://www.curl.example.com/fakesdp.ps rtsp - -lib%TESTNUMBER - RTSP Authentication check diff --git a/tests/data/test3101 b/tests/data/test3101 index bb385fe61a67..7d3afb3af459 100644 --- a/tests/data/test3101 +++ b/tests/data/test3101 @@ -4,6 +4,7 @@ HTTP HTTP Basic auth +libtest @@ -59,9 +60,6 @@ https http - -lib%TESTNUMBER - HTTP auth without redirection protocols diff --git a/tests/data/test3102 b/tests/data/test3102 index c66098cfbb69..06876162f060 100644 --- a/tests/data/test3102 +++ b/tests/data/test3102 @@ -4,6 +4,7 @@ HTTPS HTTP GET +libtest @@ -23,9 +24,6 @@ SSL https - -lib%TESTNUMBER - verify certificate chain order with simple HTTPS GET diff --git a/tests/data/test3103 b/tests/data/test3103 index 40179a6fb8fa..884b9f1fca44 100644 --- a/tests/data/test3103 +++ b/tests/data/test3103 @@ -3,6 +3,7 @@ cookies +libtest @@ -33,9 +34,6 @@ proxy http - -lib%TESTNUMBER - CURLOPT_COOKIELIST without expiry diff --git a/tests/data/test3104 b/tests/data/test3104 index 42271d54bd4c..dafa13b66c71 100644 --- a/tests/data/test3104 +++ b/tests/data/test3104 @@ -3,6 +3,7 @@ cookies +libtest @@ -33,9 +34,6 @@ proxy http - -lib%TESTNUMBER - CURLOPT_COOKIELIST with Netscape format diff --git a/tests/data/test3105 b/tests/data/test3105 index a85a89b59778..898ae185fc60 100644 --- a/tests/data/test3105 +++ b/tests/data/test3105 @@ -3,14 +3,12 @@ curl_multi_remove_handle +libtest # Client-side - -lib%TESTNUMBER - curl_multi_remove_handle twice diff --git a/tests/data/test3107 b/tests/data/test3107 new file mode 100644 index 000000000000..732efe68647a --- /dev/null +++ b/tests/data/test3107 @@ -0,0 +1,45 @@ + + + + +HTTP +HTTP GET +multi +curl_multi_remove_handle +libtest + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 6 + +hello + + + +# Client-side + + +http + + +reject internal easy handle passed to a notify callback + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test3109 b/tests/data/test3109 new file mode 100644 index 000000000000..d256a4041652 --- /dev/null +++ b/tests/data/test3109 @@ -0,0 +1,86 @@ + + + + +HTTP +HTTP proxy +HSTS + + + + + +# we use this as response to a CONNECT + +HTTP/1.1 200 OK +Server: fake + + + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Type: text/html +Content-Length: 0 + + + + + + +http +http-proxy +https + + +HSTS +proxy +https +Debug + + +# October 1, 2024 + +CURL_HSTS_HTTP=yes +CURL_TIME=1727733600 + + +# the child name has a later expiury than the parent so it must not be removed +# before + +.example.test "20241010 09:42:27" +.child.example.test "20251010 09:42:27" + + + +HSTS parent expires before child + + +-x http://%HOSTIP:%PROXYPORT http://child.example.test:%HTTPSPORT/%TESTNUMBER --hsts %LOGDIR/input%TESTNUMBER --insecure + + +test-duphandle + + + + + + +HTTP/1.1 200 OK%CR +Server: fake%CR +%CR +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Type: text/html +Content-Length: 0 + + + + +# Your HSTS cache. https://curl.se/docs/hsts.html +# This file was generated by libcurl! Edit at your own risk. +.example.test "20241010 09:42:27" +.child.example.test "20251010 09:42:27" + + + diff --git a/tests/data/test319 b/tests/data/test319 index 33bb2586cb4f..e55dfa21f22a 100644 --- a/tests/data/test319 +++ b/tests/data/test319 @@ -16,19 +16,15 @@ Date: Mon, 29 Nov 2004 21:56:53 GMT Server: Apache/1.3.31 (Debian GNU/Linux) mod_gzip/1.3.26.1a PHP/4.3.9-1 mod_ssl/2.8.20 OpenSSL/0.9.7d mod_perl/1.29 Content-Type: text/html; charset=ISO-8859-1 Transfer-Encoding: gobbledigook -Content-Length: 44 +Content-Length: 4400 -%hex[%1f%8b%08%08%79%9e%ab%41%00%03%6c%61%6c%61%6c%61%00%cb%c9%cc%4b%55%30%e4%52%c8%01%d1%46%5c]hex% -%hex[%10%86%31%17%00]hex% -%hex[%02%71%60%18%00%00%00]hex% +This is data +that is encoded +but the client +does not know +how to decode - -%hex[%1f%8b%08%08%79%9e%ab%41%00%03%6c%61%6c%61%6c%61%00%cb%c9%cc%4b%55%30%e4%52%c8%01%d1%46%5c]hex% -%hex[%10%86%31%17%00]hex% -%hex[%02%71%60%18%00%00%00]hex% - - # Client-side @@ -39,7 +35,7 @@ http HTTP GET gobbledigook transfer-encoded data in raw mode - + http://%HOSTIP:%HTTPPORT/%TESTNUMBER --raw diff --git a/tests/data/test3207 b/tests/data/test3207 index ba15b1a47975..303caae11727 100644 --- a/tests/data/test3207 +++ b/tests/data/test3207 @@ -3,6 +3,7 @@ HTTPS +libtest @@ -34,9 +35,6 @@ https concurrent HTTPS GET using shared SSL session cache - -lib%TESTNUMBER - # provide URL and ca-cert https://localhost:%HTTPSPORT/%TESTNUMBER %CERTDIR/certs/test-ca.crt diff --git a/tests/data/test3208 b/tests/data/test3208 index fb2936fb96e8..d1c991c016f9 100644 --- a/tests/data/test3208 +++ b/tests/data/test3208 @@ -31,9 +31,6 @@ Funny-head: yesyes https # tool is what to use instead of 'curl' - -lib%TESTNUMBER - easy_perform, multi_perform, easy_perform the same handle diff --git a/tests/data/test3211 b/tests/data/test3211 index 1c2643abc143..48ec467611a6 100644 --- a/tests/data/test3211 +++ b/tests/data/test3211 @@ -4,6 +4,9 @@ unittest uint_bset +u8_strset +u32_ptrset +ptrarray @@ -13,7 +16,7 @@ uint_bset unittest -uint_bset and uint_hashset unit tests +uint_bset, u8_strset, u32_ptrset and ptrarray unit tests diff --git a/tests/data/test3230 b/tests/data/test3230 new file mode 100644 index 000000000000..79bed0f5ad2e --- /dev/null +++ b/tests/data/test3230 @@ -0,0 +1,19 @@ + + + + +unittest +multi + + + +# Client-side + + +unittest + + +multi timer unit tests + + + diff --git a/tests/data/test3231 b/tests/data/test3231 new file mode 100644 index 000000000000..1966e508fab1 --- /dev/null +++ b/tests/data/test3231 @@ -0,0 +1,44 @@ + + + + +HTTP +CONNECT_ONLY +CURLMOPT_MAX_HOST_CONNECTIONS +libtest + + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Connection: close + + + + + + +http + + +http + + +CONNECT_ONLY survives the per-host connection limit + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test3232 b/tests/data/test3232 new file mode 100644 index 000000000000..31dcd20d1f89 --- /dev/null +++ b/tests/data/test3232 @@ -0,0 +1,47 @@ + + + + +HTTP +CONNECT_ONLY +CURLMOPT_MAX_TOTAL_CONNECTIONS +libtest + + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Connection: close + + + + + + +http + + +http + + +CONNECT_ONLY survives the total connection limit + + +lib3231 + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/data/test3233 b/tests/data/test3233 new file mode 100644 index 000000000000..c53ef9c45159 --- /dev/null +++ b/tests/data/test3233 @@ -0,0 +1,57 @@ + + + + +RTSP +SESSION_ID +followlocation +libtest + + + +# Server-side + + +RTSP/1.0 302 Found +Server: RTSPD/libcurl-test +CSeq: 1 +Session: SHOULD-NOT-LEAK +Location: rtsp://example.com:%RTSPPORT/%TESTNUMBER +Curl-private: swsbounce swsclose + + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 2 +Curl-private: swsclose + + + + +# Client-Side + + +rtsp + + + +RTSP session id is not sent to another origin after a redirect + + +rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER0001 example.com:%RTSPPORT:%HOSTIP + + + + + +OPTIONS rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER0001 RTSP/1.0 +CSeq: 1 + +OPTIONS rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER0001 RTSP/1.0 +CSeq: 2 + + + + + diff --git a/tests/data/test3234 b/tests/data/test3234 new file mode 100644 index 000000000000..2775b3c26219 --- /dev/null +++ b/tests/data/test3234 @@ -0,0 +1,35 @@ + + + + +HTTP +CONNECT_ONLY +CURLOPT_SHARE +libtest + + + +# Server-side + + + +# Client-side + + +http + + +http + + +CONNECT_ONLY forgets connection when attaching a shared pool + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + + diff --git a/tests/data/test3271 b/tests/data/test3271 new file mode 100644 index 000000000000..f87707e99a0b --- /dev/null +++ b/tests/data/test3271 @@ -0,0 +1,21 @@ + + + + +unittest +CURLOPT_RESOLVE + + + +# Client-side + + +unittest + + +# this test extends 1607 - they should probably be combined one day + +CURLOPT_RESOLVE override + + + diff --git a/tests/data/test33 b/tests/data/test33 index 2dab07cf8adc..82f8ef82c505 100644 --- a/tests/data/test33 +++ b/tests/data/test33 @@ -5,12 +5,11 @@ HTTP HTTP PUT Resume -Content-Range # Server-side - + HTTP/1.1 200 OK swsclose @@ -22,7 +21,7 @@ HTTP/1.1 200 OK swsclose http -HTTP PUT with resume +HTTP PUT rejects upload resume 012345678 @@ -37,25 +36,20 @@ HTTP PUT with resume 012345678 -http://%HOSTIP:%HTTPPORT/%TESTNUMBER -T%LOGDIR/test%TESTNUMBER.txt -C 50 +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -T%LOGDIR/test%TESTNUMBER.txt -C 50 --no-progress-meter # Verify data after the test has been "shot" - -PUT /%TESTNUMBER HTTP/1.1 -Host: %HOSTIP:%HTTPPORT -Content-Range: bytes 50-99/100 -User-Agent: curl/%VERSION -Accept: */* -Content-Length: 50 + +43 + + +curl: (43) HTTP upload cannot be resumed + + -012345678 -012345678 -012345678 -012345678 -012345678 diff --git a/tests/data/test35 b/tests/data/test35 index 04f5093c0dcb..9224f1284d31 100644 --- a/tests/data/test35 +++ b/tests/data/test35 @@ -24,7 +24,7 @@ blablabla http -HTTP POST binary data with embedded NUL +HTTP POST binary data with embedded NUL byte http://%HOSTIP:%HTTPPORT/we/want/%TESTNUMBER --data-binary @%LOGDIR/test%TESTNUMBER.txt diff --git a/tests/data/test3500 b/tests/data/test3500 new file mode 100644 index 000000000000..1bcf3bc17d31 --- /dev/null +++ b/tests/data/test3500 @@ -0,0 +1,47 @@ + + + + +SFTP +known_hosts +libssh2 + + + +# Server-side + + +Test data +for hashed known hosts + + + +# Client-side + + +sftp + + +sftp +libssh2 +!badlibssh + + +%PERL %SRCDIR/libtest/test3500.pl %LOGDIR/server/curl_host_key.pub %LOGDIR/known%TESTNUMBER "[%HOSTIP]:%SSHPORT" + + +SFTP matches requested hashed known_hosts entry + + +--knownhosts %LOGDIR/known%TESTNUMBER --key %LOGDIR/server/curl_client_key --pubkey %LOGDIR/server/curl_client_key.pub -u %USER: sftp://%HOSTIP:%SSHPORT%SFTP_PWD/%LOGDIR/file%TESTNUMBER.txt + + +Test data +for hashed known hosts + + + +# Verify data after the test has been "shot" + + + diff --git a/tests/data/test3555 b/tests/data/test3555 new file mode 100644 index 000000000000..f673ad6ca60b --- /dev/null +++ b/tests/data/test3555 @@ -0,0 +1,54 @@ + + + + +TELNET +UPLOAD +proxy + + + +# This is test 1452 done over HTTP proxy + + +HTTP/1.1 200 welcome +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Accept-Ranges: bytes +Content-Length: 0 +Connection: close + + + + + + +# Client-side + + +telnet +http-proxy + + +http +telnet +proxy + + +Basic TELNET negotiation over HTTP proxy + + +test%TESTNUMBER + + +telnet://%HOSTIP:%TELNETPORT --upload-file - -p -x http://%HOSTIP:%PROXYPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +test%TESTNUMBER + + + diff --git a/tests/data/test3595 b/tests/data/test3595 new file mode 100644 index 000000000000..ff139be02413 --- /dev/null +++ b/tests/data/test3595 @@ -0,0 +1,19 @@ + + + + +mime +libtest + + + +# Client-side + + +curl_mime_free + + + + + + diff --git a/tests/data/test3596 b/tests/data/test3596 new file mode 100644 index 000000000000..d123963007a3 --- /dev/null +++ b/tests/data/test3596 @@ -0,0 +1,34 @@ + + + + +mime +libtest + + + +# Server-side + + + + + +# Client-side + + +Mime + + +http + + +Curl_mime_prepare_headers + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + + + + diff --git a/tests/data/test3611 b/tests/data/test3611 new file mode 100644 index 000000000000..c87dda98492a --- /dev/null +++ b/tests/data/test3611 @@ -0,0 +1,52 @@ + + + + +cookies + + + + + +HTTP/1.1 200 OK +Content-Length: 0 +Set-Cookie: session=content;=path=/insane; HttpOnly; Secure; path=/moo + + + + + +# Client-side + + +cookies + + +https + + +Get cookies with a nameless property + + +--resolve my-machine:%HTTPSPORT:%HOSTIP -c "%LOGDIR/dump%TESTNUMBER" https://my-machine:%HTTPSPORT/moo/%TESTNUMBER -k + + + +# Verify data after the test has been "shot" + + +GET /moo/%TESTNUMBER HTTP/1.1 +Host: my-machine:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +#HttpOnly_my-machine%TABFALSE%TAB/moo%TABTRUE%TAB0%TABsession%TABcontent + + + diff --git a/tests/data/test4039 b/tests/data/test4039 new file mode 100644 index 000000000000..9a30dd6a33a7 --- /dev/null +++ b/tests/data/test4039 @@ -0,0 +1,27 @@ + + + + +--proto-default + + + + + +http + + +--proto-default http with an invalid URL + + +/example.com/ --proto-default http + + + +# Verify data after the test has been "shot" + + +3 + + + diff --git a/tests/data/test4040 b/tests/data/test4040 new file mode 100644 index 000000000000..ae39d378e1a8 --- /dev/null +++ b/tests/data/test4040 @@ -0,0 +1,27 @@ + + + + +--proto-default + + + + + +--proto-default file and invalid URL + + +file + + +--proto-default file remote.example/etc/passwd + + + +# Verify data after the test has been "shot" + + +3 + + + diff --git a/tests/data/test414 b/tests/data/test414 index d04426e060bd..a357ecd9dd54 100644 --- a/tests/data/test414 +++ b/tests/data/test414 @@ -16,7 +16,7 @@ Date: Tue, 09 Nov 2010 14:49:00 GMT Server: test-server/fake Content-Length: 6 Set-Cookie: SESSIONID=originaltoken; secure -Set-Cookie: second=originaltoken; secure; path=/a +Set-Cookie: second=originaltoken; secure = 1; path=/a Location: http://attack.invalid:%HTTPPORT/a/b/%TESTNUMBER0002 -foo- diff --git a/tests/data/test426 b/tests/data/test426 index 1f2443dbfe68..e6b80d90a2c5 100644 --- a/tests/data/test426 +++ b/tests/data/test426 @@ -27,7 +27,10 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER -d foobar -C 3 # Verify data after the test has been "shot" -2 +43 + +curl: (43) HTTP upload cannot be resumed + diff --git a/tests/data/test428 b/tests/data/test428 index 73ef94f4c4e3..306097ff853d 100644 --- a/tests/data/test428 +++ b/tests/data/test428 @@ -36,6 +36,9 @@ FUNVALUE=contents VALUE2=curl BLANK= + +blank-env + Expand environment variables within config file diff --git a/tests/data/test440 b/tests/data/test440 index 3ed08f4730dd..eaf3f518a61e 100644 --- a/tests/data/test440 +++ b/tests/data/test440 @@ -75,7 +75,7 @@ https://this.hsts.example./%TESTNUMBER 7 -Allocations: 160 +Allocations: 200 diff --git a/tests/data/test451 b/tests/data/test451 index 3b9e30a7c4a8..1345bacdf930 100644 --- a/tests/data/test451 +++ b/tests/data/test451 @@ -31,7 +31,7 @@ Funny-head: yesyes http -Variable from file that is JSON and URL encoded (with null byte) +Variable from file that is JSON and URL encoded (with NUL byte) %hex[%01%02%03%00%04%05%06]hex% diff --git a/tests/data/test453 b/tests/data/test453 index 9dab3ba33135..b8574cddaddb 100644 --- a/tests/data/test453 +++ b/tests/data/test453 @@ -13,7 +13,7 @@ variables http -Variable output containing null byte +Variable output containing NUL byte %hex[%01%02%03%00%04%05%06]hex% diff --git a/tests/data/test456 b/tests/data/test456 index 169cf3882ca1..4f9b59ddf9bc 100644 --- a/tests/data/test456 +++ b/tests/data/test456 @@ -13,7 +13,7 @@ variables http -Variable output starting with null byte +Variable output starting with NUL byte %hex[%00]hex%hello diff --git a/tests/data/test463 b/tests/data/test463 index 4cd99d546c78..05191e9dbc83 100644 --- a/tests/data/test463 +++ b/tests/data/test463 @@ -24,7 +24,7 @@ ok! http -HTTP with -d @file with file containing CR, LF and null byte +HTTP with -d @file with file containing CR, LF and NUL byte %hex[one%0dtwo%0dthree]hex% diff --git a/tests/data/test464 b/tests/data/test464 new file mode 100644 index 000000000000..b0974b92ce01 --- /dev/null +++ b/tests/data/test464 @@ -0,0 +1,29 @@ + + + + +DNS +HTTPS RR +libtest + + + +# Client-side + + +Debug +asyn-rr + + +https://localhost:%NOLISTENPORT/464 + + + +resolver start callback vetoes the HTTPS RR query before it is sent + + + +# Verify data after the test has been "shot" + + + diff --git a/tests/data/test4644 b/tests/data/test4644 new file mode 100644 index 000000000000..4a708a22b74e --- /dev/null +++ b/tests/data/test4644 @@ -0,0 +1,56 @@ + + + + +--no-clobber + + + + + +HTTP/1.1 200 OK swsclose +Content-Length: 12 +Connection: close +Content-Type: text/html + +new content + + + + + +exists before command runs + + +exists before command runs + + +http + + +--no-clobber with extension and an added number + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER -o %LOGDIR/save.txt --no-clobber + + + +# Verify data after the test has been "shot". + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +# this file should be untouched + +exists before command runs + + +new content + + + diff --git a/tests/data/test4656 b/tests/data/test4656 new file mode 100644 index 000000000000..54aedb72f014 --- /dev/null +++ b/tests/data/test4656 @@ -0,0 +1,86 @@ + + + + +HTTP CONNECT +HTTP proxy + + + +# Server-side + + + +HTTP/1.1 301 OK +Location: %TESTNUMBER0002 +Content-Length: 0 +Connection: close + + + + +HTTP/1.1 200 OK swsclose +Content-Length: 18 + +final destination + + + +HTTP/1.1 200 welcome dear +Date: Tue, 09 Nov 2010 14:49:00 GMT +Content-Disposition: attachment; filename="proxy-chosen.bin" + + + + + +# Client-side + + +IPv6 +proxy + + +http-proxy +http +https + + +HTTPS over proxy with CONNECT redirected and -OJ + + +https://remotesite:%HTTPSPORT/%TESTNUMBER -L -O -J -x %HOSTIP:%PROXYPORT --insecure --output-dir=%LOGDIR + + + +# Verify data after the test has been "shot" + + +CONNECT remotesite:%HTTPSPORT HTTP/1.1 +Host: remotesite:%HTTPSPORT +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + +CONNECT remotesite:%HTTPSPORT HTTP/1.1 +Host: remotesite:%HTTPSPORT +User-Agent: curl/%VERSION +Proxy-Connection: Keep-Alive + + + +GET /%TESTNUMBER HTTP/1.1 +Host: remotesite:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: remotesite:%HTTPSPORT +User-Agent: curl/%VERSION +Accept: */* + + + +final destination + + + diff --git a/tests/data/test4657 b/tests/data/test4657 new file mode 100644 index 000000000000..4fbd66d43332 --- /dev/null +++ b/tests/data/test4657 @@ -0,0 +1,89 @@ + + + + +--remote-header-name + + + +# Server-side + + + +HTTP/1.1 301 OK +Location: %TESTNUMBER0002 +Content-Length: 0 + + + + +HTTP/1.1 301 OK +Location: %TESTNUMBER0003 +Content-Length: 0 + + + + +HTTP/1.1 301 OK +Location: %TESTNUMBER0004 +Content-Length: 0 + + + + +HTTP/1.1 200 OK +Content-Length: 8 + +content + + + + +# Client-side + + +IPv6 +proxy + + +http-proxy +http +https + + +-J with multiple redirects, use the last name + + +-L -O -J http://%HOSTIP:%HTTPPORT --output-dir=%LOGDIR + + + +# Verify data after the test has been "shot" + + +GET / HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0002 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0003 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + +GET /%TESTNUMBER0004 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +User-Agent: curl/%VERSION +Accept: */* + + + +content + + + diff --git a/tests/data/test4781 b/tests/data/test4781 new file mode 100644 index 000000000000..556054dbf663 --- /dev/null +++ b/tests/data/test4781 @@ -0,0 +1,21 @@ + + + + +unittest +weekday +day +monthname + + + + + +unittest + + +weekday, month name and timezone parsers + + + + diff --git a/tests/data/test4782 b/tests/data/test4782 new file mode 100644 index 000000000000..dd44ac857785 --- /dev/null +++ b/tests/data/test4782 @@ -0,0 +1,20 @@ + + + + +unittest +Schannel +TLS + + + + + +unittest +Schannel + + +Schannel shutdown retries retain unsent TLS records + + + diff --git a/tests/data/test500 b/tests/data/test500 index 077b119ff44d..b36f7b254157 100644 --- a/tests/data/test500 +++ b/tests/data/test500 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # Server-side @@ -30,9 +31,6 @@ Funny-head: yesyes http # tool is what to use instead of 'curl' - -lib500 - simple libcurl HTTP GET tool diff --git a/tests/data/test5000 b/tests/data/test5000 index 3f2a98280206..1a3f99edec0d 100644 --- a/tests/data/test5000 +++ b/tests/data/test5000 @@ -5,6 +5,7 @@ HTTP CURLOPT_HTTPSIG_ALGORITHM RFC9421 +libtest @@ -34,9 +35,6 @@ httpsig httpsig: basic GET with Ed25519 - -lib%TESTNUMBER - http://example.com:9000/%TESTNUMBER/resource example.com:9000:%HOSTIP:%HTTPPORT diff --git a/tests/data/test5004 b/tests/data/test5004 index 33822b6b6d65..bd6e83e8f213 100644 --- a/tests/data/test5004 +++ b/tests/data/test5004 @@ -7,6 +7,7 @@ CURLOPT_HTTPSIG_ALGORITHM RFC9421 ed25519 B.2.6 +libtest @@ -35,9 +36,6 @@ httpsig httpsig: Ed25519 POST with headers (RFC test vector) - -lib%TESTNUMBER - CURL_HTTPSIG_CREATED=1618884473 diff --git a/tests/data/test501 b/tests/data/test501 index 52e2023df2f9..3749d9bcf171 100644 --- a/tests/data/test501 +++ b/tests/data/test501 @@ -3,6 +3,7 @@ missing URL +libtest # Server-side @@ -15,9 +16,6 @@ missing URL http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - simple libcurl attempt operation without URL set diff --git a/tests/data/test5018 b/tests/data/test5018 index 365a85fb2b23..abbfb5f82d40 100644 --- a/tests/data/test5018 +++ b/tests/data/test5018 @@ -32,14 +32,14 @@ http Debug httpsig - + putdata httpsig: PUT upload -"http://example.com:5800/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -T log/upload5018 --connect-to example.com:5800:%HOSTIP:%HTTPPORT +"http://example.com:5800/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key @%SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -T %LOGDIR/upload5018 --connect-to example.com:5800:%HOSTIP:%HTTPPORT diff --git a/tests/data/test502 b/tests/data/test502 index 5e0c57660826..fde002d3597c 100644 --- a/tests/data/test502 +++ b/tests/data/test502 @@ -3,6 +3,7 @@ FILE +libtest # Server-side @@ -22,9 +23,6 @@ moo file # tool is what to use instead of 'curl' - -lib%TESTNUMBER - simple multi file:// get diff --git a/tests/data/test5029 b/tests/data/test5029 new file mode 100644 index 000000000000..f0a15af24071 --- /dev/null +++ b/tests/data/test5029 @@ -0,0 +1,47 @@ + + + + +HTTPS +connect timeout + + + +# Server-side + + + +# Client-side + + +SSL + + +http + + +-w IP info and connect time after TLS handshake timeout + +# The plain HTTP server never answers the TLS ClientHello, so the TCP +# connect succeeds and the TLS handshake times out + +-k https://%HOSTIP:%HTTPPORT/%TESTNUMBER --connect-timeout 2 -w 'remote_ip == %{remote_ip}\nremote_port == %{remote_port}\nlocal ip == %{local_ip}\ntime_connect == %{time_connect}\n' + + + +# Verify data after the test has been "shot" + + +28 + + +s/time_connect == (?!0\.0+$)\d+\.\d+$/time_connect == [non-zero]/ + + +remote_ip == %HOSTIP +remote_port == %HTTPPORT +local ip == 127.0.0.1 +time_connect == [non-zero] + + + diff --git a/tests/data/test503 b/tests/data/test503 index b09ac69ddde7..a2748167eda9 100644 --- a/tests/data/test503 +++ b/tests/data/test503 @@ -9,6 +9,7 @@ HTTP proxy HTTP proxy Basic auth proxytunnel multi +libtest @@ -48,9 +49,6 @@ http http-proxy # tool is what to use instead of 'curl' - -lib%TESTNUMBER - proxy diff --git a/tests/data/test5030 b/tests/data/test5030 new file mode 100644 index 000000000000..dd187825d013 --- /dev/null +++ b/tests/data/test5030 @@ -0,0 +1,47 @@ + + + + +HTTPS +FTP + + + +# Server-side + + + +# Client-side + + +SSL + + +ftp + + +-w IP info and connect time after failed TLS handshake + +# The FTP server sends its plain text greeting, so the TCP connect +# succeeds and the TLS handshake fails + +-k https://%HOSTIP:%FTPPORT/%TESTNUMBER -w 'remote_ip == %{remote_ip}\nremote_port == %{remote_port}\nlocal ip == %{local_ip}\ntime_connect == %{time_connect}\n' + + + +# Verify data after the test has been "shot" + + +35 + + +s/time_connect == (?!0\.0+$)\d+\.\d+$/time_connect == [non-zero]/ + + +remote_ip == %HOSTIP +remote_port == %FTPPORT +local ip == 127.0.0.1 +time_connect == [non-zero] + + + diff --git a/tests/data/test504 b/tests/data/test504 index c4670ee1b174..87aed4566ada 100644 --- a/tests/data/test504 +++ b/tests/data/test504 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy multi connect to non-listen +libtest @@ -21,9 +22,6 @@ http proxy # tool is what to use instead of 'curl' - -lib%TESTNUMBER - simple multi through local proxy without listener diff --git a/tests/data/test505 b/tests/data/test505 index be6be1b7f1ae..c16ca7ecb639 100644 --- a/tests/data/test505 +++ b/tests/data/test505 @@ -4,6 +4,7 @@ FTP STOR +libtest @@ -17,9 +18,6 @@ STOR ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - FTP upload with rename after transfer diff --git a/tests/data/test506 b/tests/data/test506 index ed8e4e315e75..9dbc32d53433 100644 --- a/tests/data/test506 +++ b/tests/data/test506 @@ -6,6 +6,7 @@ HTTP HTTP GET cookies shared cookies +libtest @@ -71,9 +72,6 @@ HTTP with shared cookie list (and dns cache) TZ=GMT - -lib%TESTNUMBER - http://%HOSTIP:%HTTPPORT/%TESTNUMBER %LOGDIR/jar%TESTNUMBER diff --git a/tests/data/test507 b/tests/data/test507 index 5915ef70422b..c527785a8932 100644 --- a/tests/data/test507 +++ b/tests/data/test507 @@ -5,6 +5,7 @@ HTTP multi non-existing host +libtest @@ -20,9 +21,6 @@ http multi interface get with non-existing hostname - -lib%TESTNUMBER - http://non-existing-host.haxx.se/ diff --git a/tests/data/test508 b/tests/data/test508 index 11bf74a9b35f..f99a431b60f0 100644 --- a/tests/data/test508 +++ b/tests/data/test508 @@ -5,6 +5,7 @@ HTTP POST POST callback +libtest # Server-side @@ -26,9 +27,6 @@ hello http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - send HTTP POST using read callback diff --git a/tests/data/test509 b/tests/data/test509 index a73fbaf5ca8b..6092284dd1e0 100644 --- a/tests/data/test509 +++ b/tests/data/test509 @@ -3,6 +3,7 @@ memory callbacks +libtest # Server-side @@ -15,9 +16,6 @@ memory callbacks http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - initialization with memory callbacks and actual usage diff --git a/tests/data/test510 b/tests/data/test510 index a865c577056a..730149f61f81 100644 --- a/tests/data/test510 +++ b/tests/data/test510 @@ -5,6 +5,7 @@ HTTP HTTP POST chunked Transfer-Encoding +libtest @@ -27,9 +28,6 @@ hello http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - send HTTP POST using read callback, using chunked transfer-encoding diff --git a/tests/data/test511 b/tests/data/test511 index ea7deec2ad7a..fb4675d82835 100644 --- a/tests/data/test511 +++ b/tests/data/test511 @@ -3,6 +3,7 @@ FTP +libtest @@ -20,9 +21,6 @@ REPLY SIZE 550 bluah you f00l! ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - FTP with FILETIME and NOBODY but missing file diff --git a/tests/data/test512 b/tests/data/test512 index daa336273110..58e673b0dec8 100644 --- a/tests/data/test512 +++ b/tests/data/test512 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -25,9 +26,6 @@ hello http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - simple curl_easy_duplicate() test diff --git a/tests/data/test513 b/tests/data/test513 index 551047c94059..d1ddc519326f 100644 --- a/tests/data/test513 +++ b/tests/data/test513 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest @@ -17,9 +18,6 @@ HTTP POST http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - send HTTP POST using read callback that returns CURL_READFUNC_ABORT diff --git a/tests/data/test514 b/tests/data/test514 index fadbd9d50477..a09079744c14 100644 --- a/tests/data/test514 +++ b/tests/data/test514 @@ -4,6 +4,7 @@ HTTP HTTP HEAD +libtest # Server-side @@ -28,9 +29,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - First set options to POST and then to make HEAD diff --git a/tests/data/test515 b/tests/data/test515 index 690f5d54cb35..1f5cac33fa81 100644 --- a/tests/data/test515 +++ b/tests/data/test515 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest # Server-side @@ -24,9 +25,6 @@ OK http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - make a POSTFIELDS set to NULL with POSTFIELDSIZE set to zero diff --git a/tests/data/test516 b/tests/data/test516 index ae087387b4b6..5518c6452bbc 100644 --- a/tests/data/test516 +++ b/tests/data/test516 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest @@ -29,9 +30,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - make an HTTPPOST set to NULL diff --git a/tests/data/test517 b/tests/data/test517 index 6af03b7b6204..e67f851d5296 100644 --- a/tests/data/test517 +++ b/tests/data/test517 @@ -3,7 +3,7 @@ getdate -unittest +libtest # Server-side @@ -13,9 +13,6 @@ unittest # Client-side # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_getdate() testing diff --git a/tests/data/test518 b/tests/data/test518 index 54aa63122197..0b2e8ba6779b 100644 --- a/tests/data/test518 +++ b/tests/data/test518 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -34,9 +35,6 @@ http getrlimit # tool is what to use instead of 'curl' - -lib%TESTNUMBER - # precheck is a command line to run before the test, to see if we can execute # the test or not diff --git a/tests/data/test519 b/tests/data/test519 index 53ad2a095c61..73e474d9bae0 100644 --- a/tests/data/test519 +++ b/tests/data/test519 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -47,9 +48,6 @@ content2 http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - GET same URL twice with different users diff --git a/tests/data/test520 b/tests/data/test520 index 1ae3607754a0..1ad68b70ce86 100644 --- a/tests/data/test520 +++ b/tests/data/test520 @@ -3,6 +3,7 @@ FTP +libtest @@ -22,9 +23,6 @@ REPLY MDTM 213 20030405060708 ftp # tool is what to use instead of 'curl' - -lib520 - FTP RETR with FILETIME diff --git a/tests/data/test521 b/tests/data/test521 index 84e1a3646d34..be4c69bfcf9b 100644 --- a/tests/data/test521 +++ b/tests/data/test521 @@ -5,6 +5,7 @@ FTP PASV CURLOPT_PORT +libtest # Server-side @@ -29,9 +30,6 @@ dr-xr-xr-x 5 0 1 512 Oct 1 1997 usr ftp - -lib%TESTNUMBER - FTP dir list PASV with CURLOPT_PORT diff --git a/tests/data/test522 b/tests/data/test522 index ef76f5480f01..67002b168524 100644 --- a/tests/data/test522 +++ b/tests/data/test522 @@ -5,6 +5,7 @@ HTTP HTTP GET CURLOPT_PORT +libtest diff --git a/tests/data/test523 b/tests/data/test523 index 5c0db2c3e2ae..f98643f680d1 100644 --- a/tests/data/test523 +++ b/tests/data/test523 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy CURLOPT_PORT CURLOPT_PROXY +libtest @@ -30,9 +31,6 @@ hello http - -lib%TESTNUMBER - HTTP GET with proxy and CURLOPT_PORT diff --git a/tests/data/test524 b/tests/data/test524 index 37b33cb835f2..ac45ee3ea7db 100644 --- a/tests/data/test524 +++ b/tests/data/test524 @@ -4,6 +4,7 @@ FTP UPLOAD +libtest @@ -16,9 +17,6 @@ UPLOAD ftp - -lib%TESTNUMBER - FTP upload with target URL ending with slash diff --git a/tests/data/test525 b/tests/data/test525 index 11bc85c1571b..e86a6113aba8 100644 --- a/tests/data/test525 +++ b/tests/data/test525 @@ -6,6 +6,7 @@ FTP PORT STOR multi +libtest @@ -20,9 +21,6 @@ multi ftp - -lib%TESTNUMBER - FTP PORT upload using multi interface diff --git a/tests/data/test526 b/tests/data/test526 index a55a1f5eaeb3..2bb05dac92b5 100644 --- a/tests/data/test526 +++ b/tests/data/test526 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest @@ -27,9 +28,6 @@ file contents should appear once for each file ftp - -lib%TESTNUMBER - FTP RETR same file using different handles but same connection diff --git a/tests/data/test527 b/tests/data/test527 index 735bd20e8528..292e45af015c 100644 --- a/tests/data/test527 +++ b/tests/data/test527 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest diff --git a/tests/data/test528 b/tests/data/test528 index 54f170727649..3e9f39b07637 100644 --- a/tests/data/test528 +++ b/tests/data/test528 @@ -5,6 +5,7 @@ HTTP HTTP GET multi +libtest diff --git a/tests/data/test529 b/tests/data/test529 index 52495ce63c6e..3f6cd30f701c 100644 --- a/tests/data/test529 +++ b/tests/data/test529 @@ -6,6 +6,7 @@ FTP PORT STOR multi +libtest diff --git a/tests/data/test530 b/tests/data/test530 index 0875b5e5f81e..17b9803c3ce9 100644 --- a/tests/data/test530 +++ b/tests/data/test530 @@ -4,6 +4,7 @@ multi HTTP +libtest @@ -31,9 +32,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - multi_socket interface transfer with callbacks returning error diff --git a/tests/data/test531 b/tests/data/test531 index a43d4d787ee8..84989087c1b6 100644 --- a/tests/data/test531 +++ b/tests/data/test531 @@ -6,6 +6,7 @@ FTP PORT STOR multi +libtest diff --git a/tests/data/test532 b/tests/data/test532 index aed9c3dd773b..ac95b9dfb859 100644 --- a/tests/data/test532 +++ b/tests/data/test532 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest diff --git a/tests/data/test533 b/tests/data/test533 index e0ca3b014260..c88ccd3b55fd 100644 --- a/tests/data/test533 +++ b/tests/data/test533 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest @@ -25,9 +26,6 @@ REPLY SIZE 500 Can't check for file existence ftp - -lib%TESTNUMBER - FTP RETR a non-existing file twice using the multi interface diff --git a/tests/data/test534 b/tests/data/test534 index 5ef1f68abd03..a235b41c85be 100644 --- a/tests/data/test534 +++ b/tests/data/test534 @@ -7,6 +7,7 @@ PASV RETR multi non-existing host +libtest diff --git a/tests/data/test535 b/tests/data/test535 index 49802e21ca90..f493662e93fc 100644 --- a/tests/data/test535 +++ b/tests/data/test535 @@ -5,6 +5,7 @@ HTTP HTTP GET multi +libtest diff --git a/tests/data/test536 b/tests/data/test536 index e0bcc906f7de..e0af3ef61ac2 100644 --- a/tests/data/test536 +++ b/tests/data/test536 @@ -8,6 +8,7 @@ HTTP proxy CURLOPT_PROXY CURLOPT_NOBODY CURLINFO_USED_PROXY +libtest @@ -31,9 +32,6 @@ hello http - -lib%TESTNUMBER - CURLINFO_USED_PROXY diff --git a/tests/data/test537 b/tests/data/test537 index 177636b3e069..7be43454b162 100644 --- a/tests/data/test537 +++ b/tests/data/test537 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -34,9 +35,6 @@ http getrlimit # tool is what to use instead of 'curl' - -lib%TESTNUMBER - # precheck is a command line to run before the test, to see if we can execute # the test or not diff --git a/tests/data/test538 b/tests/data/test538 index 69e6fa931c35..39fe02bbc5ae 100644 --- a/tests/data/test538 +++ b/tests/data/test538 @@ -4,6 +4,7 @@ FTP multi +libtest diff --git a/tests/data/test539 b/tests/data/test539 index 2a487c652a62..d13456e838fc 100644 --- a/tests/data/test539 +++ b/tests/data/test539 @@ -3,6 +3,7 @@ FTP +libtest @@ -28,9 +29,6 @@ contents for file number two ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Two FTP fetches using different CURLOPT_FTP_FILEMETHOD diff --git a/tests/data/test540 b/tests/data/test540 index 0999f5e96993..4a75376004c1 100644 --- a/tests/data/test540 +++ b/tests/data/test540 @@ -7,6 +7,7 @@ HTTP GET HTTP proxy HTTP proxy Digest auth multi +libtest @@ -62,9 +63,6 @@ Nice proxy auth sir! http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test541 b/tests/data/test541 index 67039a3e8173..68a0dc29eeb9 100644 --- a/tests/data/test541 +++ b/tests/data/test541 @@ -4,6 +4,7 @@ FTP STOR +libtest @@ -17,9 +18,6 @@ STOR ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - FTP upload and upload same file again without rewind diff --git a/tests/data/test542 b/tests/data/test542 index 29aa562d6869..bc57ec77c04b 100644 --- a/tests/data/test542 +++ b/tests/data/test542 @@ -5,6 +5,7 @@ FTP PASV RETR +libtest # Server-side @@ -28,9 +29,6 @@ Accept-ranges: bytes ftp - -lib%TESTNUMBER - FTP a file with NOBODY yes and HEADER no diff --git a/tests/data/test543 b/tests/data/test543 index ec6677bf0b87..02aadd3d72ac 100644 --- a/tests/data/test543 +++ b/tests/data/test543 @@ -3,15 +3,13 @@ curl_easy_escape +libtest # Server-side # Client-side - -lib%TESTNUMBER - curl_easy_escape diff --git a/tests/data/test544 b/tests/data/test544 index f71cd095e784..5b6aeb0a2e13 100644 --- a/tests/data/test544 +++ b/tests/data/test544 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest @@ -25,9 +26,6 @@ OK http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP POST text data using CURLOPT_COPYPOSTFIELDS diff --git a/tests/data/test545 b/tests/data/test545 index 0cffd7b20be5..d7cf3b6c5c38 100644 --- a/tests/data/test545 +++ b/tests/data/test545 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest @@ -30,7 +31,7 @@ lib544 -HTTP POST binary with embedded zero and no trailing zero +HTTP POST binary with embedded NUL byte and no null-terminator http://%HOSTIP:%HTTPPORT/%TESTNUMBER diff --git a/tests/data/test546 b/tests/data/test546 index 97459e9e3b52..141752d02caf 100644 --- a/tests/data/test546 +++ b/tests/data/test546 @@ -6,6 +6,7 @@ FTP PASV RETR multi +libtest # Server-side diff --git a/tests/data/test547 b/tests/data/test547 index 723d687f1048..8a26250f4ccb 100644 --- a/tests/data/test547 +++ b/tests/data/test547 @@ -8,6 +8,7 @@ POST callback HTTP proxy HTTP proxy NTLM auth NTLM +libtest # Server-side @@ -73,9 +74,6 @@ Contents of that page you requested, sir. http # tool to use - -lib%TESTNUMBER - NTLM SSL diff --git a/tests/data/test548 b/tests/data/test548 index 2a60640fc224..1f12ff1d5571 100644 --- a/tests/data/test548 +++ b/tests/data/test548 @@ -7,6 +7,7 @@ HTTP POST HTTP proxy HTTP proxy NTLM auth NTLM +libtest diff --git a/tests/data/test549 b/tests/data/test549 index c20612fc274a..e352e431f995 100644 --- a/tests/data/test549 +++ b/tests/data/test549 @@ -6,6 +6,7 @@ FTP CURLOPT_PROXY_TRANSFER_MODE CURLOPT_PROXY HTTP proxy +libtest @@ -33,9 +34,6 @@ http ftp proxy - -lib%TESTNUMBER - FTP RETR over proxy with CURLOPT_PROXY_TRANSFER_MODE diff --git a/tests/data/test550 b/tests/data/test550 index 7ef61f89a431..804f83528430 100644 --- a/tests/data/test550 +++ b/tests/data/test550 @@ -6,6 +6,7 @@ FTP HTTP proxy CURLOPT_PROXY_TRANSFER_MODE CURLOPT_PROXY +libtest diff --git a/tests/data/test551 b/tests/data/test551 index 410214523026..42f524d50128 100644 --- a/tests/data/test551 +++ b/tests/data/test551 @@ -8,6 +8,7 @@ HTTP POST POST callback HTTP proxy HTTP proxy Digest auth +libtest diff --git a/tests/data/test552 b/tests/data/test552 index 621f378b1ec5..a2b31f2a3054 100644 --- a/tests/data/test552 +++ b/tests/data/test552 @@ -11,6 +11,7 @@ HTTP POST POST callback HTTP proxy HTTP proxy Digest auth +libtest # Server-side @@ -51,9 +52,6 @@ Contents of that page you requested, sir. http # tool to use - -lib%TESTNUMBER - !SSPI crypto diff --git a/tests/data/test553 b/tests/data/test553 index 0a1ce9e414cd..ec9ac75aca92 100644 --- a/tests/data/test553 +++ b/tests/data/test553 @@ -6,6 +6,7 @@ HTTP HTTP POST POST callback huge request header +libtest # Server-side @@ -27,9 +28,6 @@ hello http # tool to use - -lib%TESTNUMBER - diff --git a/tests/data/test554 b/tests/data/test554 index 04be5b582e98..13028ad8d71e 100644 --- a/tests/data/test554 +++ b/tests/data/test554 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest @@ -46,9 +47,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP multi-part formpost using read callback for the file part diff --git a/tests/data/test555 b/tests/data/test555 index bb2ff515e8a9..073873816bf7 100644 --- a/tests/data/test555 +++ b/tests/data/test555 @@ -12,6 +12,7 @@ HTTP proxy HTTP proxy NTLM auth multi NTLM +libtest @@ -78,9 +79,6 @@ Contents of that page you requested, sir. http # tool to use - -lib%TESTNUMBER - NTLM SSL diff --git a/tests/data/test556 b/tests/data/test556 index 7d39cc1737c6..826ce4a9a194 100644 --- a/tests/data/test556 +++ b/tests/data/test556 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest @@ -24,9 +25,6 @@ Connection: close http - -lib%TESTNUMBER - send and recv HTTP diff --git a/tests/data/test557 b/tests/data/test557 index df859e97c7c6..75e39e49ff7d 100644 --- a/tests/data/test557 +++ b/tests/data/test557 @@ -3,7 +3,7 @@ printf -unittest +libtest # Server-side @@ -13,9 +13,6 @@ unittest # Client-side # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_mprintf() testing diff --git a/tests/data/test558 b/tests/data/test558 index e6513ac5a30a..7b3d21d63477 100644 --- a/tests/data/test558 +++ b/tests/data/test558 @@ -3,6 +3,7 @@ TrackMemory +libtest @@ -17,9 +18,6 @@ TrackMemory IPv6 # tool is what to use instead of 'curl' - -lib%TESTNUMBER - libtest memory tracking operational @@ -32,8 +30,7 @@ libtest memory tracking operational MEM easy.c: malloc() MEM lib%TESTNUMBER.c: malloc() MEM lib%TESTNUMBER.c: free() -MEM dynbuf.c: realloc() -MEM dynbuf.c: realloc() +MEM escape.c: malloc() MEM escape.c: free() MEM easy.c: free() diff --git a/tests/data/test559 b/tests/data/test559 index 35f888ad469a..fe49b154becd 100644 --- a/tests/data/test559 +++ b/tests/data/test559 @@ -4,6 +4,7 @@ CURLOPT_BUFFERSIZE HTTP +libtest @@ -31,9 +32,6 @@ Silly-header: %repeat[511 x Z]% http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - use tiny CURLOPT_BUFFERSIZE diff --git a/tests/data/test560 b/tests/data/test560 index 9aa36a479536..f980e665bf44 100644 --- a/tests/data/test560 +++ b/tests/data/test560 @@ -5,6 +5,7 @@ HTTPS HTTP GET multi +libtest @@ -28,9 +29,6 @@ SSL https - -lib%TESTNUMBER - simple HTTPS GET with multi interface diff --git a/tests/data/test561 b/tests/data/test561 index bd752397f3dc..20ee5e7cd7ea 100644 --- a/tests/data/test561 +++ b/tests/data/test561 @@ -7,6 +7,7 @@ HTTP proxy CURLOPT_PROXY_TRANSFER_MODE CURLOPT_PROXY type= +libtest diff --git a/tests/data/test562 b/tests/data/test562 index 6e0778b66103..d83b02d37b74 100644 --- a/tests/data/test562 +++ b/tests/data/test562 @@ -5,6 +5,7 @@ FTP PASV RETR +libtest # Server-side @@ -20,9 +21,6 @@ over FTP ftp - -lib%TESTNUMBER - FTP a type=A URL and CURLOPT_PORT set diff --git a/tests/data/test563 b/tests/data/test563 index c3f09884a3fa..987a8eab6db4 100644 --- a/tests/data/test563 +++ b/tests/data/test563 @@ -5,6 +5,7 @@ FTP CURLOPT_PORT HTTP proxy +libtest diff --git a/tests/data/test564 b/tests/data/test564 index dba2a7fb0365..f309ea200cb1 100644 --- a/tests/data/test564 +++ b/tests/data/test564 @@ -9,6 +9,7 @@ PASV RETR multi SOCKS4 +libtest # Server-side @@ -30,9 +31,6 @@ works ftp socks4 - -lib%TESTNUMBER - FTP RETR a file over a SOCKS proxy using the multi interface diff --git a/tests/data/test565 b/tests/data/test565 index 6975239b77de..06e8e6509e1b 100644 --- a/tests/data/test565 +++ b/tests/data/test565 @@ -6,6 +6,7 @@ HTTP HTTP POST HTTP Digest auth chunked Transfer-Encoding +libtest # Server-side diff --git a/tests/data/test566 b/tests/data/test566 index 993ba79635a5..c922212cd30a 100644 --- a/tests/data/test566 +++ b/tests/data/test566 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest # Server-side @@ -29,9 +30,6 @@ Funny-head: yesyes http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP GET with CURLINFO_CONTENT_LENGTH_DOWNLOAD and 0 bytes transfer diff --git a/tests/data/test567 b/tests/data/test567 index 756e8d438ea4..bfab1a349a41 100644 --- a/tests/data/test567 +++ b/tests/data/test567 @@ -4,6 +4,7 @@ RTSP OPTIONS +libtest @@ -24,9 +25,6 @@ Curl-Private: swsclose rtsp - -lib%TESTNUMBER - simple RTSP OPTIONS command diff --git a/tests/data/test568 b/tests/data/test568 index 07b610dcc6d0..ca57db875172 100644 --- a/tests/data/test568 +++ b/tests/data/test568 @@ -4,6 +4,7 @@ RTSP ANNOUNCE +libtest @@ -49,9 +50,6 @@ Informational: Empty Options Response rtsp - -lib%TESTNUMBER - RTSP Announce (PUT and POST style) test diff --git a/tests/data/test569 b/tests/data/test569 index 1e6ec7c00f66..39a145929e86 100644 --- a/tests/data/test569 +++ b/tests/data/test569 @@ -4,6 +4,7 @@ RTSP SESSION_ID +libtest @@ -57,9 +58,6 @@ CSeq: 6 rtsp - -lib%TESTNUMBER - RTSP Session ID parsing diff --git a/tests/data/test570 b/tests/data/test570 index 10739b071af2..58035bfb5f23 100644 --- a/tests/data/test570 +++ b/tests/data/test570 @@ -4,6 +4,7 @@ RTSP Mismatch checking +libtest @@ -41,9 +42,6 @@ CSeq: 1000 rtsp - -lib%TESTNUMBER - RTSP CSeq and Session Mismatch checks diff --git a/tests/data/test571 b/tests/data/test571 index 258ee6ae68d9..56f6f8f63c51 100644 --- a/tests/data/test571 +++ b/tests/data/test571 @@ -8,6 +8,7 @@ RTSP RTP +libtest @@ -77,9 +78,6 @@ rtp: part 4 channel 0 size 63 rtsp - -lib%TESTNUMBER - RTSP RTP Interleaving Test diff --git a/tests/data/test572 b/tests/data/test572 index 7f887f6b98d6..fb161d9b50b8 100644 --- a/tests/data/test572 +++ b/tests/data/test572 @@ -4,6 +4,7 @@ RTSP GET_PARAMETER +libtest @@ -64,9 +65,6 @@ Informational: Empty Options Response rtsp - -lib%TESTNUMBER - RTSP GET_PARAMETER (Put/Heartbeat/Post) diff --git a/tests/data/test573 b/tests/data/test573 index 24c674dd9340..715256ea6e82 100644 --- a/tests/data/test573 +++ b/tests/data/test573 @@ -5,6 +5,7 @@ HTTP multi flaky +libtest # Server-side @@ -31,9 +32,6 @@ Funny-head: yesyes http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - verify connect time with multi interface diff --git a/tests/data/test574 b/tests/data/test574 index 38dfb7ab800d..17430464ca4f 100644 --- a/tests/data/test574 +++ b/tests/data/test574 @@ -7,6 +7,7 @@ LIST RETR wildcardmatch ftplistparser +libtest @@ -18,9 +19,6 @@ ftplistparser ftp - -lib%TESTNUMBER - FTP wildcard download - changed fnmatch, 2x perform (Unix LIST) diff --git a/tests/data/test575 b/tests/data/test575 index 2f4cf3c6bbbb..c71afe6f1066 100644 --- a/tests/data/test575 +++ b/tests/data/test575 @@ -7,6 +7,7 @@ RETR multi wildcardmatch ftplistparser +libtest @@ -21,9 +22,6 @@ ftplistparser ftp - -lib%TESTNUMBER - FTP wildcard download - dup_handle and multi interface diff --git a/tests/data/test576 b/tests/data/test576 index 7ea01077656c..312d1e5a2414 100644 --- a/tests/data/test576 +++ b/tests/data/test576 @@ -5,6 +5,7 @@ FTP wildcardmatch ftplistparser +libtest @@ -19,9 +20,6 @@ ftplistparser ftp - -lib%TESTNUMBER - FTP wildcard - skip/parser_correctness/CURLOPT_FNMATCH_FUNCTION (Unix) diff --git a/tests/data/test577 b/tests/data/test577 index 5016e1b0d2c7..bc4937d17e52 100644 --- a/tests/data/test577 +++ b/tests/data/test577 @@ -4,6 +4,7 @@ RTSP OPTIONS +libtest diff --git a/tests/data/test578 b/tests/data/test578 index f3768cab1253..856090dcac38 100644 --- a/tests/data/test578 +++ b/tests/data/test578 @@ -4,6 +4,7 @@ HTTP HTTP POST +libtest # Server-side @@ -30,9 +31,6 @@ Funny-head: yesyes http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP POST lower than MAX_INITIAL_POST_SIZE with progress callback diff --git a/tests/data/test579 b/tests/data/test579 index 127223950064..cef657a4f61b 100644 --- a/tests/data/test579 +++ b/tests/data/test579 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP Digest auth +libtest # Server-side @@ -61,9 +62,6 @@ crypto digest # tool is what to use instead of 'curl' - -lib%TESTNUMBER - small chunked HTTP POSTs with digest auth and progress callback diff --git a/tests/data/test580 b/tests/data/test580 index da043e8125ce..cd4dd4a3ff44 100644 --- a/tests/data/test580 +++ b/tests/data/test580 @@ -6,6 +6,7 @@ HTTP HTTP GET multi Duplicate-header +libtest diff --git a/tests/data/test581 b/tests/data/test581 index 818ee7149e2e..0d92cf6579e7 100644 --- a/tests/data/test581 +++ b/tests/data/test581 @@ -6,6 +6,7 @@ HTTP HTTP GET multi Duplicate-header +libtest diff --git a/tests/data/test582 b/tests/data/test582 index ca533b8c4b6f..a29961ce3cbe 100644 --- a/tests/data/test582 +++ b/tests/data/test582 @@ -4,6 +4,7 @@ SFTP multi +libtest @@ -18,9 +19,6 @@ multi sftp - -lib%TESTNUMBER - SFTP upload using multi interface diff --git a/tests/data/test583 b/tests/data/test583 index 8428d31395c9..0b3817090aea 100644 --- a/tests/data/test583 +++ b/tests/data/test583 @@ -4,6 +4,7 @@ SFTP multi +libtest @@ -18,9 +19,6 @@ multi sftp - -lib%TESTNUMBER - SFTP with multi interface, remove handle early diff --git a/tests/data/test584 b/tests/data/test584 index 563481edb522..661a58cc88ab 100644 --- a/tests/data/test584 +++ b/tests/data/test584 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME +libtest diff --git a/tests/data/test585 b/tests/data/test585 index bd5c569a8afd..600f756a3060 100644 --- a/tests/data/test585 +++ b/tests/data/test585 @@ -7,6 +7,7 @@ HTTP GET multi CURLOPT_OPENSOCKETFUNCTION CURLOPT_CLOSESOCKETFUNCTION +libtest diff --git a/tests/data/test586 b/tests/data/test586 index d0da079c98cb..840f0ddb870e 100644 --- a/tests/data/test586 +++ b/tests/data/test586 @@ -5,6 +5,7 @@ FTP PASV CURLOPT_PORT +libtest # Server-side diff --git a/tests/data/test587 b/tests/data/test587 index 74dbfe3b8f43..a5c69b333d5f 100644 --- a/tests/data/test587 +++ b/tests/data/test587 @@ -6,6 +6,7 @@ HTTP HTTP POST FORM flaky +libtest diff --git a/tests/data/test588 b/tests/data/test588 index da0bceba7722..6286a4dd2cdf 100644 --- a/tests/data/test588 +++ b/tests/data/test588 @@ -10,6 +10,7 @@ PORT STOR multi EPRT refused +libtest diff --git a/tests/data/test589 b/tests/data/test589 index 949e5b6f719f..914a24719cac 100644 --- a/tests/data/test589 +++ b/tests/data/test589 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME +libtest @@ -29,9 +30,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - make an HTTP MIME POST set to NULL diff --git a/tests/data/test590 b/tests/data/test590 index 8d8400845e44..68e8ba6c2ab6 100644 --- a/tests/data/test590 +++ b/tests/data/test590 @@ -8,6 +8,7 @@ POST callback HTTP proxy HTTP proxy NTLM auth NTLM +libtest # Server-side @@ -71,9 +72,6 @@ Contents of that page you requested, sir. http # tool to use - -lib%TESTNUMBER - NTLM SSL diff --git a/tests/data/test591 b/tests/data/test591 index fbcd1b942632..b71fba4db624 100644 --- a/tests/data/test591 +++ b/tests/data/test591 @@ -11,6 +11,7 @@ STOR multi EPRT refused NODATACONN425 +libtest @@ -29,9 +30,6 @@ REPLY EPRT 500 we don't like EPRT now ftp - -lib%TESTNUMBER - FTP multi PORT and 425 on upload diff --git a/tests/data/test592 b/tests/data/test592 index 5ef624ae1bb7..77992ae45542 100644 --- a/tests/data/test592 +++ b/tests/data/test592 @@ -11,6 +11,7 @@ STOR multi EPRT refused NODATACONN421 +libtest diff --git a/tests/data/test593 b/tests/data/test593 index cb45d7b8d549..e75de1d6edf9 100644 --- a/tests/data/test593 +++ b/tests/data/test593 @@ -11,6 +11,7 @@ STOR multi EPRT refused NODATACONN150 +libtest diff --git a/tests/data/test594 b/tests/data/test594 index aaa007cbde8a..e59168dc828c 100644 --- a/tests/data/test594 +++ b/tests/data/test594 @@ -12,6 +12,7 @@ multi EPRT refused NODATACONN timeout +libtest diff --git a/tests/data/test595 b/tests/data/test595 index d29be62c76c5..775d6d75f9d8 100644 --- a/tests/data/test595 +++ b/tests/data/test595 @@ -4,6 +4,7 @@ FTP +libtest # Server-side diff --git a/tests/data/test596 b/tests/data/test596 index 4696aa900f56..e709716b48a3 100644 --- a/tests/data/test596 +++ b/tests/data/test596 @@ -4,6 +4,7 @@ FTP +libtest # Server-side diff --git a/tests/data/test597 b/tests/data/test597 index d65afa72214b..f3d33a118755 100644 --- a/tests/data/test597 +++ b/tests/data/test597 @@ -4,6 +4,7 @@ FTP CONNECT_ONLY +libtest @@ -12,9 +13,6 @@ CONNECT_ONLY ftp - -lib%TESTNUMBER - FTP connect only option diff --git a/tests/data/test598 b/tests/data/test598 index 73c5b82ece48..3c8df89e8bf6 100644 --- a/tests/data/test598 +++ b/tests/data/test598 @@ -8,6 +8,7 @@ HTTP set cookie cookies CURLOPT_REFERER curl_easy_reset +libtest # Server-side @@ -48,9 +49,6 @@ Content-Type: text/html http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_easy_reset with referer and other strings set diff --git a/tests/data/test599 b/tests/data/test599 index a8c883dc2d24..e7814d40e2a6 100644 --- a/tests/data/test599 +++ b/tests/data/test599 @@ -5,6 +5,7 @@ HTTP HTTP POST chunked Transfer-Encoding +libtest # Server-side @@ -62,9 +63,6 @@ this data is slightly larger than the first piece http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - # Bug 3510057 pointed out that when following a location to a larger chunked # encoded page, the first size would still be used in the progress callback diff --git a/tests/data/test643 b/tests/data/test643 index ad3d612e4d8f..f5a7d701832f 100644 --- a/tests/data/test643 +++ b/tests/data/test643 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest @@ -46,9 +47,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP multi-part mimepost using read callback for the file part diff --git a/tests/data/test645 b/tests/data/test645 index 7e48e20a877c..63f4d5218566 100644 --- a/tests/data/test645 +++ b/tests/data/test645 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest diff --git a/tests/data/test650 b/tests/data/test650 index 8e8fb28809a2..64bc85428f31 100644 --- a/tests/data/test650 +++ b/tests/data/test650 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest @@ -41,9 +42,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP formpost using form API - with redirect and re-POST diff --git a/tests/data/test651 b/tests/data/test651 index 49cf1aab9331..2e76fafa5200 100644 --- a/tests/data/test651 +++ b/tests/data/test651 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest @@ -30,9 +31,6 @@ form-api http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - curl_formadd with huge COPYCONTENTS diff --git a/tests/data/test652 b/tests/data/test652 index 5315ab4baaa7..bde5e36d5169 100644 --- a/tests/data/test652 +++ b/tests/data/test652 @@ -4,6 +4,7 @@ SMTP MIME +libtest @@ -19,9 +20,6 @@ Mime smtp - -lib%TESTNUMBER - SMTP with encoded huge mime data contents diff --git a/tests/data/test653 b/tests/data/test653 index 3674f4e07ba0..49c400f5a259 100644 --- a/tests/data/test653 +++ b/tests/data/test653 @@ -5,6 +5,7 @@ HTTP HTTP POST MIME +libtest @@ -46,9 +47,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Reuse of handle after altering mime data diff --git a/tests/data/test654 b/tests/data/test654 index 34e052836cbe..78810669a472 100644 --- a/tests/data/test654 +++ b/tests/data/test654 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest @@ -46,9 +47,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP duplicate easy handle with mime data diff --git a/tests/data/test655 b/tests/data/test655 index ce2a1726f76f..e956cd901f47 100644 --- a/tests/data/test655 +++ b/tests/data/test655 @@ -3,6 +3,7 @@ HTTP +libtest @@ -28,9 +29,6 @@ hello http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - resolver start callback diff --git a/tests/data/test657 b/tests/data/test657 new file mode 100644 index 000000000000..ec9e21514ccb --- /dev/null +++ b/tests/data/test657 @@ -0,0 +1,59 @@ + + + + +RTSP +OPTIONS +libtest + + + +# Server-side + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 1 +Public: DESCRIBE, OPTIONS, SETUP, TEARDOWN, PLAY, PAUSE +Content-Length: 18 + +No-More: sessions + + + +# Client-side + + +rtsp + + +lib567 + + +RTSP OPTIONS response with a body + + +rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +OPTIONS rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER RTSP/1.0 +CSeq: 1 +User-Agent: test567 +Test-Number: 567 + + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 1 +Public: DESCRIBE, OPTIONS, SETUP, TEARDOWN, PLAY, PAUSE +Content-Length: 18 + +No-More: sessions + + + diff --git a/tests/data/test658 b/tests/data/test658 index 2ec0e0661e40..17a82f0da860 100644 --- a/tests/data/test658 +++ b/tests/data/test658 @@ -5,6 +5,7 @@ HTTP HTTP GET CURLOPT_CURLU +libtest @@ -27,9 +28,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - Pass URL to libcurl with CURLOPT_CURLU diff --git a/tests/data/test659 b/tests/data/test659 index e16f2ad75372..222658f19353 100644 --- a/tests/data/test659 +++ b/tests/data/test659 @@ -6,6 +6,7 @@ HTTP HTTP GET CURLOPT_CURLU proxy +libtest @@ -28,9 +29,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - CURLOPT_CURLU without the path set - over proxy diff --git a/tests/data/test660 b/tests/data/test660 index 62ca998ec3bc..45685f7649b3 100644 --- a/tests/data/test660 +++ b/tests/data/test660 @@ -4,6 +4,7 @@ IMAP CONNECT_ONLY +libtest diff --git a/tests/data/test661 b/tests/data/test661 index c2e7e5cbc107..4673daeed724 100644 --- a/tests/data/test661 +++ b/tests/data/test661 @@ -3,6 +3,7 @@ FTP +libtest @@ -18,9 +19,6 @@ FTP ftp # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Avoid redundant CWDs diff --git a/tests/data/test666 b/tests/data/test666 index 2ed6ea60c6ca..1539a9bd174c 100644 --- a/tests/data/test666 +++ b/tests/data/test666 @@ -6,6 +6,7 @@ HTTP HTTP POST HTTP MIME POST MIME +libtest @@ -37,9 +38,6 @@ Mime http - -lib%TESTNUMBER - HTTP mime post with binary-encoded huge data contents diff --git a/tests/data/test667 b/tests/data/test667 index d1a4d41b9c58..cfd4b1a81676 100644 --- a/tests/data/test667 +++ b/tests/data/test667 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest @@ -39,9 +40,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP chunked mimepost using single-byte read callback with encoder diff --git a/tests/data/test668 b/tests/data/test668 index 288f8c134337..06b90c2208f2 100644 --- a/tests/data/test668 +++ b/tests/data/test668 @@ -5,6 +5,7 @@ HTTP HTTP POST HTTP MIME POST +libtest @@ -39,9 +40,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - HTTP mimepost early end of data detection diff --git a/tests/data/test670 b/tests/data/test670 index 77262b1049cd..8c30b294e75f 100644 --- a/tests/data/test670 +++ b/tests/data/test670 @@ -5,6 +5,7 @@ HTTP HTTP POST MIME +libtest @@ -39,9 +40,6 @@ Mime http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - Request pause from mime read callback: multi diff --git a/tests/data/test671 b/tests/data/test671 index 5d7e3ef6d010..af35b71b0a94 100644 --- a/tests/data/test671 +++ b/tests/data/test671 @@ -5,6 +5,7 @@ HTTP HTTP POST MIME +libtest diff --git a/tests/data/test672 b/tests/data/test672 index e89923e19e47..1e351374b752 100644 --- a/tests/data/test672 +++ b/tests/data/test672 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest diff --git a/tests/data/test673 b/tests/data/test673 index ed15cea6d894..c5435d4bc7cd 100644 --- a/tests/data/test673 +++ b/tests/data/test673 @@ -5,6 +5,7 @@ HTTP HTTP POST FORM +libtest diff --git a/tests/data/test674 b/tests/data/test674 index 745d8eaa38ed..ff331c31dfd7 100644 --- a/tests/data/test674 +++ b/tests/data/test674 @@ -6,6 +6,7 @@ HTTP HTTP GET CURLOPT_CURLU curl_easy_duphandle +libtest @@ -28,9 +29,6 @@ Funny-head: yesyes http - -lib%TESTNUMBER - Set CURLOPT_CURLU and dupe the handle diff --git a/tests/data/test676 b/tests/data/test676 index 0da221e68a07..95d27c9aa4b9 100644 --- a/tests/data/test676 +++ b/tests/data/test676 @@ -8,6 +8,7 @@ HTTP set cookie cookies CURLOPT_REFERER curl_easy_reset +libtest # Server-side @@ -51,9 +52,6 @@ Content-Type: text/html http # tool is what to use instead of 'curl' - -lib%TESTNUMBER - verify setting CURLOPT_COOKIEFILE to NULL again diff --git a/tests/data/test677 b/tests/data/test677 index ebff275c0253..24daaf17f8c6 100644 --- a/tests/data/test677 +++ b/tests/data/test677 @@ -4,6 +4,7 @@ IMAP CONNECT_ONLY +libtest @@ -21,9 +22,6 @@ imap IMAP with CONNECT_ONLY, custom command then exit - -lib%TESTNUMBER - imap://%HOSTIP:%IMAPPORT/677 diff --git a/tests/data/test678 b/tests/data/test678 index 8df67074404e..c692c4d7b9d2 100644 --- a/tests/data/test678 +++ b/tests/data/test678 @@ -5,6 +5,7 @@ HTTPS HTTP GET PEM certificate +libtest @@ -31,9 +32,6 @@ https test-localhost.pem HTTPS GET using CURLOPT_CAINFO_BLOB - -lib%TESTNUMBER - # provide URL and ca-cert https://localhost:%HTTPSPORT/%TESTNUMBER %CERTDIR/certs/test-ca.crt diff --git a/tests/data/test689 b/tests/data/test689 index 13f514bd74e2..27f8e0469e05 100644 --- a/tests/data/test689 +++ b/tests/data/test689 @@ -4,6 +4,7 @@ RTSP OPTIONS +libtest diff --git a/tests/data/test694 b/tests/data/test694 index bfc4181eba70..836e3926912f 100644 --- a/tests/data/test694 +++ b/tests/data/test694 @@ -8,6 +8,7 @@ POST callback HTTP proxy HTTP proxy NTLM auth NTLM +libtest # Server-side @@ -86,9 +87,6 @@ Contents of that second request. Differn. http # tool to use - -lib%TESTNUMBER - NTLM !SSPI diff --git a/tests/data/test695 b/tests/data/test695 index df6574473199..5418d4d633ca 100644 --- a/tests/data/test695 +++ b/tests/data/test695 @@ -3,6 +3,7 @@ MIME +libtest # Server-side @@ -25,9 +26,6 @@ Mime http - -lib%TESTNUMBER - MIME parts reuse as a child part diff --git a/tests/data/test696 b/tests/data/test696 index ce85a5f5b37d..4cbdf85757e7 100644 --- a/tests/data/test696 +++ b/tests/data/test696 @@ -4,6 +4,7 @@ HTTP HTTP GET +libtest diff --git a/tests/data/test750 b/tests/data/test750 index 03352ece66b9..ce7ac14b8006 100644 --- a/tests/data/test750 +++ b/tests/data/test750 @@ -49,10 +49,10 @@ Proxy-Connection: Keep-Alive -43 +8 -curl: (43) Invalid response header +curl: (8) Invalid response header diff --git a/tests/data/test751 b/tests/data/test751 index f642733c7f69..b1713a77ffdb 100644 --- a/tests/data/test751 +++ b/tests/data/test751 @@ -3,6 +3,7 @@ MULTI +libtest @@ -13,9 +14,6 @@ MULTI # Client-side # tool is what to use instead of 'curl' - -lib%TESTNUMBER - multi - add many easy handles diff --git a/tests/data/test753 b/tests/data/test753 index 868c7f2c4cf3..54e6f18fad8a 100644 --- a/tests/data/test753 +++ b/tests/data/test753 @@ -3,6 +3,7 @@ FTP +libtest @@ -15,9 +16,6 @@ ftp ftp - -lib%TESTNUMBER - cleanup easy without multi_remove_handle diff --git a/tests/data/test757 b/tests/data/test757 index 80d7706d61f7..c39392e4eade 100644 --- a/tests/data/test757 +++ b/tests/data/test757 @@ -3,6 +3,7 @@ MIME +libtest # Server-side @@ -25,9 +26,6 @@ Mime http - -lib%TESTNUMBER - MIME parts reused as a child part, using data_cb diff --git a/tests/data/test758 b/tests/data/test758 index 08f024fecd61..140c630e4664 100644 --- a/tests/data/test758 +++ b/tests/data/test758 @@ -4,6 +4,7 @@ multi HTTPS +libtest @@ -35,9 +36,6 @@ OpenSSL https - -lib%TESTNUMBER - HTTPS multi_socket interface transfer with callbacks returning error diff --git a/tests/data/test766 b/tests/data/test766 index 6b08c7226f27..c64e2d2ff4b0 100644 --- a/tests/data/test766 +++ b/tests/data/test766 @@ -6,6 +6,7 @@ FTP PORT CURLOPT_SOCKOPTFUNCTION timing-dependent +libtest @@ -21,9 +22,6 @@ hello ftp - -lib%TESTNUMBER - FTP PORT with sockopt callback refusing the accept diff --git a/tests/data/test767 b/tests/data/test767 index a0d96f1b6df2..28022381a1bd 100644 --- a/tests/data/test767 +++ b/tests/data/test767 @@ -49,7 +49,7 @@ Accept: */* -Allocations: 135 +Allocations: 141 Maximum allocated: 136000 diff --git a/tests/data/test792 b/tests/data/test792 index 1763ffc781d9..c817fdb42ff0 100644 --- a/tests/data/test792 +++ b/tests/data/test792 @@ -22,7 +22,7 @@ ftp proxy -.netrc with embedded NULL byte +.netrc with embedded NUL byte --netrc --netrc-file %LOGDIR/netrc%TESTNUMBER ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test793 b/tests/data/test793 index b293a8e3fbb7..9111ef90c3e2 100644 --- a/tests/data/test793 +++ b/tests/data/test793 @@ -22,7 +22,7 @@ ftp proxy -.netrc with embedded NULL byte, with quoted token +.netrc with embedded NUL byte, with quoted token --netrc --netrc-file %LOGDIR/netrc%TESTNUMBER ftp://%HOSTIP:%FTPPORT/%TESTNUMBER diff --git a/tests/data/test804 b/tests/data/test804 index d60f0e0d0fd2..275b448ad3ea 100644 --- a/tests/data/test804 +++ b/tests/data/test804 @@ -24,10 +24,11 @@ body imap -IMAP does not perform SELECT if reusing the same mailbox +IMAP selects a mailbox when its case changes -'imap://%HOSTIP:%IMAPPORT/%TESTNUMBER/;MAILINDEX=123/;SECTION=1' 'imap://%HOSTIP:%IMAPPORT/%TESTNUMBER/;MAILINDEX=456/;SECTION=2.3' -u user:secret +'imap://%HOSTIP:%IMAPPORT/Private/;MAILINDEX=123/;SECTION=1' +'imap://%HOSTIP:%IMAPPORT/private/;MAILINDEX=456/;SECTION=2.3' -u user:secret @@ -36,10 +37,11 @@ IMAP does not perform SELECT if reusing the same mailbox A001 CAPABILITY A002 LOGIN user secret -A003 SELECT %TESTNUMBER +A003 SELECT Private A004 FETCH 123 BODY[1] -A005 FETCH 456 BODY[2.3] -A006 LOGOUT +A005 SELECT private +A006 FETCH 456 BODY[2.3] +A007 LOGOUT diff --git a/tests/data/test970 b/tests/data/test970 index e936865d2de4..c579d96435d6 100644 --- a/tests/data/test970 +++ b/tests/data/test970 @@ -37,7 +37,7 @@ proxy CURL_TIME=13 CURL_DEBUG_SIZE=4019 -CURL_VERSION=curl-unit-test-fake-version +CURL_DEBUG_VERSION=curl-unit-test-fake-version HTTP GET with JSON output diff --git a/tests/data/test972 b/tests/data/test972 index 725da28019b5..731299f995bf 100644 --- a/tests/data/test972 +++ b/tests/data/test972 @@ -38,7 +38,7 @@ proxy CURL_TIME=13 CURL_DEBUG_SIZE=4019 -CURL_VERSION=curl-unit-test-fake-version +CURL_DEBUG_VERSION=curl-unit-test-fake-version Verify JSON output diff --git a/tests/directories.pm b/tests/directories.pm index cbb5921cd396..7df2291c3314 100644 --- a/tests/directories.pm +++ b/tests/directories.pm @@ -179,7 +179,29 @@ my %file_someothertext_txt = ( 'perm' => "rw-r--r--" ); +my %file_percent_txt = ( + 'name' => "some%2etxt", + 'content' => "percent in file name.\n", + 'time' => "Apr 17 11:01", + 'dostime' => "04-17-10 11:01AM", + 'perm' => "rw-r--r--" +); + +my %file_slash = ( + 'name' => "really/special", + 'content' => "slash in the name \n", + 'time' => "Jan 17 11:01", + 'dostime' => "01-17-10 11:01AM", + 'perm' => "rw-rw-rw-" +); + my %lists = ( + '/fully_simulated/percent/' => { + 'files' => [ \%file_someothertext_txt, \%file_percent_txt, + \%file_slash], + 'eol' => "\r\n", + 'type' => "unix" + }, '/fully_simulated/' => { 'files' => [ \%dir_dot, \%dir_ddot, \%dir_DOS, \%dir_UNIX ], 'eol' => "\r\n", diff --git a/tests/ftpserver.pl b/tests/ftpserver.pl index 23278e6de665..98c49f1cdea6 100755 --- a/tests/ftpserver.pl +++ b/tests/ftpserver.pl @@ -442,7 +442,7 @@ sub getreplydata { my $testpart = ""; $num =~ s/^([^0-9]*)//; - if($num > 10000) { + if($num && $num > 10000) { $testpart = $num % 10000; } @@ -2115,6 +2115,7 @@ sub LIST_ftp { my @ftpdir = ftp_contentlist($ftptargetdir); # old hard-coded style for(@ftpdir) { + logmsg "LIST: $_"; senddata $_; } } diff --git a/tests/http/Makefile.am b/tests/http/Makefile.am index 4f2fad7d02d7..40f564051c8f 100644 --- a/tests/http/Makefile.am +++ b/tests/http/Makefile.am @@ -68,6 +68,7 @@ EXTRA_DIST = \ test_20_websockets.py \ test_21_resolve.py \ test_22_httpsrr.py \ + test_23_ech.py \ test_30_vsftpd.py \ test_31_vsftpds.py \ test_32_ftps_vsftpd.py \ diff --git a/tests/http/conftest.py b/tests/http/conftest.py index 31a3d55b6487..ffa2792deede 100644 --- a/tests/http/conftest.py +++ b/tests/http/conftest.py @@ -1,4 +1,4 @@ -# *************************************************************************** +#*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | @@ -33,7 +33,17 @@ sys.path.append(os.path.join(os.path.dirname(__file__), ".")) -from testenv import Env, Httpd, Nghttpx, NghttpxFwd, NghttpxQuic, Sshd +from testenv import ( + Dante, + Dnsd, + Env, + Httpd, + Nghttpx, + NghttpxFwd, + NghttpxQuic, + NghttpxTcp, + Sshd, +) from testenv.h2o import H2oProxy, H2oServer log = logging.getLogger(__name__) @@ -57,6 +67,8 @@ def pytest_report_header(config): report.extend([f" h2o: {env.h2o_version()}"]) if env.has_caddy(): report.extend([f" Caddy: {env.caddy_version()}"]) + if env.has_sshd(): + report.extend([f" sshd: {env.sshd_version()}"]) if env.has_vsftpd(): report.extend([f" VsFTPD: {env.vsftpd_version()}"]) buildinfo_fn = os.path.join(env.build_dir, "buildinfo.txt") @@ -92,6 +104,25 @@ def env(pytestconfig, env_config) -> Env: return env +@pytest.fixture(scope='session') +def danted(env, httpd) -> Generator[Union[Dante, bool], None, None]: + if Env.has_danted(): + danted = Dante(env=env) + assert danted.initial_start() + yield danted + danted.stop() + else: + yield False + + +@pytest.fixture(scope='session', autouse=True) +def dnsd(env) -> Generator[Dnsd, None, None]: + dnsd = Dnsd(env=env) + assert dnsd.initial_start() + yield dnsd + dnsd.stop() + + @pytest.fixture(scope="session") def httpd(env) -> Generator[Httpd, None, None]: httpd = Httpd(env=env) @@ -115,6 +146,18 @@ def nghttpx(env, httpd) -> Generator[Union[Nghttpx, bool], None, None]: yield False +@pytest.fixture(scope="session") +def nghttpx_tcp(env, httpd) -> Generator[Union[Nghttpx, bool], None, None]: + nghttpx = NghttpxTcp(env=env) + if nghttpx.exists(): + nghttpx.clear_logs() + assert nghttpx.initial_start() + yield nghttpx + nghttpx.stop() + else: + yield False + + @pytest.fixture(scope="session") def nghttpx_fwd(env, httpd) -> Generator[Union[Nghttpx, bool], None, None]: nghttpx = NghttpxFwd(env=env) diff --git a/tests/http/requirements.txt b/tests/http/requirements.txt index cb577aaa4ed6..fd6d7624c078 100644 --- a/tests/http/requirements.txt +++ b/tests/http/requirements.txt @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: curl -cryptography==50.0.0 -filelock==3.32.4 +cryptography==50.0.1 +filelock==3.32.7 psutil==7.2.2 pytest==9.1.1 pytest-xdist==3.8.0 -websockets==17.0.1 +websockets==17.1 diff --git a/tests/http/scorecard.py b/tests/http/scorecard.py index 55497cb90f24..dc1f1c3a6cab 100755 --- a/tests/http/scorecard.py +++ b/tests/http/scorecard.py @@ -106,40 +106,40 @@ def fmt_reqs(cls, val): @classmethod def mk_mbs_cell(cls, samples, profiles, errors): - val = mean(samples) if len(samples) else -1 + val = mean(samples) if samples else -1 cell = { 'val': val, 'sval': Card.fmt_mbs(val) if val >= 0 else '--', } - if len(profiles): + if profiles: cell['stats'] = RunProfile.AverageStats(profiles) - if len(errors): + if errors: cell['errors'] = errors return cell @classmethod def mk_speed_cell(cls, samples, profiles, errors, limit): - val = mean(samples) if len(samples) else -1 + val = mean(samples) if samples else -1 cell = { 'val': val, 'sval': Card.fmt_speed_result(val, limit) if val >= 0 else '--', } - if len(profiles): + if profiles: cell['stats'] = RunProfile.AverageStats(profiles) - if len(errors): + if errors: cell['errors'] = errors return cell @classmethod def mk_reqs_cell(cls, samples, profiles, errors): - val = mean(samples) if len(samples) else -1 + val = mean(samples) if samples else -1 cell = { 'val': val, 'sval': Card.fmt_reqs(val) if val >= 0 else '--', } - if len(profiles): + if profiles: cell['stats'] = RunProfile.AverageStats(profiles) - if len(errors): + if errors: cell['errors'] = errors return cell @@ -224,7 +224,7 @@ def print_score_table(cls, score): if 'errors' in cell: errors.extend(cell['errors']) print() - if len(errors): + if errors: print(f'Errors: {errors}') @@ -314,9 +314,9 @@ def handshakes(self) -> Dict[str, Any]: else: errors.append(f'exit={r.exit_code}') props[authority][f'{ipv}-connect'] = mean(c_samples) \ - if len(c_samples) else -1 + if c_samples else -1 props[authority][f'{ipv}-handshake'] = mean(hs_samples) \ - if len(hs_samples) else -1 + if hs_samples else -1 props[authority][f'{ipv}-errors'] = errors self.info('ok.\n') return props @@ -652,7 +652,7 @@ def requests(self, count: int, meta: Dict[str, Any]) -> Dict[str, Any]: 'sval': Card.fmt_size(fsize) }, { 'val': count, - 'sval': f'{count}', + 'sval': f'{count}', } ] self.info('requests, max parallel...') diff --git a/tests/http/test_01_basic.py b/tests/http/test_01_basic.py index 3b7719fcce26..e89c1025d6df 100644 --- a/tests/http/test_01_basic.py +++ b/tests/http/test_01_basic.py @@ -213,7 +213,7 @@ def test_01_14_gigalarge_resp_headers(self, env: Env, httpd, configures_httpd, p f'/curltest/tweak?x-hd={256 * 1024}' r = curl.http_get(url=url, alpn_proto=proto, extra_args=[]) if proto == 'h2': - r.check_exit_code(16) # CURLE_HTTP2 + r.check_exit_code(56) # CURLE_RECV_ERROR else: r.check_exit_code(0) # 1.1 can do diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index 473502809b46..106094fd46c8 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -38,7 +38,8 @@ class TestDownload: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-0k", fsize=0) env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) @@ -317,10 +318,7 @@ def test_02_23a_lib_abort_paused(self, env: Env, httpd, nghttpx, proto): pytest.skip("fails in CI, but works locally for unknown reasons") count = 10 max_parallel = 5 - if proto in Env.http_mplx_protos(): - pause_offset = 64 * 1024 - else: - pause_offset = 12 * 1024 + pause_offset = 1024 * (64 if proto in Env.http_mplx_protos() else 12) docname = 'data-1m' url = f'https://localhost:{env.https_port}/{docname}' client = LocalClient(name='cli_hx_download', env=env) @@ -343,10 +341,7 @@ def test_02_23b_lib_abort_offset(self, env: Env, httpd, nghttpx, proto): pytest.skip("fails in CI, but works locally for unknown reasons") count = 10 max_parallel = 5 - if proto in Env.http_mplx_protos(): - abort_offset = 64 * 1024 - else: - abort_offset = 12 * 1024 + abort_offset = 1024 * (64 if proto in Env.http_mplx_protos() else 12) docname = 'data-1m' url = f'https://localhost:{env.https_port}/{docname}' client = LocalClient(name='cli_hx_download', env=env) @@ -369,10 +364,7 @@ def test_02_23c_lib_fail_offset(self, env: Env, httpd, nghttpx, proto): pytest.skip("fails in CI, but works locally for unknown reasons") count = 10 max_parallel = 5 - if proto in Env.http_mplx_protos(): - fail_offset = 64 * 1024 - else: - fail_offset = 12 * 1024 + fail_offset = 1024 * (64 if proto in Env.http_mplx_protos() else 12) docname = 'data-1m' url = f'https://localhost:{env.https_port}/{docname}' client = LocalClient(name='cli_hx_download', env=env) @@ -656,8 +648,8 @@ def test_02_34_max_total_conns(self, env: Env, httpd, nghttpx, proto, max_total_ assert n <= max_total_conns assert matched_lines > 0 - # 2 parallel transers, pause and resume. Load a 100 MB zip bomb from - # the server with "Content-Encoding: gzip" that gets exloded during + # 2 parallel transfers, pause and resume. Load a 100 MB zip bomb from + # the server with "Content-Encoding: gzip" that gets exploded during # response writing to the client. Client pauses after 1MB unzipped data # and causes buffers to fill while the server sends more response # data. @@ -716,3 +708,30 @@ def test_02_36_looong_urls(self, env: Env, httpd, nghttpx, proto, url_junk): f'got {r.exit_code}\n{r.dump_logs()}' if r.exit_code == 0: r.check_response(http_status=431) + + # download 2 http/1.1 resources, limited to a single connection max + @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") + def test_02_37_pending_timeout(self, env: Env, httpd): + # Run 2 http/1.1 transfers in parallel with a connection limit of 1 + # - xfer 0 has a delay response, making xfer 1 pending during its run + # - xfer 1 will timeout while pending, waiting on the connection + proto = 'http/1.1' + run_env = os.environ.copy() + run_env['CURL_DBG_MAX_TOTAL_CONNECTIONS'] = '1' + curl = CurlClient(env=env, run_env=run_env) + url1 = f'https://{env.authority_for(env.domain1, proto)}' \ + '/curltest/tweak/?&delay=3s' + url2 = f'https://{env.authority_for(env.domain1, proto)}/data.json' + r = curl.http_download(urls=[url1, url2], alpn_proto=proto, extra_args=[ + '--http1.1', '--parallel'], + url_options={ + url1: ['--max-time', '10'], + url2: ['--max-time', '1'] + }) + r.check_exit_code(28) + xfers = {stat['xfer_id']: stat for stat in r.stats} + assert xfers[0]['http_code'] == 200, f'{r.stats[0]}' # succeeded + assert xfers[1]['exitcode'] == 28, f'{r.stats[1]}' # timed out + m = re.search(r'timed out after (\d+) milliseconds', xfers[1]['errormsg']) + assert m, f'{r.stats[1]}' + assert int(m.group(1)) < 3000, f'{r.stats[1]}' diff --git a/tests/http/test_06_eyeballs.py b/tests/http/test_06_eyeballs.py index b72a89d75e4c..fb5f57493931 100644 --- a/tests/http/test_06_eyeballs.py +++ b/tests/http/test_06_eyeballs.py @@ -24,6 +24,8 @@ # import logging import re +import socket +import threading import pytest from testenv import CurlClient, Env @@ -68,6 +70,69 @@ def test_06_04_h3_fallback_h1(self, env: Env, httpd, nghttpx): r.check_response(count=1, http_status=200) assert r.stats[0]['http_version'] == '1.1' + # download using HTTP/3 on a server that silently drops UDP: fallback on + # h2 at the soft timeout, since QUIC never sees any data + @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") + @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") + def test_06_05_h3_silent_fallback_h2(self, env: Env, httpd, nghttpx): + curl = CurlClient(env=env) + urln = f'https://{env.domain1}:{env.https_only_tcp_port}/data.json' + # take the UDP port without reading it, so that packets are not refused + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.bind(('127.0.0.1', env.https_only_tcp_port)) + r = curl.http_download(urls=[urln], extra_args=[ + '--http3', '--happy-eyeballs-timeout-ms', '200', + '--trace-config', 'https-connect' + ]) + r.check_response(count=1, http_status=200) + assert r.stats[0]['http_version'] == '2' + soft_timeouts = [line for line in r.trace_lines + if re.match(r'.*h3 has not seen any data after 100ms', line)] + assert len(soft_timeouts) == 1, f'{r.dump_logs()}' + + # download using HTTP/3 on a server that answers UDP with junk: QUIC has + # seen data, fallback on h2 at the hard timeout + @pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support") + @pytest.mark.skipif(condition=not Env.curl_uses_lib('ngtcp2'), reason="requires ngtcp2 packet handling") + @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs curl verbose strings") + def test_06_06_h3_stalled_fallback_h2(self, env: Env, httpd, nghttpx): + curl = CurlClient(env=env) + urln = f'https://{env.domain1}:{env.https_only_tcp_port}/data.json' + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.bind(('127.0.0.1', env.https_only_tcp_port)) + sock.settimeout(0.1) + done = threading.Event() + + def answer_junk(): + while not done.is_set(): + try: + _, addr = sock.recvfrom(65536) + sock.sendto(b'\0', addr) + except socket.timeout: + pass + + t = threading.Thread(target=answer_junk) + t.start() + try: + r = curl.http_download(urls=[urln], extra_args=[ + '--http3', '--happy-eyeballs-timeout-ms', '200', + '--trace-config', 'https-connect,timer' + ]) + finally: + done.set() + t.join() + r.check_response(count=1, http_status=200) + assert r.stats[0]['http_version'] == '2' + hard_timeouts = [line for line in r.trace_lines + if re.match(r'.*h3 inconclusive after 200', line)] + assert len(hard_timeouts) == 1, f'{r.dump_logs()}' + # the timer is set again for the hard timeout, QUIC does not wake us + # up before its retransmission after about one second + he_timers_set = [line for line in r.trace_lines + if re.match(r'.*\[TIMER] \[ALPN_EYEBALLS] set for', line)] + assert len(he_timers_set) >= 2, f'{r.dump_logs()}' + assert r.stats[0]['time_connect'] < 0.8, f'{r.stats[0]}' + # make a successful https: transfer and observer the timer stats def test_06_10_stats_success(self, env: Env, httpd, nghttpx): curl = CurlClient(env=env) @@ -133,7 +198,7 @@ def test_06_13_timers(self, env: Env): # timeouts being set here failed_attempts = [line for line in r.trace_lines if re.match(r'.*checked connect attempts: 0 ongoing', line)] - if len(failed_attempts): + if failed_attempts: # github CI fails right away with "Network is unreachable", slackers... assert len(failed_attempts) == 3, f'found: {"".join(failed_attempts)}\n{r.dump_logs()}' else: diff --git a/tests/http/test_07_upload.py b/tests/http/test_07_upload.py index 8187e0a99bdd..13da59260530 100644 --- a/tests/http/test_07_upload.py +++ b/tests/http/test_07_upload.py @@ -40,7 +40,8 @@ class TestUpload: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx): + @classmethod + def _class_scope(cls, env, httpd, nghttpx): env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-63k", fsize=63 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-64k", fsize=64 * 1024) diff --git a/tests/http/test_08_caddy.py b/tests/http/test_08_caddy.py index 467f1cc8308c..09a0be64de60 100644 --- a/tests/http/test_08_caddy.py +++ b/tests/http/test_08_caddy.py @@ -40,13 +40,15 @@ class TestCaddy: @pytest.fixture(autouse=True, scope='class') - def caddy(self, env): + @classmethod + def caddy(cls, env): caddy = Caddy(env=env) assert caddy.initial_start() yield caddy caddy.stop() - def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): + @classmethod + def _make_docs_file(cls, docs_dir: str, fname: str, fsize: int): fpath = os.path.join(docs_dir, fname) data1k = 1024*'x' flen = 0 @@ -57,12 +59,13 @@ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): return flen @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, caddy): - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10k.data', fsize=10 * 1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data1.data', fsize=1024 * 1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data5.data', fsize=5 * 1024 * 1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10.data', fsize=10 * 1024 * 1024) - self._make_docs_file(docs_dir=caddy.docs_dir, fname='data100.data', fsize=100 * 1024 * 1024) + @classmethod + def _class_scope(cls, env, caddy): + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data10k.data', fsize=10 * 1024) + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data1.data', fsize=1024 * 1024) + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data5.data', fsize=5 * 1024 * 1024) + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data10.data', fsize=10 * 1024 * 1024) + cls._make_docs_file(docs_dir=caddy.docs_dir, fname='data100.data', fsize=100 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) # download 1 file diff --git a/tests/http/test_09_push.py b/tests/http/test_09_push.py index 7fb37b679df3..a77e874a38f4 100644 --- a/tests/http/test_09_push.py +++ b/tests/http/test_09_push.py @@ -34,7 +34,8 @@ class TestPush: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): push_dir = os.path.join(httpd.docs_dir, 'push') if not os.path.exists(push_dir): os.makedirs(push_dir) @@ -49,6 +50,7 @@ def httpd_configure(self, env, httpd): ' H2PushResource /push/data2', '', '', + ' Header set Location "/push/data3"', ' H2PushResource /push/data1', ' H2PushResource /push/data3', '', @@ -82,3 +84,5 @@ def test_09_02_h2_push(self, env: Env, httpd, configures_httpd): r.check_exit_code(0) assert os.path.exists(client.download_file(0)) assert os.path.exists(os.path.join(client.run_dir, 'push0')), r.dump_logs() + redirect_url = f'https://localhost:{env.https_port}/push/data3' + assert f'**** push redirect URL: {redirect_url}' in r.stderr diff --git a/tests/http/test_10_proxy.py b/tests/http/test_10_proxy.py index bd07841a68c0..da02cb08f3c6 100644 --- a/tests/http/test_10_proxy.py +++ b/tests/http/test_10_proxy.py @@ -37,7 +37,8 @@ class TestProxy: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx_fwd): + @classmethod + def _class_scope(cls, env, httpd, nghttpx_fwd): push_dir = os.path.join(httpd.docs_dir, 'push') if not os.path.exists(push_dir): os.makedirs(push_dir) diff --git a/tests/http/test_11_unix.py b/tests/http/test_11_unix.py index 9321d7141402..8b6d7877bc15 100644 --- a/tests/http/test_11_unix.py +++ b/tests/http/test_11_unix.py @@ -80,14 +80,15 @@ def _process(self): finally: c.close() - except (ConnectionAbortedError, OSError): + except OSError: self._done = True class TestUnix: - @pytest.fixture(scope="class") - def uds_faker(self, env: Env) -> Generator[UDSFaker, None, None]: + @pytest.fixture(scope='class') + @classmethod + def uds_faker(cls, env: Env) -> Generator[UDSFaker, None, None]: uds_path = os.path.join(env.gen_dir, 'uds_11.sock') faker = UDSFaker(path=uds_path) faker.start() diff --git a/tests/http/test_14_auth.py b/tests/http/test_14_auth.py index ab941b6efa2d..0e0e1311cd34 100644 --- a/tests/http/test_14_auth.py +++ b/tests/http/test_14_auth.py @@ -35,7 +35,8 @@ class TestAuth: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx): + @classmethod + def _class_scope(cls, env, httpd, nghttpx): env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) # download 1 file, not authenticated diff --git a/tests/http/test_16_info.py b/tests/http/test_16_info.py index 0d184931e208..a7160badc7f0 100644 --- a/tests/http/test_16_info.py +++ b/tests/http/test_16_info.py @@ -34,7 +34,8 @@ class TestInfo: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) diff --git a/tests/http/test_17_ssl_use.py b/tests/http/test_17_ssl_use.py index 70af06eff9a0..85f7909a8af6 100644 --- a/tests/http/test_17_ssl_use.py +++ b/tests/http/test_17_ssl_use.py @@ -64,7 +64,8 @@ class TLSDefs: class TestSSLUse: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx): + @classmethod + def _class_scope(cls, env, httpd, nghttpx): env.make_data_file(indir=httpd.docs_dir, fname="data-10k", fsize=10 * 1024) def test_17_01_sslinfo_plain(self, env: Env, httpd): @@ -231,8 +232,8 @@ def gen_test_17_07_list(): 'TLSv1.2': 'TLSv1.2'}.items(): for [cid13, ciphers13, succeed13] in tls13_tests: for [cid12, ciphers12, succeed12] in tls12_tests: - id = f'{tls_id}-{cid13}-{cid12}' - ret.append(pytest.param(tls_proto, ciphers13, ciphers12, succeed13, succeed12, id=id)) + testid = f'{tls_id}-{cid13}-{cid12}' + ret.append(pytest.param(tls_proto, ciphers13, ciphers12, succeed13, succeed12, id=testid)) return ret @pytest.mark.parametrize( @@ -598,3 +599,29 @@ def test_17_22_capath_invalid(self, env: Env, httpd): ]) # CURLE_PEER_FAILED_VERIFICATION or CURLE_SSL_CACERT_BADFILE assert r.exit_code in [60, 77], f'{r.dump_logs()}' + + @pytest.mark.parametrize("proto", Env.http_protos()) + def test_17_23_embedded_zero(self, env: Env, proto, httpd, nghttpx): + httpd.set_domain1_cred_name('embedded-zero') + httpd.reload_if_config_changed() + if proto == 'h3': + nghttpx.set_cred_name('embedded-zero') + nghttpx.reload_if_config_changed() + curl = CurlClient(env=env) + url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo' + r = curl.http_get(url=url, alpn_proto=proto) + # CURLE_SSL_CONNECT_ERROR or CURLE_PEER_FAILED_VERIFICATION + assert r.exit_code in [35, 60], f'{r.dump_logs()}' + + @pytest.mark.parametrize("proto", Env.http_protos()) + def test_17_24_embedded_zero_wildcard(self, env: Env, proto, httpd, nghttpx): + httpd.set_domain1_cred_name('embedded-zero-wildcard') + httpd.reload_if_config_changed() + if proto == 'h3': + nghttpx.set_cred_name('embedded-zero-wildcard') + nghttpx.reload_if_config_changed() + curl = CurlClient(env=env) + url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo' + r = curl.http_get(url=url, alpn_proto=proto) + # CURLE_SSL_CONNECT_ERROR or CURLE_PEER_FAILED_VERIFICATION + assert r.exit_code in [35, 60], f'{r.dump_logs()}' diff --git a/tests/http/test_18_methods.py b/tests/http/test_18_methods.py index 452b422c6b06..fc69def62bfc 100644 --- a/tests/http/test_18_methods.py +++ b/tests/http/test_18_methods.py @@ -33,7 +33,8 @@ class TestMethods: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd, nghttpx): + @classmethod + def _class_scope(cls, env, httpd, nghttpx): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) diff --git a/tests/http/test_19_shutdown.py b/tests/http/test_19_shutdown.py index 388d3955dabe..e4fdea648bfc 100644 --- a/tests/http/test_19_shutdown.py +++ b/tests/http/test_19_shutdown.py @@ -35,7 +35,8 @@ class TestShutdown: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=indir, fname="data-100k", fsize=100 * 1024) diff --git a/tests/http/test_20_websockets.py b/tests/http/test_20_websockets.py index c859279c0ee6..a0f32585d82e 100644 --- a/tests/http/test_20_websockets.py +++ b/tests/http/test_20_websockets.py @@ -111,7 +111,8 @@ def shutdown(self): class TestWebsockets: @pytest.fixture(autouse=True, scope='class') - def ws_echo(self, env): + @classmethod + def ws_echo(cls, env): cmd = os.path.join(env.project_dir, 'tests/http/testenv/ws_echo_server.py') server = WsServer('ws_echo', env, cmd) @@ -120,7 +121,8 @@ def ws_echo(self, env): server.shutdown() @pytest.fixture(autouse=True, scope='class') - def ws_4frames(self, env): + @classmethod + def ws_4frames(cls, env): cmd = os.path.join(env.project_dir, 'tests/http/testenv/ws_4frames_server.py') server = WsServer('ws_4frames', env, cmd) @@ -273,12 +275,11 @@ def srv(): hashlib.sha1(k + b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest() ).decode() c.sendall( - ( - "HTTP/1.1 101 Switching Protocols\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - f"Sec-WebSocket-Accept: {a}\r\n\r\n" - ).encode() + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + f"Sec-WebSocket-Accept: {a}\r\n\r\n" + .encode() ) f = b"\x89\x00" * 65536 # PING frames, many @@ -326,7 +327,7 @@ def srv(): assert r.exit_code in [55, 56], f'{r.dump_logs()}' # SEND/RECV_ERROR assert r.profile, f'{r}' rss2 = r.profile.stats['rss-max'] / (1024 * 1024) - assert (rss1 * 1.2) > rss2, 'bad memory increase' + assert (rss1 * 1.23) > rss2, 'bad memory increase' # test small frames delivery when pausing def test_20_12_pause_frames_small(self, env: Env, ws_4frames): diff --git a/tests/http/test_21_resolve.py b/tests/http/test_21_resolve.py index 28634f14614e..9e9ea4bfd185 100644 --- a/tests/http/test_21_resolve.py +++ b/tests/http/test_21_resolve.py @@ -26,10 +26,9 @@ import os import re from datetime import timedelta -from typing import Generator import pytest -from testenv import CurlClient, Dnsd, Env, LocalClient +from testenv import CurlClient, Env, LocalClient log = logging.getLogger(__name__) @@ -38,15 +37,9 @@ @pytest.mark.skipif(condition=not Env.curl_has_feature('AsynchDNS'), reason="needs AsynchDNS") class TestResolve: - @pytest.fixture(scope='class') - def dnsd(self, env: Env) -> Generator[Dnsd, None, None]: - dnsd = Dnsd(env=env) - assert dnsd.initial_start() - yield dnsd - dnsd.stop() - @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-0k", fsize=0) diff --git a/tests/http/test_22_httpsrr.py b/tests/http/test_22_httpsrr.py index 63bb2c029ed9..840bbfc48fb7 100644 --- a/tests/http/test_22_httpsrr.py +++ b/tests/http/test_22_httpsrr.py @@ -37,7 +37,8 @@ class TestHTTPSRR: @pytest.fixture(scope='class') - def dnsd(self, env: Env) -> Generator[Dnsd, None, None]: + @classmethod + def dnsd(cls, env: Env) -> Generator[Dnsd, None, None]: dnsd = Dnsd(env=env) assert dnsd.initial_start() yield dnsd diff --git a/tests/http/test_23_ech.py b/tests/http/test_23_ech.py new file mode 100644 index 000000000000..c436f069ea7c --- /dev/null +++ b/tests/http/test_23_ech.py @@ -0,0 +1,178 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , 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 +# +########################################################################### +# +import logging +import os +import re + +import pytest +from testenv import CurlClient, Env + +log = logging.getLogger(__name__) + + +@pytest.mark.skipif(condition=not Env.curl_has_feature('ECH'), reason="no curl ECH support") +@pytest.mark.skipif(condition=not Env.have_nghttpx_ech(), reason="no nghttpx ECH support") +@pytest.mark.skipif(condition=not Env.have_openssl_ech(), reason="no openssl cmd ECH support") +@pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") +class TestECH: + + @pytest.mark.parametrize("ech_mode, exp_result, exp_exit", [ + ['false', None, 0], + ['grease', 'sent GREASE', 0], + ['true', 'not configured', 0], + ['hard', None, 35] # CURLE_SSL_CONNECT_ERROR + ]) + def test_23_01_ech_no_config(self, env: Env, httpd, nghttpx_tcp, ech_mode, exp_result, exp_exit): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url = f'https://{env.domain1}:{nghttpx_tcp.port}/data.json' + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--ech', ech_mode + ]) + r.check_exit_code(exp_exit) + if exp_exit == 0: + r.check_stats(count=1, http_status=200, exitcode=0) + if env.curl_uses_lib('openssl'): + ech_result, _, _ = self._get_ech_result(r) + assert ech_result == exp_result, f'{r.dump_logs()}' + + @pytest.mark.parametrize("ech_mode, exp_result, exp_exit", [ + ['false', None, 0], + ['grease', 'sent GREASE', 0], + ['true', 'succeeded', 0], + ['hard', 'succeeded', 0] + ]) + def test_23_02_ech_good_config(self, env: Env, httpd, nghttpx_tcp, ech_mode, exp_result, exp_exit): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url = f'https://{env.domain1}:{nghttpx_tcp.port}/data.json' + ech_config = env.get_echconfig_arg(env.domain1) + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--ech', ech_mode, '--ech', f'ecl:{ech_config}' + ]) + r.check_exit_code(exp_exit) + if exp_exit == 0: + r.check_stats(count=1, http_status=200, exitcode=0) + if env.curl_uses_lib('openssl'): + ech_result, inner, outer = self._get_ech_result(r) + assert ech_result == exp_result, f'{r.dump_logs()}' + if ech_result == 'succeeded': + assert inner == env.domain1, f'{r.dump_logs()}' + assert outer == f'public.{env.domain1}', f'{r.dump_logs()}' + + @pytest.mark.parametrize("ech_mode, exp_result, exp_exit", [ + ['false', None, 0], + ['grease', 'sent GREASE', 0], + ['true', 'rejected', 101], # CURLE_ECH_REQUIRED + ['hard', 'rejected', 101] + ]) + def test_23_03_ech_bad_config(self, env: Env, httpd, nghttpx_tcp, ech_mode, exp_result, exp_exit): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url = f'https://{env.domain1}:{nghttpx_tcp.port}/data.json' + ech_config = env.get_echconfig_arg(env.domain2) + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--ech', ech_mode, '--ech', f'ecl:{ech_config}' + ]) + if env.curl_uses_lib('wolfssl'): + # wolfssl ECH errors result in CURLE_PEER_FAILED_VERIFICATION + exp_exit = 60 if exp_exit else 0 + elif env.curl_uses_lib('rustls-ffi'): + # rustls ECH errors result in CURLE_SSL_CONNECT_ERROR + exp_exit = 35 if exp_exit else 0 + r.check_exit_code(exp_exit) + if env.curl_uses_lib('openssl'): + ech_result, _, _ = self._get_ech_result(r) + assert ech_result == exp_result, f'{r.dump_logs()}' + + @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), + reason="needs OpenSSL") + @pytest.mark.parametrize("ech_mode, exp_result, pub_domain", [ + ['true', 'succeeded', 'innocent.invalid'], + ['hard', 'succeeded', 'innocent.invalid'], + ]) + def test_23_04_ech_outer(self, env: Env, httpd, nghttpx_tcp, ech_mode, exp_result, pub_domain): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url = f'https://{env.domain1}:{nghttpx_tcp.port}/data.json' + ech_config = env.get_echconfig_arg(env.domain1) + r = curl.http_download(urls=[url], with_stats=True, extra_args=[ + '--ech', ech_mode, '--ech', f'ecl:{ech_config}', '--ech', f'pn:{pub_domain}' + ]) + r.check_exit_code(0) + r.check_stats(count=1, http_status=200, exitcode=0) + ech_result, inner, outer = self._get_ech_result(r) + assert ech_result == exp_result, f'{r.dump_logs()}' + assert inner == env.domain1, f'{r.dump_logs()}' + assert outer == pub_domain, f'{r.dump_logs()}' + + def test_23_05_ech_conn_ech_reuse(self, env: Env, httpd, nghttpx_tcp): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url1 = f'https://{env.domain1}:{nghttpx_tcp.port}/data1.json' + url2 = f'https://{env.domain1}:{nghttpx_tcp.port}/data2.json' + r = curl.http_download(urls=[url1, url2], with_stats=True, url_options={ + url1: ['--ech', 'false'], + url2: ['--ech', 'true'] + }) + r.check_exit_code(0), f'{r}' + r.check_response(http_status=404, count=2, connect_count=2) + + def test_23_06_ech_conn_ech_config_reuse(self, env: Env, httpd, nghttpx_tcp): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url1 = f'https://{env.domain1}:{nghttpx_tcp.port}/data1.json' + url2 = f'https://{env.domain1}:{nghttpx_tcp.port}/data2.json' + ech_config = env.get_echconfig_arg(env.domain1) + r = curl.http_download(urls=[url1, url2], with_stats=True, url_options={ + url1: ['--ech', 'true'], + url2: ['--ech', 'true', '--ech', f'ecl:{ech_config}'] + }) + r.check_exit_code(0), f'{r}' + r.check_response(http_status=404, count=2, connect_count=2) + + @pytest.mark.skipif(condition=not Env.curl_uses_lib('openssl'), + reason="needs OpenSSL") + def test_23_07_ech_conn_ech_pub_reuse(self, env: Env, httpd, nghttpx_tcp): + run_env = os.environ.copy() + curl = CurlClient(env=env, run_env=run_env) + url1 = f'https://{env.domain1}:{nghttpx_tcp.port}/data1.json' + url2 = f'https://{env.domain1}:{nghttpx_tcp.port}/data2.json' + r = curl.http_download(urls=[url1, url2], with_stats=True, url_options={ + url1: ['--ech', 'true'], + url2: ['--ech', 'true', '--ech', 'pn:innocent.invalid'] + }) + r.check_exit_code(0), f'{r}' + r.check_response(http_status=404, count=2, connect_count=2) + + def _get_ech_result(self, r): + for line in r.trace_lines: + m = re.search(r'ECH: result \'(.+)\' \(inner=(.+), outer=(.+)\)', line) + if m: + return m.group(1), m.group(2), m.group(3) + m = re.search(r'ECH: rejected(.*)', line) + if m: + return 'rejected', None, None + return None, None, None diff --git a/tests/http/test_30_vsftpd.py b/tests/http/test_30_vsftpd.py index 860c38d5d3f4..756457fbca84 100644 --- a/tests/http/test_30_vsftpd.py +++ b/tests/http/test_30_vsftpd.py @@ -38,33 +38,38 @@ class TestVsFTPD: @pytest.fixture(autouse=True, scope='class') - def vsftpd(self, env): + @classmethod + def vsftpd(cls, env): vsftpd = VsFTPD(env=env) assert vsftpd.initial_start() yield vsftpd vsftpd.stop() - def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): + @classmethod + def _make_docs_file(cls, docs_dir: str, fname: str, fsize: int): fpath = os.path.join(docs_dir, fname) data1k = 1024*'x' flen = 0 with open(fpath, 'w') as fd: + fd.write(f'{fname}--->\n') while flen < fsize: fd.write(data1k) flen += len(data1k) + fd.write(f'<---{fname}\n') return flen @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, vsftpd): + @classmethod + def _class_scope(cls, env, vsftpd): if os.path.exists(vsftpd.docs_dir): shutil.rmtree(vsftpd.docs_dir) if not os.path.exists(vsftpd.docs_dir): os.makedirs(vsftpd.docs_dir) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-0k', fsize=0) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1k', fsize=1024) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10k', fsize=10 * 1024) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1m', fsize=1024 * 1024) - self._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-0k', fsize=0) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1k', fsize=1024) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10k', fsize=10 * 1024) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-1m', fsize=1024 * 1024) + cls._make_docs_file(docs_dir=vsftpd.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-0k", fsize=0) env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024) env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100 * 1024) @@ -244,20 +249,40 @@ def test_30_12_upload_eprt(self, env: Env, vsftpd: VsFTPD): dstfile = os.path.join(vsftpd.docs_dir, docname) assert os.path.exists(dstfile), f'{r.dump_logs()}' + @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") + def test_30_13_wildcard(self, env: Env, vsftpd: VsFTPD): + run_env = os.environ.copy() + run_env['CURL_DBG_FTP_WILDCARD'] = '1' + curl = CurlClient(env=env, run_env=run_env) + url = f'ftp://{env.ftp_domain}:{vsftpd.port}/data*0k' + r = curl.ftp_get(urls=[url], with_stats=True) + r.check_stats(count=1, http_status=0) + reffile = os.path.join(env.gen_dir, 'test-30-13.reference') + with open(reffile, "w") as fd: + with open(os.path.join(vsftpd.docs_dir, 'data-0k')) as fd2: + fd.writelines(fd2.readlines()) + with open(os.path.join(vsftpd.docs_dir, 'data-10k')) as fd2: + fd.writelines(fd2.readlines()) + dfile = os.path.join(curl.run_dir, 'download_#1.data') + self.check_download(reffile, dfile) + def check_downloads(self, client, srcfile: str, count: int, complete: bool = True): for i in range(count): dfile = client.download_file(i) - assert os.path.exists(dfile) - if complete and not filecmp.cmp(srcfile, dfile, shallow=False): - with open(srcfile) as fa, open(dfile) as fb: - a = fa.readlines() - b = fb.readlines() - diff = "".join(difflib.unified_diff(a=a, b=b, - fromfile=srcfile, - tofile=dfile, - n=1)) - assert False, f'download {dfile} differs:\n{diff}' + self.check_download(srcfile, dfile, complete) + + def check_download(self, reffile: str, dfile: str, complete: bool = True): + assert os.path.exists(dfile) + if complete and not filecmp.cmp(reffile, dfile, shallow=False): + with open(reffile) as fa, open(dfile) as fb: + a = fa.readlines() + b = fb.readlines() + diff = "".join(difflib.unified_diff(a=a, b=b, + fromfile=reffile, + tofile=dfile, + n=1)) + assert False, f'download {dfile} differs:\n{diff}' def check_upload(self, env, vsftpd: VsFTPD, docname, binary=True): srcfile = os.path.join(env.gen_dir, docname) diff --git a/tests/http/test_31_vsftpds.py b/tests/http/test_31_vsftpds.py index debb8c9b5bc1..c3032e4924c3 100644 --- a/tests/http/test_31_vsftpds.py +++ b/tests/http/test_31_vsftpds.py @@ -44,7 +44,8 @@ class TestVsFTPD: SUPPORTS_SSL = True @pytest.fixture(autouse=True, scope='class') - def vsftpds(self, env): + @classmethod + def vsftpds(cls, env): if not TestVsFTPD.SUPPORTS_SSL: pytest.skip('vsftpd does not seem to support SSL') vsftpds = VsFTPD(env=env, with_ssl=True) @@ -55,7 +56,8 @@ def vsftpds(self, env): yield vsftpds vsftpds.stop() - def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): + @classmethod + def _make_docs_file(cls, docs_dir: str, fname: str, fsize: int): fpath = os.path.join(docs_dir, fname) data1k = 1024*'x' flen = 0 @@ -66,15 +68,16 @@ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): return flen @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, vsftpds): + @classmethod + def _class_scope(cls, env, vsftpds): if os.path.exists(vsftpds.docs_dir): shutil.rmtree(vsftpds.docs_dir) if not os.path.exists(vsftpds.docs_dir): os.makedirs(vsftpds.docs_dir) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024) env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024 * 1024) @@ -262,7 +265,7 @@ def test_31_10_upload_stdin(self, env: Env, vsftpds: VsFTPD, indata): assert os.path.exists(dstfile) with open(dstfile) as fd: destdata = fd.readlines() - expdata = [indata] if len(indata) else [] + expdata = [indata] if indata else [] assert expdata == destdata, f'expected: {expdata}, got: {destdata}' def test_31_11_download_non_existing(self, env: Env, vsftpds: VsFTPD): diff --git a/tests/http/test_32_ftps_vsftpd.py b/tests/http/test_32_ftps_vsftpd.py index 6db7fb1eaa0f..9301b5265ade 100644 --- a/tests/http/test_32_ftps_vsftpd.py +++ b/tests/http/test_32_ftps_vsftpd.py @@ -44,7 +44,8 @@ class TestFtpsVsFTPD: SUPPORTS_SSL = True @pytest.fixture(autouse=True, scope='class') - def vsftpds(self, env): + @classmethod + def vsftpds(cls, env): if not TestFtpsVsFTPD.SUPPORTS_SSL: pytest.skip('vsftpd does not seem to support SSL') vsftpds = VsFTPD(env=env, with_ssl=True, ssl_implicit=True) @@ -55,7 +56,8 @@ def vsftpds(self, env): yield vsftpds vsftpds.stop() - def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): + @classmethod + def _make_docs_file(cls, docs_dir: str, fname: str, fsize: int): fpath = os.path.join(docs_dir, fname) data1k = 1024*'x' flen = 0 @@ -66,15 +68,16 @@ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): return flen @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, vsftpds): + @classmethod + def _class_scope(cls, env, vsftpds): if os.path.exists(vsftpds.docs_dir): shutil.rmtree(vsftpds.docs_dir) if not os.path.exists(vsftpds.docs_dir): os.makedirs(vsftpds.docs_dir) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) - self._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1k', fsize=1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10k', fsize=10 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-1m', fsize=1024 * 1024) + cls._make_docs_file(docs_dir=vsftpds.docs_dir, fname='data-10m', fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1k", fsize=1024) env.make_data_file(indir=env.gen_dir, fname="upload-100k", fsize=100 * 1024) env.make_data_file(indir=env.gen_dir, fname="upload-1m", fsize=1024 * 1024) @@ -272,7 +275,7 @@ def test_32_10_upload_stdin(self, env: Env, vsftpds: VsFTPD, indata): assert os.path.exists(dstfile) with open(dstfile) as fd: destdata = fd.readlines() - expdata = [indata] if len(indata) else [] + expdata = [indata] if indata else [] assert expdata == destdata, f'expected: {expdata}, got: {destdata}' def test_32_11_download_non_existing(self, env: Env, vsftpds: VsFTPD): diff --git a/tests/http/test_40_socks.py b/tests/http/test_40_socks.py index 186b75e43072..d29e03314367 100644 --- a/tests/http/test_40_socks.py +++ b/tests/http/test_40_socks.py @@ -24,8 +24,6 @@ # import logging import os -import time -from typing import Generator import pytest from testenv import CurlClient, Dante, Env @@ -36,16 +34,9 @@ @pytest.mark.skipif(condition=not Env.has_danted(), reason="missing danted") class TestSocks: - @pytest.fixture(scope='class') - def danted(self, env: Env) -> Generator[Dante, None, None]: - danted = Dante(env=env) - assert danted.initial_start() - time.sleep(1) - yield danted - danted.stop() - @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, httpd): + @classmethod + def _class_scope(cls, env, httpd): indir = httpd.docs_dir env.make_data_file(indir=indir, fname="data-10m", fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10 * 1024 * 1024) diff --git a/tests/http/test_50_scp.py b/tests/http/test_50_scp.py index 13c36acd6571..14be668d7599 100644 --- a/tests/http/test_50_scp.py +++ b/tests/http/test_50_scp.py @@ -38,7 +38,8 @@ class TestScp: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, sshd): + @classmethod + def _class_scope(cls, env, sshd): env.make_data_file(indir=sshd.home_dir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=sshd.home_dir, fname="data-10m", fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10 * 1024) diff --git a/tests/http/test_51_sftp.py b/tests/http/test_51_sftp.py index 6caaad9d25ba..878b2e434531 100644 --- a/tests/http/test_51_sftp.py +++ b/tests/http/test_51_sftp.py @@ -38,7 +38,8 @@ class TestSftp: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, sshd): + @classmethod + def _class_scope(cls, env, sshd): env.make_data_file(indir=sshd.home_dir, fname="data-10k", fsize=10 * 1024) env.make_data_file(indir=sshd.home_dir, fname="data-10m", fsize=10 * 1024 * 1024) env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10 * 1024) @@ -136,6 +137,40 @@ def test_51_12_dl_parallel(self, env: Env, sshd: Sshd): r.check_exit_code(0) self.check_downloads(curl, doc_file, count) + def test_51_13_dl_reuse(self, env: Env, sshd: Sshd): + count = 3 + curl = CurlClient(env=env) + doc_file = os.path.join(sshd.home_dir, 'data-10k') + url = f'sftp://{env.domain1}:{sshd.port}/{doc_file}?[0-{count-1}]' + r = curl.ssh_download(urls=[url], extra_args=[ + '--knownhosts', sshd.known_hosts, + '--pubkey', sshd.user1_pubkey_file, + '--key', sshd.user1_privkey_file, + '--user', f'{os.environ["USER"]}:', + ]) + r.check_exit_code(0) + assert len(r.stats) == count, r.dump_logs() + assert r.total_connects == 1, r.dump_logs() + + def test_51_14_dl_no_reuse(self, env: Env, sshd: Sshd): + if not env.curl_is_debug(): + pytest.skip('only works for curl debug builds') + count = 3 + run_env = os.environ.copy() + run_env['CURL_FORBID_REUSE'] = '1' + curl = CurlClient(env=env, run_env=run_env) + doc_file = os.path.join(sshd.home_dir, 'data-10k') + url = f'sftp://{env.domain1}:{sshd.port}/{doc_file}?[0-{count-1}]' + r = curl.ssh_download(urls=[url], extra_args=[ + '--knownhosts', sshd.known_hosts, + '--pubkey', sshd.user1_pubkey_file, + '--key', sshd.user1_privkey_file, + '--user', f'{os.environ["USER"]}:', + ]) + r.check_exit_code(0) + assert len(r.stats) == count, r.dump_logs() + assert r.total_connects == count, r.dump_logs() + def test_51_20_ul_single(self, env: Env, sshd: Sshd): srcfile = os.path.join(env.gen_dir, 'data-10k') destfile = os.path.join(sshd.home_dir, 'upload_20.data') diff --git a/tests/http/test_60_h3_proxy.py b/tests/http/test_60_h3_proxy.py index 650644eb0d5a..c31f9668aca8 100644 --- a/tests/http/test_60_h3_proxy.py +++ b/tests/http/test_60_h3_proxy.py @@ -1,4 +1,4 @@ -# *************************************************************************** +#*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | @@ -428,6 +428,7 @@ def test_60_08_large_upload(self, env: Env, httpd, h2o_server, h2o_proxy): @MARK_NEEDS_PROXY_HTTP3 @MARK_NEEDS_H2O + @pytest.mark.skipif(condition=not Env.have_h2_curl(), reason="curl without h2") def test_60_09_parallel_downloads(self, env: Env, h2o_server, h2o_proxy): _require_available(h2o_server=h2o_server, h2o_proxy=h2o_proxy) env.make_data_file(indir=h2o_server.docs_dir, fname="download-1m", fsize=1 * 1024 * 1024) @@ -437,7 +438,7 @@ def test_60_09_parallel_downloads(self, env: Env, h2o_server, h2o_proxy): proxy_args = _h2o_proxy_args(env, h2o_proxy, "h3", tunnel=True) proxy_args.extend(["--parallel", "--parallel-max", f"{count}"]) r = curl.http_download( - urls=[urln], alpn_proto="http/1.1", with_stats=True, extra_args=proxy_args + urls=[urln], alpn_proto="h2", with_stats=True, extra_args=proxy_args ) r.check_response(count=count, http_status=200) diff --git a/tests/http/testenv/__init__.py b/tests/http/testenv/__init__.py index 834230a853bd..4a34354e564d 100644 --- a/tests/http/testenv/__init__.py +++ b/tests/http/testenv/__init__.py @@ -36,8 +36,8 @@ from .dante import Dante from .dnsd import Dnsd from .env import Env -from .h2o import H2oServer, H2oProxy +from .h2o import H2oProxy, H2oServer from .httpd import Httpd -from .nghttpx import Nghttpx, NghttpxFwd, NghttpxQuic +from .nghttpx import Nghttpx, NghttpxFwd, NghttpxQuic, NghttpxTcp from .sshd import Sshd from .vsftpd import VsFTPD diff --git a/tests/http/testenv/certs.py b/tests/http/testenv/certs.py index 418590679ac6..3cf057b499fc 100644 --- a/tests/http/testenv/certs.py +++ b/tests/http/testenv/certs.py @@ -213,7 +213,7 @@ def hashdir(self) -> Optional[str]: def get_first(self, name) -> Optional['Credentials']: creds = self._store.get_credentials_for_name(name) if self._store else [] - return creds[0] if len(creds) else None + return creds[0] if creds else None def get_credentials_for_name(self, name) -> List['Credentials']: return self._store.get_credentials_for_name(name) if self._store else [] @@ -368,13 +368,13 @@ def create_root(cls, name: str, store_dir: str, key_type: str = "rsa2048") -> Cr store = CertStore(fpath=store_dir) creds = store.load_credentials(name="ca", key_type=key_type, issuer=None) if creds is None: - creds = TestCA._make_ca_credentials(name=name, key_type=key_type) + creds = cls._make_ca_credentials(name=name, key_type=key_type) store.save(creds, name="ca") creds.set_store(store) return creds - @staticmethod - def create_credentials(spec: CertificateSpec, issuer: Credentials, key_type: Any, + @classmethod + def create_credentials(cls, spec: CertificateSpec, issuer: Credentials, key_type: Any, valid_from: timedelta = timedelta(days=-1), valid_to: timedelta = timedelta(days=89), ) -> Credentials: @@ -384,17 +384,17 @@ def create_credentials(spec: CertificateSpec, issuer: Credentials, key_type: Any :returns: the certificate and private key PEM file paths """ if spec.domains and len(spec.domains): - return TestCA._make_server_credentials(name=spec.name, domains=spec.domains, - issuer=issuer, valid_from=valid_from, - valid_to=valid_to, key_type=key_type) + return cls._make_server_credentials(name=spec.name, domains=spec.domains, + issuer=issuer, valid_from=valid_from, + valid_to=valid_to, key_type=key_type) if spec.client: - return TestCA._make_client_credentials(name=spec.name, issuer=issuer, - email=spec.email, valid_from=valid_from, - valid_to=valid_to, key_type=key_type) + return cls._make_client_credentials(name=spec.name, issuer=issuer, + email=spec.email, valid_from=valid_from, + valid_to=valid_to, key_type=key_type) if spec.name: - return TestCA._make_ca_credentials(name=spec.name, issuer=issuer, - valid_from=valid_from, valid_to=valid_to, - key_type=key_type) + return cls._make_ca_credentials(name=spec.name, issuer=issuer, + valid_from=valid_from, valid_to=valid_to, + key_type=key_type) raise CertError(f"unrecognized certificate specification: {spec}") @staticmethod @@ -521,8 +521,8 @@ def _add_client_usages(csr: Any, issuer: Credentials, rfc82name: Optional[str] = ) return cert - @staticmethod - def _make_ca_credentials(name, key_type: Any, + @classmethod + def _make_ca_credentials(cls, name: str, key_type: Any, issuer: Optional[Credentials] = None, valid_from: timedelta = timedelta(days=-1), valid_to: timedelta = timedelta(days=89), @@ -534,46 +534,48 @@ def _make_ca_credentials(name, key_type: Any, else: issuer_subject = None issuer_key = pkey - subject = TestCA._make_x509_name(org_name=name, parent=issuer.subject if issuer else None) - csr = TestCA._make_csr(subject=subject, - issuer_subject=issuer_subject, pkey=pkey, - valid_from_delta=valid_from, valid_until_delta=valid_to) - csr = TestCA._add_ca_usages(csr) + subject = cls._make_x509_name(org_name=name, parent=issuer.subject if issuer else None) + csr = cls._make_csr(subject=subject, + issuer_subject=issuer_subject, pkey=pkey, + valid_from_delta=valid_from, valid_until_delta=valid_to) + csr = cls._add_ca_usages(csr) cert = csr.sign(private_key=issuer_key, algorithm=hashes.SHA256(), backend=default_backend()) return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) - @staticmethod - def _make_server_credentials(name: str, domains: List[str], issuer: Credentials, + @classmethod + def _make_server_credentials(cls, + name: str, domains: List[str], issuer: Credentials, key_type: Any, valid_from: timedelta = timedelta(days=-1), valid_to: timedelta = timedelta(days=89), ) -> Credentials: pkey = _private_key(key_type=key_type) - subject = TestCA._make_x509_name(common_name=name, parent=issuer.subject) - csr = TestCA._make_csr(subject=subject, - issuer_subject=issuer.certificate.subject, pkey=pkey, - valid_from_delta=valid_from, valid_until_delta=valid_to) - csr = TestCA._add_leaf_usages(csr, domains=domains, issuer=issuer) + subject = cls._make_x509_name(common_name=name, parent=issuer.subject) + csr = cls._make_csr(subject=subject, + issuer_subject=issuer.certificate.subject, pkey=pkey, + valid_from_delta=valid_from, valid_until_delta=valid_to) + csr = cls._add_leaf_usages(csr, domains=domains, issuer=issuer) cert = csr.sign(private_key=issuer.private_key, algorithm=hashes.SHA256(), backend=default_backend()) return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) - @staticmethod - def _make_client_credentials(name: str, + @classmethod + def _make_client_credentials(cls, + name: str, issuer: Credentials, email: Optional[str], key_type: Any, valid_from: timedelta = timedelta(days=-1), valid_to: timedelta = timedelta(days=89), ) -> Credentials: pkey = _private_key(key_type=key_type) - subject = TestCA._make_x509_name(common_name=name, parent=issuer.subject) - csr = TestCA._make_csr(subject=subject, - issuer_subject=issuer.certificate.subject, pkey=pkey, - valid_from_delta=valid_from, valid_until_delta=valid_to) - csr = TestCA._add_client_usages(csr, issuer=issuer, rfc82name=email) + subject = cls._make_x509_name(common_name=name, parent=issuer.subject) + csr = cls._make_csr(subject=subject, + issuer_subject=issuer.certificate.subject, pkey=pkey, + valid_from_delta=valid_from, valid_until_delta=valid_to) + csr = cls._add_client_usages(csr, issuer=issuer, rfc82name=email) cert = csr.sign(private_key=issuer.private_key, algorithm=hashes.SHA256(), backend=default_backend()) diff --git a/tests/http/testenv/curl.py b/tests/http/testenv/curl.py index 34b28b9533ef..9b7f7822d5b7 100644 --- a/tests/http/testenv/curl.py +++ b/tests/http/testenv/curl.py @@ -55,9 +55,9 @@ def AverageStats(cls, profiles: List['RunProfile']): stats = [p.stats for p in profiles] for key in cls.STAT_KEYS: vals = [s[key] for s in stats] - avg[key] = mean(vals) if len(vals) else 0.0 + avg[key] = mean(vals) if vals else 0.0 vals = [s['rss-max'] for s in [p.stats for p in profiles]] - avg['rss-max'] = mean(vals) if len(vals) else 0 + avg['rss-max'] = mean(vals) if vals else 0 return avg def __init__(self, pid: int, started_at: datetime, run_dir): @@ -380,7 +380,7 @@ def stats(self) -> List: @property def total_connects(self) -> Optional[int]: - if len(self.stats): + if self.stats: n = 0 for stat in self.stats: n += stat['num_connects'] diff --git a/tests/http/testenv/dante.py b/tests/http/testenv/dante.py index f42e3f875534..50f7027e0880 100644 --- a/tests/http/testenv/dante.py +++ b/tests/http/testenv/dante.py @@ -139,10 +139,10 @@ def wait_live(self, timeout: timedelta): ]) try_until = datetime.now(timezone.utc) + timeout while datetime.now(timezone.utc) < try_until: + time.sleep(.1) r = curl.http_get(url=f'http://{self.env.domain1}:{self.env.http_port}/') if r.exit_code == 0: return True - time.sleep(.1) log.error(f"Server still not responding after {timeout}") return False diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py index 9571907d8622..1f31485db276 100644 --- a/tests/http/testenv/env.py +++ b/tests/http/testenv/env.py @@ -1,4 +1,4 @@ -# *************************************************************************** +#*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | @@ -74,6 +74,7 @@ def init_config_from(conf_path): class NghttpxUtil: CMD = None VERSION_FULL = None + CAN_ECH = None @classmethod def version(cls, cmd): @@ -93,6 +94,20 @@ def version(cls, cmd): def version_with_h3(version): return re.match(r".* ngtcp2/\d+\.\d+\.\d+.*", version) is not None + @classmethod + def can_ech(cls, cmd): + if cmd is None: + return None + if cls.CAN_ECH is None or cmd != cls.CMD: + p = subprocess.run(args=[cmd, "-h"], capture_output=True, text=True, check=True) + cls.CMD = cmd + cls.CAN_ECH = False + for line in p.stdout.splitlines(keepends=False): + if re.search(r'--ech-config-file', line): + cls.CAN_ECH = True + break + return cls.CAN_ECH + class EnvConfig: def __init__( @@ -218,9 +233,23 @@ def __init__( CertificateSpec(name="user1", client=True), ], ), + CertificateSpec( + name='embedded-zero', + domains=[f'{self.domain1}\0actually.{self.domain2}'], + key_type='rsa2048', + ), + CertificateSpec( + name='embedded-zero-wildcard', + domains=[f'*.{self.tld}\0actually.{self.domain2}'], + key_type='rsa2048', + ), ] - self.openssl = "openssl" + if "OPENSSL" in os.environ: + self.openssl = os.environ["OPENSSL"] + else: + self.openssl = "openssl" + self.openssl_ech = False p = subprocess.run( args=[self.openssl, "version"], capture_output=True, text=True, check=False ) @@ -230,15 +259,20 @@ def __init__( self.openssl_version = None else: self.openssl_version = p.stdout.strip() + m = re.match(r'OpenSSL (\d+).*', self.openssl_version) + if m and int(m.group(1)) >= 4: + self.openssl_ech = True self.nghttpx = self.config["nghttpx"]["nghttpx"] if len(self.nghttpx.strip()) == 0: self.nghttpx = None + self.nghttpx_ech = False self._nghttpx_version = None self.nghttpx_with_h3 = False if self.nghttpx is not None: self._nghttpx_version = NghttpxUtil.version(self.nghttpx) self.nghttpx_with_h3 = NghttpxUtil.version_with_h3(self._nghttpx_version) + self.nghttpx_ech = NghttpxUtil.can_ech(self.nghttpx) self.caddy = self.config["caddy"]["caddy"] self._caddy_version = None @@ -342,7 +376,7 @@ def __init__( if p.returncode != 0: self.sshd = None else: - m = re.match(r"^OpenSSH_(\d+\.\d+.*),.*", p.stderr) + m = re.match(r"^OpenSSH_(\d+\.\d+).*,.*", p.stderr) assert m, f"version: {p.stderr}" if m: self._sshd_version = m.group(1) @@ -429,6 +463,10 @@ def vsftpd_version(self): def h2o_version(self): return self._h2o_version + @property + def sshd_version(self): + return self._sshd_version + @property def tcpdump(self) -> Optional[str]: return self._tcpdump @@ -444,245 +482,257 @@ class Env: CONFIG = EnvConfig() - @staticmethod - def setup_incomplete() -> bool: - return not Env.CONFIG.is_complete() + @classmethod + def setup_incomplete(cls) -> bool: + return not cls.CONFIG.is_complete() - @staticmethod - def incomplete_reason() -> Optional[str]: - return Env.CONFIG.get_incomplete_reason() + @classmethod + def incomplete_reason(cls) -> Optional[str]: + return cls.CONFIG.get_incomplete_reason() - @staticmethod - def have_openssl() -> bool: - return Env.CONFIG.openssl is not None + @classmethod + def have_openssl(cls) -> bool: + return cls.CONFIG.openssl is not None - @staticmethod - def have_nghttpx() -> bool: - return Env.CONFIG.nghttpx is not None + @classmethod + def have_nghttpx(cls) -> bool: + return cls.CONFIG.nghttpx is not None - @staticmethod - def have_h3_server() -> bool: - return Env.CONFIG.nghttpx_with_h3 + @classmethod + def have_h3_server(cls) -> bool: + return cls.CONFIG.nghttpx_with_h3 @staticmethod - def have_h2o() -> bool: - return Env.CONFIG.h2o is not None + def have_openssl_ech() -> bool: + return Env.CONFIG.openssl_ech @staticmethod - def have_ssl_curl() -> bool: - return Env.curl_has_feature("ssl") or Env.curl_has_feature("multissl") + def have_nghttpx_ech() -> bool: + return Env.CONFIG.nghttpx_ech - @staticmethod - def have_h2_curl() -> bool: - return "http2" in Env.CONFIG.curl_props["features"] + @classmethod + def have_h2o(cls) -> bool: + return cls.CONFIG.h2o is not None - @staticmethod - def have_h3_curl() -> bool: - return "http3" in Env.CONFIG.curl_props["features"] + @classmethod + def have_ssl_curl(cls) -> bool: + return cls.curl_has_feature("ssl") or cls.curl_has_feature("multissl") - @staticmethod - def have_compressed_curl() -> bool: + @classmethod + def have_h2_curl(cls) -> bool: + return "http2" in cls.CONFIG.curl_props["features"] + + @classmethod + def have_h3_curl(cls) -> bool: + return "http3" in cls.CONFIG.curl_props["features"] + + @classmethod + def have_compressed_curl(cls) -> bool: return ( - "brotli" in Env.CONFIG.curl_props["libs"] - or "zlib" in Env.CONFIG.curl_props["libs"] - or "zstd" in Env.CONFIG.curl_props["libs"] + "brotli" in cls.CONFIG.curl_props["libs"] + or "zlib" in cls.CONFIG.curl_props["libs"] + or "zstd" in cls.CONFIG.curl_props["libs"] ) - @staticmethod - def curl_uses_lib(libname: str) -> bool: - return libname.lower() in Env.CONFIG.curl_props["libs"] + @classmethod + def curl_uses_lib(cls, libname: str) -> bool: + return libname.lower() in cls.CONFIG.curl_props["libs"] - @staticmethod - def curl_uses_any_libs(libs: List[str]) -> bool: - return any(libname.lower() in Env.CONFIG.curl_props["libs"] for libname in libs) + @classmethod + def curl_uses_any_libs(cls, libs: List[str]) -> bool: + return any(libname.lower() in cls.CONFIG.curl_props["libs"] for libname in libs) - @staticmethod - def curl_uses_ossl_quic() -> bool: - if Env.have_h3_curl(): - return not Env.curl_uses_lib("ngtcp2") and Env.curl_uses_lib("nghttp3") + @classmethod + def curl_uses_ossl_quic(cls) -> bool: + if cls.have_h3_curl(): + return not cls.curl_uses_lib("ngtcp2") and cls.curl_uses_lib("nghttp3") return False - @staticmethod - def curl_version_string() -> str: - return Env.CONFIG.curl_props["version_string"] + @classmethod + def curl_version_string(cls) -> str: + return cls.CONFIG.curl_props["version_string"] - @staticmethod - def curl_version_at_least(min_version) -> bool: - version = Env.curl_version() - return Env.CONFIG.versiontuple(min_version) <= Env.CONFIG.versiontuple( + @classmethod + def curl_version_at_least(cls, min_version) -> bool: + version = cls.curl_version() + return cls.CONFIG.versiontuple(min_version) <= cls.CONFIG.versiontuple( version ) - @staticmethod - def curl_features_string() -> str: - return Env.CONFIG.curl_props["features_string"] + @classmethod + def curl_features_string(cls) -> str: + return cls.CONFIG.curl_props["features_string"] - @staticmethod - def curl_has_feature(feature: str) -> bool: - return feature.lower() in Env.CONFIG.curl_props["features"] + @classmethod + def curl_has_feature(cls, feature: str) -> bool: + return feature.lower() in cls.CONFIG.curl_props["features"] - @staticmethod - def curl_protocols_string() -> str: - return Env.CONFIG.curl_props["protocols_string"] + @classmethod + def curl_protocols_string(cls) -> str: + return cls.CONFIG.curl_props["protocols_string"] - @staticmethod - def curl_has_protocol(protocol: str) -> bool: - return protocol.lower() in Env.CONFIG.curl_props["protocols"] + @classmethod + def curl_has_protocol(cls, protocol: str) -> bool: + return protocol.lower() in cls.CONFIG.curl_props["protocols"] - @staticmethod - def curl_lib_version(libname: str) -> str: + @classmethod + def curl_lib_version(cls, libname: str) -> str: prefix = f"{libname.lower()}/" - for lversion in Env.CONFIG.curl_props["lib_versions"]: + for lversion in cls.CONFIG.curl_props["lib_versions"]: if lversion.startswith(prefix): return lversion[len(prefix):] return "unknown" - @staticmethod - def curl_lib_version_at_least(libname: str, min_version) -> bool: - lversion = Env.curl_lib_version(libname) + @classmethod + def curl_lib_version_at_least(cls, libname: str, min_version) -> bool: + lversion = cls.curl_lib_version(libname) if lversion != "unknown": - return Env.CONFIG.versiontuple(min_version) <= Env.CONFIG.versiontuple( + return cls.CONFIG.versiontuple(min_version) <= cls.CONFIG.versiontuple( lversion ) return False - @staticmethod - def curl_lib_version_before(libname: str, lib_version) -> bool: - lversion = Env.curl_lib_version(libname) + @classmethod + def curl_lib_version_before(cls, libname: str, lib_version) -> bool: + lversion = cls.curl_lib_version(libname) if lversion != "unknown": if m := re.match(r"(\d+\.\d+\.\d+).*", lversion): lversion = m.group(1) - return Env.CONFIG.versiontuple(lib_version) > Env.CONFIG.versiontuple( + return cls.CONFIG.versiontuple(lib_version) > cls.CONFIG.versiontuple( lversion ) return False - @staticmethod - def curl_os() -> str: - return Env.CONFIG.curl_props["os"] + @classmethod + def curl_os(cls) -> str: + return cls.CONFIG.curl_props["os"] - @staticmethod - def curl_fullname() -> str: - return Env.CONFIG.curl_props["fullname"] + @classmethod + def curl_fullname(cls) -> str: + return cls.CONFIG.curl_props["fullname"] - @staticmethod - def curl_version() -> str: - return Env.CONFIG.curl_props["version"] + @classmethod + def curl_version(cls) -> str: + return cls.CONFIG.curl_props["version"] - @staticmethod - def curl_is_debug() -> bool: - return Env.CONFIG.curl_is_debug + @classmethod + def curl_is_debug(cls) -> bool: + return cls.CONFIG.curl_is_debug - @staticmethod - def curl_is_verbose() -> bool: - return Env.CONFIG.curl_is_verbose + @classmethod + def curl_is_verbose(cls) -> bool: + return cls.CONFIG.curl_is_verbose - @staticmethod - def curl_can_cert_status() -> bool: - return Env.CONFIG.curl_can_cert_status + @classmethod + def curl_can_cert_status(cls) -> bool: + return cls.CONFIG.curl_can_cert_status - @staticmethod - def curl_override_dns() -> bool: - return Env.CONFIG.curl_override_dns + @classmethod + def curl_override_dns(cls) -> bool: + return cls.CONFIG.curl_override_dns - @staticmethod - def curl_resolv_threaded() -> bool: - return Env.CONFIG.curl_resolv_threaded + @classmethod + def curl_resolv_threaded(cls) -> bool: + return cls.CONFIG.curl_resolv_threaded - @staticmethod - def curl_can_doh() -> bool: - return Env.CONFIG.curl_can_doh + @classmethod + def curl_can_doh(cls) -> bool: + return cls.CONFIG.curl_can_doh - @staticmethod - def curl_can_early_data() -> bool: - if Env.curl_uses_lib('gnutls'): - return Env.curl_lib_version_at_least('gnutls', '3.7.2') - return Env.curl_uses_any_libs(['wolfssl', 'quictls', 'openssl']) + @classmethod + def curl_can_early_data(cls) -> bool: + if cls.curl_uses_lib('gnutls'): + return cls.curl_lib_version_at_least('gnutls', '3.7.2') + return cls.curl_uses_any_libs(['wolfssl', 'quictls', 'openssl']) - @staticmethod - def curl_can_h3_early_data() -> bool: - return Env.curl_can_early_data() and Env.curl_uses_lib("ngtcp2") + @classmethod + def curl_can_h3_early_data(cls) -> bool: + return cls.curl_can_early_data() and cls.curl_uses_lib("ngtcp2") - @staticmethod - def http_protos() -> List[str]: + @classmethod + def http_protos(cls) -> List[str]: # http protocols we can test - if Env.have_h2_curl(): - if Env.have_h3(): + if cls.have_h2_curl(): + if cls.have_h3(): return ["http/1.1", "h2", "h3"] return ["http/1.1", "h2"] return ["http/1.1"] - @staticmethod - def http_h1_h2_protos() -> List[str]: + @classmethod + def http_h1_h2_protos(cls) -> List[str]: # http 1+2 protocols we can test - if Env.have_h2_curl(): + if cls.have_h2_curl(): return ["http/1.1", "h2"] return ["http/1.1"] - @staticmethod - def http_mplx_protos() -> List[str]: + @classmethod + def http_mplx_protos(cls) -> List[str]: # http multiplexing protocols we can test - if Env.have_h2_curl(): - if Env.have_h3(): + if cls.have_h2_curl(): + if cls.have_h3(): return ["h2", "h3"] return ["h2"] return [] - @staticmethod - def have_h3() -> bool: - return Env.have_h3_curl() and Env.have_h3_server() + @classmethod + def have_h3(cls) -> bool: + return cls.have_h3_curl() and cls.have_h3_server() - @staticmethod - def httpd_version() -> str: - return Env.CONFIG.httpd_version + @classmethod + def httpd_version(cls) -> str: + return cls.CONFIG.httpd_version - @staticmethod - def nghttpx_version() -> str: - return Env.CONFIG.nghttpx_version + @classmethod + def nghttpx_version(cls) -> str: + return cls.CONFIG.nghttpx_version - @staticmethod - def caddy_version() -> str: - return Env.CONFIG.caddy_version + @classmethod + def caddy_version(cls) -> str: + return cls.CONFIG.caddy_version - @staticmethod - def h2o_version() -> str: - return Env.CONFIG.h2o_version + @classmethod + def h2o_version(cls) -> str: + return cls.CONFIG.h2o_version - @staticmethod - def caddy_is_at_least(minv) -> bool: - return Env.CONFIG.caddy_is_at_least(minv) + @classmethod + def sshd_version(cls) -> str: + return cls.CONFIG.sshd_version - @staticmethod - def httpd_is_at_least(minv) -> bool: - return Env.CONFIG.httpd_is_at_least(minv) + @classmethod + def caddy_is_at_least(cls, minv) -> bool: + return cls.CONFIG.caddy_is_at_least(minv) - @staticmethod - def has_caddy() -> bool: - return Env.CONFIG.caddy is not None + @classmethod + def httpd_is_at_least(cls, minv) -> bool: + return cls.CONFIG.httpd_is_at_least(minv) - @staticmethod - def has_vsftpd() -> bool: - return Env.CONFIG.vsftpd is not None + @classmethod + def has_caddy(cls) -> bool: + return cls.CONFIG.caddy is not None - @staticmethod - def vsftpd_version() -> str: - return Env.CONFIG.vsftpd_version + @classmethod + def has_vsftpd(cls) -> bool: + return cls.CONFIG.vsftpd is not None - @staticmethod - def has_danted() -> bool: - return Env.CONFIG.danted is not None + @classmethod + def vsftpd_version(cls) -> str: + return cls.CONFIG.vsftpd_version - @staticmethod - def has_sshd() -> bool: - return Env.CONFIG.sshd is not None + @classmethod + def has_danted(cls) -> bool: + return cls.CONFIG.danted is not None - @staticmethod - def has_sftpd() -> bool: - return Env.has_sshd() and Env.CONFIG.sftpd is not None + @classmethod + def has_sshd(cls) -> bool: + return cls.CONFIG.sshd is not None - @staticmethod - def tcpdump() -> Optional[str]: - return Env.CONFIG.tcpdump + @classmethod + def has_sftpd(cls) -> bool: + return cls.has_sshd() and cls.CONFIG.sftpd is not None + + @classmethod + def tcpdump(cls) -> Optional[str]: + return cls.CONFIG.tcpdump def __init__(self, pytestconfig=None, env_config=None): if env_config: @@ -704,6 +754,15 @@ def issue_certs(self): self._ca.issue_certs(self.CONFIG.cert_specs) if self.have_openssl(): self._ca.create_hashdir(self.openssl) + if self.have_openssl_ech(): + for domain in [self.domain1, self.domain2]: + p = subprocess.run(args=[ + self.openssl, 'ech', + '-out', os.path.join(ca_dir, f'{domain}.echconfig'), + '-public_name', f'public.{domain}' + ], capture_output=True, text=True, check=True) + if p.returncode != 0: + raise EnvError(f"Error generating ECHConfig for {domain}") def setup(self): os.makedirs(self.gen_dir, exist_ok=True) @@ -716,6 +775,28 @@ def get_credentials(self, domain) -> Optional[Credentials]: return creds[0] return None + def get_echconfig_file(self, domain) -> Optional[str]: + ech_file = os.path.join(self.gen_dir, f"ca/{domain}.echconfig") + if os.path.exists(ech_file): + return ech_file + return None + + def get_echconfig_arg(self, domain) -> Optional[str]: + ech_file = self.get_echconfig_file(domain) + if ech_file: + ech_config = '' + in_pem = False + with open(ech_file) as fd: + for line in fd.read().splitlines(): + if not in_pem and line == '-----BEGIN ECHCONFIG-----': + in_pem = True + elif in_pem and line == '-----END ECHCONFIG-----': + break + elif in_pem: + ech_config += line + return ech_config + return None + @property def verbose(self) -> int: return self._verbose diff --git a/tests/http/testenv/h2o.py b/tests/http/testenv/h2o.py index 82ab16b76bfb..64c24eba2779 100644 --- a/tests/http/testenv/h2o.py +++ b/tests/http/testenv/h2o.py @@ -1,4 +1,4 @@ -# *************************************************************************** +#*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | @@ -86,8 +86,8 @@ def dump_logs(self): "-------------------------------------------"]) self._dump_file(self._stderr, lines) lines.extend(["", - f"errorlog of {self._name}", - "-------------------------------------------"]) + f"errorlog of {self._name}", + "-------------------------------------------"]) self._dump_file(self._error_log, lines) lines.append("") return lines @@ -220,13 +220,13 @@ def wait_for_state( url = f"https://{self._domain}:{self._port}/" while datetime.now(timezone.utc) < try_until: if live: - r = curl.http_get( - url=url, extra_args=["--trace", "curl.trace", "--trace-time"] - ) + r = curl.http_get(url=url, extra_args=[ + '--trace', 'curl.trace', '--trace-time', '--insecure' + ]) if r.exit_code == 0: return True else: - r = curl.http_get(url=url) + r = curl.http_get(url=url, extra_args=['--insecure']) if r.exit_code != 0: return True time.sleep(0.1) diff --git a/tests/http/testenv/nghttpx.py b/tests/http/testenv/nghttpx.py index ba0cbe1a798e..87ba7ec760d3 100644 --- a/tests/http/testenv/nghttpx.py +++ b/tests/http/testenv/nghttpx.py @@ -59,6 +59,7 @@ def __init__(self, env: Env, name: str, domain: str, cred_name: str): self._cred_name = self._def_cred_name = cred_name self._loaded_cred_name = '' self._version = NghttpxUtil.version(self._cmd) + self._uses_ech = False def supports_h3(self): return NghttpxUtil.version_with_h3(self._version) @@ -83,6 +84,10 @@ def port(self): def port_is_quic(self): return self._port_is_quic + @property + def uses_ech(self): + return self._uses_ech + def exists(self): return self._cmd and os.path.exists(self._cmd) @@ -182,7 +187,7 @@ def wait_live(self, timeout: timedelta): while datetime.now(timezone.utc) < try_until: xargs = [ '--trace', 'curl.trace', '--trace-time', - '--connect-timeout', '1' + '--connect-timeout', '1', '--insecure' ] if self.port_is_quic: xargs.extend(['--http3-only']) @@ -221,6 +226,10 @@ def __init__(self, env: Env): domain=env.domain1, cred_name=env.domain1) self._https_port = 0 + @property + def https_port(self): + return self._https_port + def initial_start(self): super().initial_start() @@ -269,6 +278,74 @@ def start(self, wait_live=True): '--frontend-http3-max-connection-window-size=100M', # f'--frontend-quic-debug-log', ]) + if self.env.have_nghttpx_ech(): + ech_conf = self.env.get_echconfig_file(self._cred_name) + if ech_conf: + args.extend([ + '--ech-config-file', ech_conf + ]) + self._uses_ech = True + self._error_fd = open(self._stderr, 'a') # noqa: SIM115 + self._process = subprocess.Popen(args=args, stderr=self._error_fd) + if self._process.returncode is not None: + return False + return not wait_live or self.wait_live(timeout=timedelta(seconds=Env.SERVER_TIMEOUT)) + + +class NghttpxTcp(Nghttpx): + + PORT_SPECS: ClassVar[Dict[str, int]] = { + 'nghttpx_tcp': socket.SOCK_STREAM, + } + + def __init__(self, env: Env): + super().__init__(env=env, name='nghttpx-tcp', + domain=env.domain1, cred_name=env.domain1) + + def initial_start(self): + super().initial_start() + + def startup(ports: Dict[str, int]) -> bool: + self._port = ports['nghttpx_tcp'] + if self.start(): + self.env.update_ports(ports) + return True + self.stop() + self._port = 0 + return False + + return alloc_ports_and_do(NghttpxTcp.PORT_SPECS, startup, + self.env.gen_root, max_tries=3) + + def start(self, wait_live=True): + self._mkpath(self._tmp_dir) + if self._process: + self.stop() + creds = self.env.get_credentials(self._cred_name) + assert creds # convince pytype this is not None + self._loaded_cred_name = self._cred_name + args = [self._cmd, f'--frontend=*,{self._port};tls'] + args.extend([ + f'--backend=127.0.0.1,{self.env.http_port}', + '--log-level=ERROR', + f'--pid-file={self._pid_file}', + f'--errorlog-file={self._error_log}', + f'--conf={self._conf_file}', + f'--cacert={self.env.ca.cert_file}', + creds.pkey_file, + creds.cert_file, + '--frontend-http3-window-size=1M', + '--frontend-http3-max-window-size=10M', + '--frontend-http3-connection-window-size=10M', + '--frontend-http3-max-connection-window-size=100M', + ]) + if self.env.have_nghttpx_ech(): + ech_conf = self.env.get_echconfig_file(self._cred_name) + if ech_conf: + args.extend([ + '--ech-config-file', ech_conf + ]) + self._uses_ech = True self._error_fd = open(self._stderr, 'a') # noqa: SIM115 self._process = subprocess.Popen(args=args, stderr=self._error_fd) if self._process.returncode is not None: @@ -330,7 +407,7 @@ def wait_dead(self, timeout: timedelta): try_until = datetime.now(timezone.utc) + timeout while datetime.now(timezone.utc) < try_until: check_url = f'https://{self.env.proxy_domain}:{self._port}/' - r = curl.http_get(url=check_url) + r = curl.http_get(url=check_url, extra_args=['--insecure']) if r.exit_code != 0: return True log.debug(f'waiting for nghttpx-fwd to stop responding: {r}') diff --git a/tests/http/testenv/sshd.py b/tests/http/testenv/sshd.py index 7753e855c9f1..24922bef694f 100644 --- a/tests/http/testenv/sshd.py +++ b/tests/http/testenv/sshd.py @@ -277,6 +277,8 @@ def _write_config(self): f'AuthorizedKeysFile {self._auth_keys}', f'PidFile {self._pid_file}', ] + if Env.CONFIG.versiontuple(self.env.sshd_version()) >= Env.CONFIG.versiontuple('9.8'): + conf.append('PerSourcePenalties no') conf.extend([f'HostKey {key_file}' for key_file in self._host_key_files]) if self._sftpd: conf.append(f'Subsystem sftp {self._sftpd}') diff --git a/tests/libtest/Makefile.am b/tests/libtest/Makefile.am index 74eb150742c7..8fdf484b5592 100644 --- a/tests/libtest/Makefile.am +++ b/tests/libtest/Makefile.am @@ -49,7 +49,8 @@ EXTRA_DIST = CMakeLists.txt $(FIRST_C) $(FIRST_H) $(UTILS_C) $(UTILS_H) $(TESTS_ test1022.pl \ test307.pl \ test610.pl \ - test613.pl + test613.pl \ + test3500.pl CFLAGS += @CURL_CFLAG_EXTRAS@ diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 26c72e5672ec..0f7e749e7490 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -35,7 +35,7 @@ UTILS_H = testutil.h testtrace.h unitcheck.h CURLX_C = \ ../../lib/curl_threads.c \ - ../../lib/curlx/fopen.c \ + ../../lib/curlx/win32-fopen.c \ ../../lib/curlx/inet_ntop.c \ ../../lib/curlx/inet_pton.c \ ../../lib/curlx/multibyte.c \ @@ -68,6 +68,7 @@ TESTS_C = \ cli_ws_pingpong.c \ cli_ws_write_err.c \ \ + lib464.c \ lib500.c \ lib501.c \ lib502.c \ @@ -169,6 +170,7 @@ TESTS_C = \ lib1308.c \ lib1396.c \ lib1398.c \ + lib1451.c \ lib1485.c \ lib1500.c \ lib1501.c \ @@ -236,12 +238,18 @@ TESTS_C = \ lib1597.c \ lib1598.c \ lib1599.c \ + lib1605.c \ + lib1616.c \ lib1647.c \ lib1648.c \ lib1649.c \ lib1662.c \ lib1678.c \ lib1686.c \ + lib1742.c \ + lib1744.c \ + lib1745.c \ + lib1791.c \ lib1900.c \ lib1901.c \ lib1902.c \ @@ -290,27 +298,34 @@ TESTS_C = \ lib1977.c \ lib1978.c \ lib1985.c \ + lib1989.c \ lib2023.c \ lib2032.c \ lib2082.c \ + lib2095.c \ lib2118.c \ + lib2270.c \ lib2301.c \ lib2302.c \ lib2304.c \ lib2306.c \ lib2308.c \ lib2309.c \ + lib2380.c \ lib2397.c \ lib2402.c \ lib2404.c \ lib2405.c \ lib2412.c \ lib2414.c \ + lib2453.c \ lib2502.c \ lib2504.c \ lib2505.c \ lib2506.c \ lib2700.c \ + lib2805.c \ + lib2924.c \ lib3010.c \ lib3025.c \ lib3026.c \ @@ -323,7 +338,13 @@ TESTS_C = \ lib3103.c \ lib3104.c \ lib3105.c \ + lib3107.c \ lib3207.c \ lib3208.c \ + lib3231.c \ + lib3233.c \ + lib3234.c \ + lib3595.c \ + lib3596.c \ lib5000.c \ lib5004.c diff --git a/tests/libtest/cli_ev_download.c b/tests/libtest/cli_ev_download.c index e3ca6ad406a7..5aa99bedc1e2 100644 --- a/tests/libtest/cli_ev_download.c +++ b/tests/libtest/cli_ev_download.c @@ -166,6 +166,8 @@ static void usage_ev_download(const char *msg) " event-based downloads with connection reuse forbidden\n" " -n number of transfers to do serially (default: 5)\n" " -C certfile for CA verification\n" + " -t ms server response timeout (default: libcurl default)\n" + " -m ms test time limit per transfer (default: 30000)\n" ); } @@ -176,6 +178,8 @@ static CURLcode test_cli_ev_download(const char *URL) char *cafile = NULL; const char *url; size_t transfer_count = 5; + long response_timeout_ms = 0; + long test_timeout_ms = 30000; size_t i; int watched = 0; struct curltime started; @@ -186,7 +190,7 @@ static CURLcode test_cli_ev_download(const char *URL) memset(&ctx, 0, sizeof(ctx)); ctx.timer_ms = -1; - while((ch = cgetopt(test_argc, test_argv, "hn:C:")) != -1) { + while((ch = cgetopt(test_argc, test_argv, "hn:C:t:m:")) != -1) { switch(ch) { case 'h': usage_ev_download(NULL); @@ -203,6 +207,21 @@ static CURLcode test_cli_ev_download(const char *URL) curlx_free(cafile); cafile = curlx_strdup(coptarg); break; + case 't': + case 'm': { + const char *opt = coptarg; + curl_off_t num; + if(curlx_str_number(&opt, &num, LONG_MAX)) { + usage_ev_download("invalid timeout"); + result = (CURLcode)1; + goto optcleanup; + } + if(ch == 't') + response_timeout_ms = (long)num; + else + test_timeout_ms = (long)num; + break; + } default: usage_ev_download("invalid option"); result = (CURLcode)1; @@ -252,6 +271,9 @@ static CURLcode test_cli_ev_download(const char *URL) curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, ev_dl_discard_cb); curl_easy_setopt(easy, CURLOPT_FORBID_REUSE, 1L); curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); + if(response_timeout_ms) + curl_easy_setopt(easy, CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, + response_timeout_ms); if(cafile) curl_easy_setopt(easy, CURLOPT_CAINFO, cafile); @@ -276,9 +298,11 @@ static CURLcode test_cli_ev_download(const char *URL) curl_mfprintf(stderr, "[t-%zu] FINISHED with result %d\n", i, (int)result); } - else if(curlx_timediff_ms(curlx_now(), started) > (timediff_t)30000) { + else if(curlx_timediff_ms(curlx_now(), started) > test_timeout_ms) { curl_mfprintf(stderr, "[t-%zu] transfer timed out\n", i); result = (CURLcode)1; + curl_multi_remove_handle(multi, easy); + curl_easy_cleanup(easy); goto cleanup; } } diff --git a/tests/libtest/cli_h2_pausing.c b/tests/libtest/cli_h2_pausing.c index 19a2760245dc..3663d841f022 100644 --- a/tests/libtest/cli_h2_pausing.c +++ b/tests/libtest/cli_h2_pausing.c @@ -260,15 +260,14 @@ static CURLcode test_cli_h2_pausing(const char *URL) while((msg = curl_multi_info_read(multi, &msgs_left)) != NULL) { if(msg->msg == CURLMSG_DONE) { for(i = 0; i < CURL_ARRAYSIZE(handles); i++) { - if(msg->easy_handle == handles[i].curl) { - if(handles[i].paused != 1 || !handles[i].resumed) { - curl_mfprintf(stderr, "ERROR: [%zu] done, paused=%d, " - "resumed=%d, result %d - wtf?\n", i, - handles[i].paused, - handles[i].resumed, (int)msg->data.result); - result = (CURLcode)1; - goto cleanup; - } + if(msg->easy_handle == handles[i].curl && + (handles[i].paused != 1 || !handles[i].resumed)) { + curl_mfprintf(stderr, "ERROR: [%zu] done, paused=%d, " + "resumed=%d, result %d - wtf?\n", i, + handles[i].paused, + handles[i].resumed, (int)msg->data.result); + result = (CURLcode)1; + goto cleanup; } } } diff --git a/tests/libtest/cli_h2_serverpush.c b/tests/libtest/cli_h2_serverpush.c index 8a16b01eba53..4c7832372e7e 100644 --- a/tests/libtest/cli_h2_serverpush.c +++ b/tests/libtest/cli_h2_serverpush.c @@ -169,6 +169,13 @@ static CURLcode test_cli_h2_serverpush(const char *URL) m = curl_multi_info_read(multi, &msgq); if(m && (m->msg == CURLMSG_DONE)) { CURL *easy = m->easy_handle; + if(easy != curl) { + char *redirect_url = NULL; + if(!curl_easy_getinfo(easy, CURLINFO_REDIRECT_URL, + &redirect_url) && redirect_url) + curl_mfprintf(stderr, "**** push redirect URL: %s\n", + redirect_url); + } transfers--; curl_multi_remove_handle(multi, easy); curl_easy_cleanup(easy); diff --git a/tests/libtest/first.h b/tests/libtest/first.h index 75ae75c6fb43..d5a3ec45fde0 100644 --- a/tests/libtest/first.h +++ b/tests/libtest/first.h @@ -46,7 +46,7 @@ extern int unitfail; /* for unittests */ #include "curlx/base64.h" /* for curlx_base64* */ #include "curlx/dynbuf.h" /* for curlx_dyn_*() */ -#include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/win32-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/strcopy.h" /* for curlx_strcopy() */ diff --git a/tests/libtest/lib1396.c b/tests/libtest/lib1396.c index 60d139a8500f..1eae1a88e541 100644 --- a/tests/libtest/lib1396.c +++ b/tests/libtest/lib1396.c @@ -69,6 +69,10 @@ static CURLcode test_lib1396(const char *arg) /* escape, this => that */ static const struct test list2[] = { { "a", 1, "a", 1 }, + { "-", 1, "-", 1 }, + { ".", 1, ".", 1 }, + { "_", 1, "_", 1 }, + { "~", 1, "~", 1 }, { "/", 1, "%2F", 3 }, { "a=b", 3, "a%3Db", 5 }, { "a=b", 0, "a%3Db", 5 }, @@ -113,5 +117,43 @@ static CURLcode test_lib1396(const char *arg) curl_free(out); } + { + unsigned char binary[256]; + char expected[sizeof(binary) * 3 + 1]; + const char digits[] = "0123456789ABCDEF"; + size_t encoded_length = 0; + unsigned int byte; + char *encoded; + char *decoded; + int decoded_length; + + for(byte = 0; byte < sizeof(binary); byte++) { + binary[byte] = (unsigned char)byte; + if((byte >= 'a' && byte <= 'z') || (byte >= 'A' && byte <= 'Z') || + (byte >= '0' && byte <= '9') || byte == '-' || byte == '.' || + byte == '_' || byte == '~') + expected[encoded_length++] = (char)byte; + else { + expected[encoded_length++] = '%'; + expected[encoded_length++] = digits[byte >> 4]; + expected[encoded_length++] = digits[byte & 15]; + } + } + expected[encoded_length] = 0; + encoded = curl_easy_escape(easy, (const char *)binary, sizeof(binary)); + abort_unless(encoded, "binary escape failed"); + fail_unless(!strcmp(encoded, expected), "wrong binary escape output"); + decoded = curl_easy_unescape(easy, encoded, 0, &decoded_length); + curl_free(encoded); + abort_unless(decoded, "binary unescape failed"); + fail_unless(decoded_length == sizeof(binary), "wrong binary output size"); + if(decoded_length == sizeof(binary)) { + fail_unless(!memcmp(decoded, binary, sizeof(binary)), + "wrong binary data"); + fail_unless(!decoded[decoded_length], "binary output not terminated"); + } + curl_free(decoded); + } + UNITTEST_END(t1396_stop(easy)) } diff --git a/tests/libtest/lib1451.c b/tests/libtest/lib1451.c new file mode 100644 index 000000000000..240fda5b93d9 --- /dev/null +++ b/tests/libtest/lib1451.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "unitcheck.h" + +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#endif + +static CURLcode test_lib1451(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#ifdef _WIN32 + /* bare "%I" must track native pointer/size_t width, not curl_off_t */ + { + struct { + unsigned char guard1[8]; + size_t count; + unsigned char guard2[8]; + } canary; + unsigned char expect_guard[8]; + char buf[64]; + int rc; + + memset(&canary, 0xaa, sizeof(canary)); + memset(expect_guard, 0xaa, sizeof(expect_guard)); + + rc = curl_msnprintf(buf, sizeof(buf), "0123456789%In", &canary.count); + fail_unless(rc == 10, "curl_msnprintf returned unexpected length"); + fail_unless(canary.count == 10, "%In wrote the wrong count"); + verify_memory(canary.guard1, expect_guard, sizeof(expect_guard)); + verify_memory(canary.guard2, expect_guard, sizeof(expect_guard)); + } + + { + char buf[64]; + curl_msnprintf(buf, sizeof(buf), "%Iu-%d", (size_t)0x11223344, 42); + fail_unless(!strcmp(buf, "287454020-42"), + "%Iu did not consume a size_t-sized argument"); + } +#endif + + UNITTEST_END_SIMPLE +} + +#ifdef CURL_HAVE_DIAG +#pragma GCC diagnostic pop +#endif diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index e18b2d6508e7..9f9073569dab 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -194,23 +194,27 @@ static const struct testcase get_parts_list[] = { "https | [11] | [12] | [13] | [::13.1.68.3] | 1234 " "| / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, { /* query and fragments with control characters */ - "http://host/path/?\001#\002", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "http://host/path/?a\001#b\002", + "http | [11] | [12] | [13] | host | [15] | /path/ | a%01 | b%02", + CURLU_URLENCODE, 0, CURLUE_OK }, { /* Control code in port number is never okay, even when URL encoding */ "http://host:\x1f""80/path/", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "", CURLU_URLENCODE, 0, CURLUE_BAD_PORT_NUMBER }, { /* Control code in scheme never okay, even when URL encoding */ "http\x1f://host/path/", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "", CURLU_URLENCODE, 0, CURLUE_BAD_SCHEME }, { /* Control code in separator never okay, even when URL encoding */ "http:/%1f/host/path/", "", CURLU_URLENCODE, 0, CURLUE_BAD_HOSTNAME }, { /* Path with control characters */ "http://host/path\001with\002ctrl/", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "http | [11] | [12] | [13] | host | [15] | /path%01with%02ctrl/ | [16] |" + " [17]", + CURLU_URLENCODE, 0, CURLUE_OK }, { /* Path with control characters and space! */ "http://host/path\x7fwith space/", - "", CURLU_URLENCODE, 0, CURLUE_MALFORMED_INPUT }, + "http | [11] | [12] | [13] | host | [15] | /path%7Fwith%20space/ | [16] " + "| [17]", CURLU_URLENCODE, 0, CURLUE_OK }, { /* Ensure %2f is NOT normalized to / even if path-as-is is NOT set */ "http://host/path%2f..%2f/", "http | [11] | [12] | [13] | host | [15] | /path%2f..%2f/ | [16] | [17]", @@ -281,7 +285,7 @@ static const struct testcase get_parts_list[] = { "https | [11] | [12] | [13] | curl.se | [15] | / | [16] | %20%20", CURLU_URLENCODE | CURLU_ALLOW_SPACE, 0, CURLUE_OK }, { "", "", 0, 0, CURLUE_MALFORMED_INPUT }, - { " ", "", 0, 0, CURLUE_MALFORMED_INPUT }, + { " ", "", 0, 0, CURLUE_BAD_SCHEME }, { "1h://example.net", "", 0, 0, CURLUE_BAD_SCHEME }, { "..://example.net", "", 0, 0, CURLUE_BAD_SCHEME }, { "-ht://example.net", "", 0, 0, CURLUE_BAD_SCHEME }, @@ -309,9 +313,9 @@ static const struct testcase get_parts_list[] = { 0, CURLU_URLDECODE, CURLUE_OK }, #ifdef USE_IDN /* https://sv.wikipedia.org/wiki/R%c3%a4ksm%c3%b6rg%c3%a5s - https://codepoints.net/U+00E4 Latin Small Letter A with Diaeresis - https://codepoints.net/U+00F6 Latin Small Letter O with Diaeresis - https://codepoints.net/U+00E5 Latin Small Letter A with Ring Above + U+00E4 Latin Small Letter A with Diaeresis + U+00F6 Latin Small Letter O with Diaeresis + U+00E5 Latin Small Letter A with Ring Above */ { "https://r\xc3\xa4ksm\xc3\xb6rg\xc3\xa5s.se", "https | [11] | [12] | [13] | xn--rksmrgs-5wao1o.se | " @@ -330,13 +334,13 @@ static const struct testcase get_parts_list[] = { "https | [11] | [12] | [13] | [30] | [15] | / | [16] | [17]", 0, CURLU_PUNYCODE, CURLUE_OK }, #endif - /* https://codepoints.net/U+2102 Double-Struck Capital C - https://codepoints.net/U+1d64 Latin Subscript Small Letter U - https://codepoints.net/U+24c7 Circled Latin Capital Letter R - https://codepoints.net/U+2112 Script Capital L - https://codepoints.net/U+3002 Ideographic Full Stop - https://codepoints.net/U+1d412 Mathematical Bold Capital S - https://codepoints.net/U+1f134 Squared Latin Capital Letter E + /* U+2102 Double-Struck Capital C + U+1D64 Latin Subscript Small Letter U + U+24C7 Circled Latin Capital Letter R + U+2112 Script Capital L + U+3002 Ideographic Full Stop + U+1D412 Mathematical Bold Capital S + U+1F134 Squared Latin Capital Letter E */ {"https://" "%e2%84%82%e1%b5%a4%e2%93%87%e2%84%92%e3%80%82%f0%9d%90%92%f0%9f%84%b4", @@ -413,15 +417,15 @@ static const struct testcase get_parts_list[] = { #endif {"https://user:password@example.net/get?this=and#but frag then", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_FRAGMENT}, {"https://user:password@example.net/get?this=and what", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_QUERY}, {"https://user:password@example.net/ge t?this=and-what", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_PATH}, {"https://user:pass word@example.net/get?this=and-what", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_PASSWORD}, {"https://u ser:password@example.net/get?this=and-what", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_USER}, {"imap://user:pass;opt ion@server/path", "", CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, /* no space allowed in scheme */ @@ -644,9 +648,9 @@ static const struct testcase get_parts_list[] = { "https | [11] | [12] | [13] | 127abc.com | [15] | / | [16] | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, {"https:// example.com?check", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_HOSTNAME}, {"https://e x a m p l e.com?check", "", - CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_HOSTNAME}, {"https://example.com?check", "https | [11] | [12] | [13] | example.com | [15] | / | check | [17]", CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, @@ -732,6 +736,10 @@ static const struct urltestcase get_url_list[] = { {"https://[fe80%3A%3A20c%3A29ff%3Afe9c%3A409b]/", "https://[fe80::20c:29ff:fe9c:409b]/", 0, 0, CURLUE_OK }, + /* URL encoded [brackets] */ + {"https://%5bfe80%3A%3A20c%3A29ff%3Afe9c%3A409b%5c/", + "", 0, 0, CURLUE_BAD_HOSTNAME }, + /* IPvFuture format */ {"http://[v1.fe80::abcd]/", "", 0, 0, CURLUE_BAD_IPV6}, @@ -882,6 +890,18 @@ static const struct urltestcase get_url_list[] = { {"https://[::%25fakeit];80/moo", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, + {"https://[::%25fa keit]:80/moo", + "", + 0, 0, CURLUE_BAD_IPV6}, + {"https://[::%25fa%\x7fkeit]:80/moo", + "", + 0, 0, CURLUE_BAD_IPV6}, + {"https://[::%25fa keit]:80/moo", + "", + CURLU_ALLOW_SPACE, 0, CURLUE_BAD_IPV6}, + {"https://[::%25fake\tit]:80/moo", + "", + 0, 0, CURLUE_BAD_HOSTNAME}, {"https://[fe80::20c:29ff:fe9c:409b]-80/moo", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, @@ -929,7 +949,7 @@ static const struct urltestcase get_url_list[] = { {"https://\xff.127.0.0.1", "https://%FF.127.0.0.1/", 0, CURLU_URLENCODE, CURLUE_OK}, {"https://127.-0.0.1", "https://127.-0.0.1/", 0, 0, CURLUE_OK}, - {"https://127.0. 1", "https://127.0.0.1/", 0, 0, CURLUE_MALFORMED_INPUT}, + {"https://127.0. 1", "https://127.0.0.1/", 0, 0, CURLUE_BAD_HOSTNAME}, {"https://1.2.3.256", "https://1.2.3.256/", 0, 0, CURLUE_OK}, {"https://1.2.3.256.", "https://1.2.3.256./", 0, 0, CURLUE_OK}, {"https://1.2.3.4.5", "https://1.2.3.4.5/", 0, 0, CURLUE_OK}, @@ -1024,14 +1044,18 @@ static const struct urltestcase get_url_list[] = { CURLU_GUESS_SCHEME, 0, CURLUE_OK}, {"HTTP://test/", "http://test/", 0, 0, CURLUE_OK}, {"http://HO0_-st..~./", "http://HO0_-st..~./", 0, 0, CURLUE_OK}, - {"http:/@example.com: 123/", "", 0, 0, CURLUE_MALFORMED_INPUT}, - {"http:/@example.com:123 /", "", 0, 0, CURLUE_MALFORMED_INPUT}, + {"http:/@example.com: 123/", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, + {"http:/@example.com:123 /", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, {"http:/@example.com:123a/", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, - {"http://host/file\r", "", 0, 0, CURLUE_MALFORMED_INPUT}, - {"http://host/file\n\x03", "", 0, 0, CURLUE_MALFORMED_INPUT}, + {"http://host/file\r", "", 0, 0, CURLUE_BAD_PATH}, + {"http://host/file?\r", "", 0, 0, CURLUE_BAD_QUERY}, + {"http://host/file?hello#\r", "", 0, 0, CURLUE_BAD_FRAGMENT}, + {"http://\r:hej@host/file", "", 0, 0, CURLUE_BAD_USER}, + {"http://hej:\r@host/file", "", 0, 0, CURLUE_BAD_PASSWORD}, + {"http://host/file\n\x03", "", 0, 0, CURLUE_BAD_PATH}, {"htt\x02://host/file", "", - CURLU_NON_SUPPORT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, - {" http://host/file", "", 0, 0, CURLUE_MALFORMED_INPUT}, + CURLU_NON_SUPPORT_SCHEME, 0, CURLUE_BAD_SCHEME}, + {" http://host/file", "", 0, 0, CURLUE_BAD_SCHEME}, /* here the password ends at the semicolon and options is 'word' */ {"imap://user:pass;word@host/file", "imap://user:pass;word@host/file", @@ -1273,7 +1297,7 @@ static const struct setcase set_parts_list[] = { {"https://example.com/", "host=0xff,", /* '++' there is no automatic URL decode when setting this part */ - "https://0xff/", + "https://0.0.0.255/", 0, /* get */ 0, /* set */ CURLUE_OK, CURLUE_OK}, @@ -1785,13 +1809,11 @@ static int setget_parts(bool has_utf8) (int)uc, (int)setget_parts_list[i].pcode); error++; } - if(!uc) { - if(checkparts(urlp, - setget_parts_list[i].set, - setget_parts_list[i].out, - setget_parts_list[i].getflags)) - error++; /* add */ - } + if(!uc && checkparts(urlp, + setget_parts_list[i].set, + setget_parts_list[i].out, + setget_parts_list[i].getflags)) + error++; /* add */ } else if(rc != CURLUE_OK) { curl_mfprintf(stderr, "Set parts\nin: %s\nreturned %d (expected %d)\n", @@ -2401,6 +2423,9 @@ static int test_api_errors(void) if(rc != CURLUE_BAD_HANDLE) return 1; + if(curl_url_dup(NULL)) + return 4; + /* NULL part pointer */ rc = curl_url_get(u, CURLUPART_URL, NULL, 0); if(rc != CURLUE_BAD_PARTPOINTER) diff --git a/tests/unit/unit1605.c b/tests/libtest/lib1605.c similarity index 97% rename from tests/unit/unit1605.c rename to tests/libtest/lib1605.c index baeb121085ef..da8109cc711a 100644 --- a/tests/unit/unit1605.c +++ b/tests/libtest/lib1605.c @@ -43,7 +43,7 @@ static void t1605_stop(CURL *easy) curl_global_cleanup(); } -static CURLcode test_unit1605(const char *arg) +static CURLcode test_lib1605(const char *arg) { CURL *easy; diff --git a/tests/libtest/lib1616.c b/tests/libtest/lib1616.c new file mode 100644 index 000000000000..328869b52f3b --- /dev/null +++ b/tests/libtest/lib1616.c @@ -0,0 +1,104 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * argv1 = URL fetched through the proxy + * argv2 = proxy host + * argv3 = proxy port + * argv4 = test number + */ + +#include "first.h" + +static CURLcode test_lib1616(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + char proxy[128]; + char direct[256]; + char tunneled[256]; + + if(test_argc < 4) + return TEST_ERR_MAJOR_BAD; + + curl_msnprintf(proxy, sizeof(proxy), "http://%s:%s", + libtest_arg2, libtest_arg3); + curl_msnprintf(direct, sizeof(direct), "http://%s:%s/%s0002", + libtest_arg2, libtest_arg3, libtest_arg4); + curl_msnprintf(tunneled, sizeof(tunneled), + "http://test.remote.example.com:%s/%s0004", + libtest_arg3, libtest_arg4); + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + start_test_timing(); + + easy_setopt(curl, CURLOPT_PROXY, proxy); + easy_setopt(curl, CURLOPT_NOPROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXYAUTH, (long)CURLAUTH_DIGEST); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "silly:person"); + + easy_setopt(curl, CURLOPT_URL, URL); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* the excluded host is reached directly and answers 407 */ + easy_setopt(curl, CURLOPT_URL, direct); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* the same proxy as before must not get a Proxy-Authorization header */ + easy_setopt(curl, CURLOPT_URL, URL); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* an origin reached through a CONNECT tunnel answers 407 as well */ + easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); + easy_setopt(curl, CURLOPT_URL, tunneled); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* and that must not reach the proxy either */ + easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 0L); + easy_setopt(curl, CURLOPT_URL, URL); + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib1678.c b/tests/libtest/lib1678.c index a2d43c02e0e0..d754f32118d6 100644 --- a/tests/libtest/lib1678.c +++ b/tests/libtest/lib1678.c @@ -130,6 +130,32 @@ static CURLcode test_lib1678(const char *URL) if(result) goto test_cleanup; + if(curl_easy_ssls_import(NULL, "example.test:443", NULL, 0, packet, + packet_len) != CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + if(curl_easy_ssls_import(easy, "example.test:443", NULL, 0, NULL, + packet_len) != CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + if(curl_easy_ssls_import(easy, "example.test:443", NULL, 0, packet, 0) != + CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + if(curl_easy_ssls_export(NULL, NULL, NULL) != + CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + if(curl_easy_ssls_export(easy, NULL, NULL) != + CURLE_BAD_FUNCTION_ARGUMENT) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto test_cleanup; + } + for(i = 0; i < T1678_IMPORT_COUNT; ++i) { result = curl_easy_ssls_import(easy, "example.test:443", diff --git a/tests/libtest/lib1742.c b/tests/libtest/lib1742.c new file mode 100644 index 000000000000..318478f8cf8f --- /dev/null +++ b/tests/libtest/lib1742.c @@ -0,0 +1,72 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * argv1 = URL + * argv2 = SOCKS5 proxy + */ + +#include "first.h" + +static CURLcode test_lib1742(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + + if(test_argc < 2) + return TEST_ERR_MAJOR_BAD; + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + if(curl_easy_setopt(curl, CURLOPT_SOCKS5_AUTH, (long)CURLAUTH_ONLY) != + CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "CURLAUTH_ONLY without a method was accepted\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "silly:person"); + easy_setopt(curl, CURLOPT_SOCKS5_AUTH, + (long)(CURLAUTH_BASIC | CURLAUTH_ONLY)); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib1744.c b/tests/libtest/lib1744.c new file mode 100644 index 000000000000..f644aa672a9a --- /dev/null +++ b/tests/libtest/lib1744.c @@ -0,0 +1,71 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * argv1 = URL + * argv2 = SOCKS5 proxy + */ + +#include "first.h" + +static CURLcode test_lib1744(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + + if(test_argc < 2) + return TEST_ERR_MAJOR_BAD; + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "silly:person"); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + /* the proxy asks for no authentication and the connection is kept */ + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* that connection must not be reused now that auth is required */ + easy_setopt(curl, CURLOPT_SOCKS5_AUTH, + (long)(CURLAUTH_BASIC | CURLAUTH_ONLY)); + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib1745.c b/tests/libtest/lib1745.c new file mode 100644 index 000000000000..7126ab5bfa7a --- /dev/null +++ b/tests/libtest/lib1745.c @@ -0,0 +1,74 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ +/* + * argv1 = FTP URL + * argv2 = SOCKS5 proxy + */ + +#include "first.h" + +static CURLcode test_lib1745(const char *URL) +{ + CURLcode result = CURLE_OK; + CURL *curl = NULL; + + if(test_argc < 2) + return TEST_ERR_MAJOR_BAD; + + res_global_init(CURL_GLOBAL_ALL); + if(result) + return result; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + start_test_timing(); + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_PROXY, libtest_arg2); + easy_setopt(curl, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5); + easy_setopt(curl, CURLOPT_PROXYUSERPWD, "silly:person"); + + /* the proxy picks no authentication for the control connection and + username/password for the data connection */ + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* the control connection did not authenticate, so it must not be + reused for this one */ + easy_setopt(curl, CURLOPT_SOCKS5_AUTH, + (long)(CURLAUTH_BASIC | CURLAUTH_ONLY)); + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib1791.c b/tests/libtest/lib1791.c new file mode 100644 index 000000000000..6c95b1890e3d --- /dev/null +++ b/tests/libtest/lib1791.c @@ -0,0 +1,59 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +static CURLcode test_lib1791(const char *URL) +{ + CURL *curl; + CURL *copy; + CURLcode result = CURLE_OK; + (void)URL; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + (void)curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)0); + (void)curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, ""); + + copy = curl_easy_duphandle(curl); + + if(copy) + curl_easy_cleanup(copy); + else + result = TEST_ERR_FAILURE; + + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} diff --git a/tests/libtest/lib1989.c b/tests/libtest/lib1989.c new file mode 100644 index 000000000000..ef331b53d3b8 --- /dev/null +++ b/tests/libtest/lib1989.c @@ -0,0 +1,67 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "unitcheck.h" +#include "urldata.h" +#include "curl/urlapi.h" + +static CURLcode test_lib1989(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + + CURLU *u = NULL; + CURLU *dup = NULL; + CURLUcode uc; + char *scheme = NULL; + + /* Parse a scheme-less URL, letting the scheme be guessed */ + u = curl_url(); + if(!u) + goto fail; + uc = curl_url_set(u, CURLUPART_URL, "example.com/path", CURLU_GUESS_SCHEME); + fail_unless(uc == CURLUE_OK, "curl_url_set with CURLU_GUESS_SCHEME failed"); + + /* The original handle should reject the scheme with CURLU_NO_GUESS_SCHEME + since the scheme was only guessed, not explicit */ + uc = curl_url_get(u, CURLUPART_SCHEME, &scheme, CURLU_NO_GUESS_SCHEME); + fail_unless(uc == CURLUE_NO_SCHEME, + "original handle did not reject guessed scheme"); + curl_free(scheme); + scheme = NULL; + + dup = curl_url_dup(u); + if(!dup) + goto fail; + + /* The duplicate must still remember that the scheme was guessed */ + uc = curl_url_get(dup, CURLUPART_SCHEME, &scheme, CURLU_NO_GUESS_SCHEME); + fail_unless(uc == CURLUE_NO_SCHEME, + "duplicated handle did not reject guessed scheme"); + curl_free(scheme); + +fail: + curl_url_cleanup(dup); + curl_url_cleanup(u); + + UNITTEST_END_SIMPLE +} diff --git a/tests/libtest/lib2095.c b/tests/libtest/lib2095.c new file mode 100644 index 000000000000..e1575112c9d8 --- /dev/null +++ b/tests/libtest/lib2095.c @@ -0,0 +1,148 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ + +/* CURLOPT_SSLCERT_BLOB / CURLOPT_SSLKEY_BLOB -- in-memory client auth */ + +#include "first.h" + +static int t2095_loadfile(const char *filename, + void **filedata, + size_t *filesize) +{ + size_t datasize = 0; + void *data = NULL; + if(filename) { + FILE *fIn = curlx_fopen(filename, "rb"); + + if(fIn) { + long tell = 0; + bool ok = fseek(fIn, 0, SEEK_END) == 0; + if(ok) + tell = ftell(fIn); + if(tell < 0) + ok = FALSE; + else + datasize = (size_t)tell; + if(ok) + ok = fseek(fIn, 0, SEEK_SET) == 0; + if(ok) + data = curlx_malloc(datasize + 1); + if(!data || ((int)fread(data, datasize, 1, fIn) != 1)) + ok = FALSE; + curlx_fclose(fIn); + if(!ok) { + curlx_safefree(data); + datasize = 0; + } + } + } + *filesize = datasize; + *filedata = data; + return data ? 1 : 0; +} + +static CURLcode t2095_cert_blob(const char *url, + const char *cafile, + const char *certfile, + const char *keyfile) +{ + CURLcode result = CURLE_OUT_OF_MEMORY; + CURL *curl; + struct curl_blob blob; + size_t sz; + void *data; + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + return CURLE_FAILED_INIT; + } + + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_HEADER, 1L); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "CURLOPT_SSLCERT_BLOB"); + + if(t2095_loadfile(cafile, &data, &sz)) { + blob.data = data; + blob.len = sz; + blob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob); + curlx_free(data); + } + + result = CURLE_OUT_OF_MEMORY; + if(t2095_loadfile(certfile, &data, &sz)) { + blob.data = data; + blob.len = sz; + blob.flags = CURL_BLOB_COPY; + result = curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &blob); + curlx_free(data); + } + if(result) + goto cleanup; + + if(t2095_loadfile(keyfile, &data, &sz)) { + blob.data = data; + blob.len = sz; + blob.flags = CURL_BLOB_COPY; + result = curl_easy_setopt(curl, CURLOPT_SSLKEY_BLOB, &blob); + curlx_free(data); + } + if(result) + goto cleanup; + + result = curl_easy_perform(curl); + +cleanup: + curl_easy_cleanup(curl); + return result; +} + +static CURLcode test_lib2095(const char *URL) +{ + CURLcode result = CURLE_OK; + curl_global_init(CURL_GLOBAL_ALL); + if(!strcmp("check", URL)) { + CURLcode w = CURLE_OK; + struct curl_blob blob = { CURL_UNCONST("x"), 1, 0 }; + CURL *curl = curl_easy_init(); + if(curl) { + w = curl_easy_setopt(curl, CURLOPT_SSLCERT_BLOB, &blob); + if(w) + curl_mprintf("CURLOPT_SSLCERT_BLOB is not supported\n"); + else { + w = curl_easy_setopt(curl, CURLOPT_SSLKEY_BLOB, &blob); + if(w) + curl_mprintf("CURLOPT_SSLKEY_BLOB is not supported\n"); + } + curl_easy_cleanup(curl); + } + result = w; + } + else + result = t2095_cert_blob(URL, libtest_arg2, libtest_arg3, libtest_arg4); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib2270.c b/tests/libtest/lib2270.c new file mode 100644 index 000000000000..17ec606c91a7 --- /dev/null +++ b/tests/libtest/lib2270.c @@ -0,0 +1,104 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +static CURLcode test_lib2270(const char *URL) +{ + CURL *curl = NULL; + curl_mime *mime = NULL; + curl_mimepart *part; + struct curl_httppost *formpost = NULL; + struct curl_httppost *lastptr = NULL; + CURLFORMcode formrc; + CURLcode result = TEST_ERR_MAJOR_BAD; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + goto test_cleanup; + } + + mime = curl_mime_init(curl); + if(!mime) { + curl_mfprintf(stderr, "curl_mime_init() failed\n"); + goto test_cleanup; + } + part = curl_mime_addpart(mime); + if(!part) { + curl_mfprintf(stderr, "curl_mime_addpart() failed\n"); + goto test_cleanup; + } + result = curl_mime_name(part, "field"); + if(result) + goto test_cleanup; + result = curl_mime_data(part, "value", CURL_ZERO_TERMINATED); + if(result) + goto test_cleanup; + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_MIMEPOST, mime); + easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)1); + result = curl_easy_perform(curl); + if(result != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "MIME resume returned %d, expected %d\n", + (int)result, CURLE_BAD_FUNCTION_ARGUMENT); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_setopt(curl, CURLOPT_MIMEPOST, NULL); + formrc = curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "field", + CURLFORM_COPYCONTENTS, "value", + CURLFORM_END); + if(formrc) { + curl_mfprintf(stderr, "curl_formadd() failed with code %d\n", + (int)formrc); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)-1); + result = curl_easy_perform(curl); + if(result != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "form resume returned %d, expected %d\n", + (int)result, CURLE_BAD_FUNCTION_ARGUMENT); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + result = CURLE_OK; + +test_cleanup: + curl_easy_cleanup(curl); + curl_mime_free(mime); + curl_formfree(formpost); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib2380.c b/tests/libtest/lib2380.c new file mode 100644 index 000000000000..e3d05809de51 --- /dev/null +++ b/tests/libtest/lib2380.c @@ -0,0 +1,88 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" +#ifdef HAVE_SYS_RESOURCE_H +#include /* RLIMIT_FSIZE */ +#endif +#include /* SIGXFSZ */ + +#if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) && \ + defined(RLIMIT_FSIZE) && defined(SIGXFSZ) +/* set a tiny limit to trigger easily */ +#define T2380_FSIZE_LIMIT 50 +#endif + +static CURLcode test_lib2380(const char *URL) +{ + CURLcode result = CURLE_OK; +#ifdef T2380_FSIZE_LIMIT + struct rlimit rl_orig; + struct rlimit rl; + FILE *f; + + if(getrlimit(RLIMIT_FSIZE, &rl_orig)) { + curl_mfprintf(stderr, "getrlimit() failed\n"); + goto test_cleanup; + } + signal(SIGXFSZ, SIG_IGN); + rl = rl_orig; + rl.rlim_cur = T2380_FSIZE_LIMIT; + if(setrlimit(RLIMIT_FSIZE, &rl)) { + curl_mfprintf(stderr, "setrlimit() failed\n"); + goto test_cleanup; + } + + f = curlx_fopen(URL, "wb"); + if(f) { + int i; + int n; + + /* switch off stream buffering */ + setvbuf(f, NULL, _IONBF, 0); + + /* this should fail already in lap 1 */ + for(i = 0; i < 4; i++) { + n = curl_mfprintf(f, + "012345678901234567890123456789012345678901234567\n"); + if(n == -1) { + curl_mfprintf(stderr, "error on lap %d\n", i); + break; + } + } + if(i != 1) { + curl_mfprintf(stderr, "got loop %d instead of the expected 1\n", i); + result = TEST_ERR_FAILURE; + } + curlx_fclose(f); + } + + /* restore the original value */ + setrlimit(RLIMIT_FSIZE, &rl_orig); + +test_cleanup: +#else + (void)URL; /* unused */ +#endif + return result; +} diff --git a/tests/libtest/lib2453.c b/tests/libtest/lib2453.c new file mode 100644 index 000000000000..ace5f034629e --- /dev/null +++ b/tests/libtest/lib2453.c @@ -0,0 +1,118 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +static CURLcode expect_xfers_done(CURLM *multi, curl_off_t expected) +{ + curl_off_t value = -1; + CURLMcode mresult = curl_multi_get_offt(multi, CURLMINFO_XFERS_DONE, + &value); + + if(mresult != CURLM_OK) { + curl_mfprintf(stderr, "curl_multi_get_offt() failed with code %d\n", + (int)mresult); + return TEST_ERR_MULTI; + } + if(value != expected) { + curl_mfprintf(stderr, + "Expected %" CURL_FORMAT_CURL_OFF_T + " unread completion messages, got %" + CURL_FORMAT_CURL_OFF_T "\n", + expected, value); + return TEST_ERR_FAILURE; + } + return CURLE_OK; +} + +static CURLcode test_lib2453(const char *URL) +{ + CURL *curl = NULL; + CURLM *multi = NULL; + CURLcode result = CURLE_OK; + CURLMsg *msg; + int msgs_left; + int still_running; + + start_test_timing(); + global_init(CURL_GLOBAL_ALL); + multi_init(multi); + easy_init(curl); + + result = expect_xfers_done(multi, 0); + if(result) + goto test_cleanup; + + easy_setopt(curl, CURLOPT_URL, URL); + multi_add_handle(multi, curl); + + do { + CURLMcode mresult; + int numfds; + + multi_perform(multi, &still_running); + if(!still_running) + break; + + mresult = curl_multi_poll(multi, NULL, 0, TEST_HANG_TIMEOUT, &numfds); + if(mresult != CURLM_OK) { + curl_mfprintf(stderr, "curl_multi_poll() failed with code %d\n", + (int)mresult); + result = TEST_ERR_MULTI; + goto test_cleanup; + } + abort_on_test_timeout(); + } while(still_running); + + result = expect_xfers_done(multi, 1); + if(result) + goto test_cleanup; + + msg = curl_multi_info_read(multi, &msgs_left); + if(!msg || msg->msg != CURLMSG_DONE || msg->easy_handle != curl || + msg->data.result != CURLE_OK || msgs_left) { + curl_mfprintf(stderr, "Unexpected completion message\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + result = expect_xfers_done(multi, 0); + if(result) + goto test_cleanup; + + multi_remove_handle(multi, curl); + multi_perform(multi, &still_running); + if(still_running) { + curl_mfprintf(stderr, "Completed transfer still counted as running\n"); + result = TEST_ERR_FAILURE; + } + +test_cleanup: + if(multi && curl) + curl_multi_remove_handle(multi, curl); + curl_easy_cleanup(curl); + curl_multi_cleanup(multi); + curl_global_cleanup(); + + return result; +} diff --git a/tests/libtest/lib2805.c b/tests/libtest/lib2805.c new file mode 100644 index 000000000000..7e2c8bb38a0b --- /dev/null +++ b/tests/libtest/lib2805.c @@ -0,0 +1,65 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +static int fnmatch2805(void *ptr, const char *pattern, const char *string) +{ + (void)ptr; + curl_mprintf("match: '%s' against pattern '%s'\n", string, pattern); + return CURL_FNMATCHFUNC_MATCH; +} + +static CURLcode test_lib2805(const char *URL) +{ + CURLcode result; + CURL *curl; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_WILDCARDMATCH, 1L); + easy_setopt(curl, CURLOPT_FNMATCH_FUNCTION, fnmatch2805); + easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long)TEST_HANG_TIMEOUT); + + result = curl_easy_perform(curl); + if(result) { + curl_mfprintf(stderr, "curl_easy_perform() failed %d\n", (int)result); + goto test_cleanup; + } + +test_cleanup: + curl_easy_cleanup(curl); + curl_global_cleanup(); + return result; +} diff --git a/lib/smb.h b/tests/libtest/lib2924.c similarity index 70% rename from lib/smb.h rename to tests/libtest/lib2924.c index 5a92ad13db3d..81ba4697e465 100644 --- a/lib/smb.h +++ b/tests/libtest/lib2924.c @@ -1,5 +1,3 @@ -#ifndef HEADER_CURL_SMB_H -#define HEADER_CURL_SMB_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,7 +5,6 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) Bill Nagel , Exacq Technologies * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which @@ -24,8 +21,27 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ -#if defined(CURL_ENABLE_SMB) && defined(USE_CURL_NTLM_CORE) -extern const struct Curl_protocol Curl_protocol_smb; -#endif +#include "first.h" -#endif /* HEADER_CURL_SMB_H */ +static CURLcode test_lib2924(const char *URL) +{ + CURL *curl; + CURLcode result = CURLE_OK; + + global_init(CURL_GLOBAL_ALL); + + easy_init(curl); + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_HEADER, 1L); + easy_setopt(curl, CURLOPT_HTTP_TRANSFER_DECODING, 0L); + + result = curl_easy_perform(curl); + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} diff --git a/tests/libtest/lib3102.c b/tests/libtest/lib3102.c index b3af3981a168..5346b26dc4a3 100644 --- a/tests/libtest/lib3102.c +++ b/tests/libtest/lib3102.c @@ -60,16 +60,15 @@ static bool is_chain_in_order(struct curl_certinfo *cert_info) curl_mprintf(" subject: %s\n", subject); curl_mprintf(" issuer: %s\n", issuer); - if(last_issuer) { - /* If the last certificate's issuer matches the current certificate's - * subject, then the chain is in order */ - if(strcmp(last_issuer, subject)) { - curl_mfprintf(stderr, - "cert %d issuer does not match cert %d subject\n", - cert - 1, cert); - curl_mfprintf(stderr, "certificate chain is not in order\n"); - return FALSE; - } + if(last_issuer && + /* If the last certificate's issuer matches the current certificate's + * subject, then the chain is in order */ + strcmp(last_issuer, subject)) { + curl_mfprintf(stderr, + "cert %d issuer does not match cert %d subject\n", + cert - 1, cert); + curl_mfprintf(stderr, "certificate chain is not in order\n"); + return FALSE; } } @@ -116,11 +115,9 @@ static CURLcode test_lib3102(const char *URL) struct curl_certinfo *cert_info = NULL; /* Get the certificate information */ result = curl_easy_getinfo(curl, CURLINFO_CERTINFO, &cert_info); - if(!result) { - /* Check to see if the certificate chain is ordered correctly */ - if(!is_chain_in_order(cert_info)) - result = TEST_ERR_FAILURE; - } + /* Check to see if the certificate chain is ordered correctly */ + if(!result && !is_chain_in_order(cert_info)) + result = TEST_ERR_FAILURE; } test_cleanup: diff --git a/tests/libtest/lib3107.c b/tests/libtest/lib3107.c new file mode 100644 index 000000000000..6ae3f3e11759 --- /dev/null +++ b/tests/libtest/lib3107.c @@ -0,0 +1,105 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +static int t3107_notified; +static CURLMcode t3107_mres; +static CURLcode t3107_eres; + +static void t3107_notify_cb(CURLM *multi, unsigned int notification, + CURL *easy, void *user_data) +{ + (void)user_data; + if(notification != CURLMNOTIFY_INFO_READ) + return; + t3107_notified++; + t3107_mres = curl_multi_remove_handle(multi, easy); + t3107_eres = curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L); + curl_easy_cleanup(easy); +} + +static CURLcode test_lib3107(const char *URL) +{ + CURL *curl = NULL; + CURLM *multi = NULL; + CURLcode result = CURLE_OK; + CURLMsg *msg; + int running = 1; + int queued; + + global_init(CURL_GLOBAL_ALL); + + multi_init(multi); + multi_setopt(multi, CURLMOPT_NOTIFYFUNCTION, t3107_notify_cb); + + if(curl_multi_notify_enable(multi, CURLMNOTIFY_INFO_READ) != CURLM_OK) { + curl_mfprintf(stderr, "curl_multi_notify_enable() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + + easy_init(curl); + easy_setopt(curl, CURLOPT_URL, URL); + + multi_add_handle(multi, curl); + + while(running) { + CURLMcode mres = curl_multi_perform(multi, &running); + if(!mres && running) + mres = curl_multi_poll(multi, NULL, 0, 1000, NULL); + if(mres) { + curl_mfprintf(stderr, "curl_multi failed, with code %d (%s)\n", + (int)mres, curl_multi_strerror(mres)); + result = TEST_ERR_MULTI; + goto test_cleanup; + } + } + + do { + msg = curl_multi_info_read(multi, &queued); + } while(msg); + + if(!t3107_notified) { + curl_mfprintf(stderr, "no CURLMNOTIFY_INFO_READ notification\n"); + result = TEST_ERR_MAJOR_BAD; + } + else if(t3107_mres != CURLM_BAD_EASY_HANDLE) { + curl_mfprintf(stderr, "curl_multi_remove_handle() returned %d\n", + (int)t3107_mres); + result = TEST_ERR_MAJOR_BAD; + } + else if(t3107_eres != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "curl_easy_setopt() returned %d\n", + (int)t3107_eres); + result = TEST_ERR_MAJOR_BAD; + } + +test_cleanup: + curl_multi_remove_handle(multi, curl); + curl_multi_cleanup(multi); + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} diff --git a/tests/libtest/lib3231.c b/tests/libtest/lib3231.c new file mode 100644 index 000000000000..6fb4fc94b7c5 --- /dev/null +++ b/tests/libtest/lib3231.c @@ -0,0 +1,131 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +static CURLcode connect_only_complete(CURLM *multi, CURL *easy) +{ + CURLcode result = CURLE_OK; + CURLMsg *msg; + int running; + int msgs_left; + int numfds; + + do { + multi_perform(multi, &running); + abort_on_test_timeout(); + if(running) + multi_poll(multi, NULL, 0, 1000, &numfds); + } while(running); + + msg = curl_multi_info_read(multi, &msgs_left); + if(!msg || msg->msg != CURLMSG_DONE || msg->easy_handle != easy || + msg->data.result != CURLE_OK || msgs_left) { + curl_mfprintf(stderr, "Unexpected transfer completion\n"); + result = TEST_ERR_FAILURE; + } + +test_cleanup: + return result; +} + +static CURLcode test_lib3231(const char *URL) +{ + CURL *connect_only = NULL; + CURL *http = NULL; + CURLM *multi = NULL; + CURLcode result = CURLE_OK; + curl_socket_t sock = CURL_SOCKET_BAD; + curl_off_t pending; + int running; + + start_test_timing(); + global_init(CURL_GLOBAL_ALL); + multi_init(multi); + if(testnum == 3232) + multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, 1L); + else + multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, 1L); + + easy_init(connect_only); + easy_setopt(connect_only, CURLOPT_URL, URL); + easy_setopt(connect_only, CURLOPT_CONNECT_ONLY, 1L); + easy_setopt(connect_only, CURLOPT_VERBOSE, 1L); + multi_add_handle(multi, connect_only); + + result = connect_only_complete(multi, connect_only); + if(result) + goto test_cleanup; + + result = curl_easy_getinfo(connect_only, CURLINFO_ACTIVESOCKET, &sock); + if(result) + goto test_cleanup; + if(sock == CURL_SOCKET_BAD) { + curl_mfprintf(stderr, "CONNECT_ONLY did not retain its socket\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + /* Do not send or receive on connect_only before competing for its slot: + the completed transfer has detached, but the application still owns it. */ + easy_init(http); + easy_setopt(http, CURLOPT_URL, URL); + easy_setopt(http, CURLOPT_VERBOSE, 1L); + multi_add_handle(multi, http); + multi_perform(multi, &running); + + result = curl_easy_getinfo(connect_only, CURLINFO_ACTIVESOCKET, &sock); + if(result) + goto test_cleanup; + if(sock == CURL_SOCKET_BAD) { + curl_mfprintf(stderr, + "Connection limit evicted the CONNECT_ONLY socket\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + if(curl_multi_get_offt(multi, CURLMINFO_XFERS_PENDING, &pending) || + pending != 1) { + curl_mfprintf(stderr, + "HTTP transfer did not wait for a connection slot\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + /* Releasing the application-owned connection must let HTTP proceed. */ + multi_remove_handle(multi, connect_only); + curl_easy_cleanup(connect_only); + connect_only = NULL; + result = connect_only_complete(multi, http); + +test_cleanup: + if(multi && connect_only) + curl_multi_remove_handle(multi, connect_only); + if(multi && http) + curl_multi_remove_handle(multi, http); + curl_easy_cleanup(connect_only); + curl_easy_cleanup(http); + curl_multi_cleanup(multi); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib3233.c b/tests/libtest/lib3233.c new file mode 100644 index 000000000000..b411e60f5388 --- /dev/null +++ b/tests/libtest/lib3233.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +/* + * A session id learned from one origin must not be sent to another one + * after a redirect. + */ +static CURLcode test_lib3233(const char *URL) +{ + CURLcode result; + CURL *curl; + struct curl_slist *resolve = NULL; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + resolve = curl_slist_append(resolve, libtest_arg2); + + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); + easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, "rtsp"); + easy_setopt(curl, CURLOPT_RESOLVE, resolve); + + result = curl_easy_perform(curl); + +test_cleanup: + + curl_slist_free_all(resolve); + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +} diff --git a/tests/libtest/lib3234.c b/tests/libtest/lib3234.c new file mode 100644 index 000000000000..2d9fb9f8367c --- /dev/null +++ b/tests/libtest/lib3234.c @@ -0,0 +1,110 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +static CURLcode test_lib3234(const char *URL) +{ + CURL *easy_a = NULL; + CURL *easy_b = NULL; + CURLSH *share = NULL; + CURLSHcode shresult; + CURLcode result = CURLE_OK; + curl_socket_t socket_a = CURL_SOCKET_BAD; + curl_socket_t socket_b = CURL_SOCKET_BAD; + curl_socket_t socket_after = CURL_SOCKET_BAD; + curl_off_t conn_id = -1; + size_t sent = 0; + + global_init(CURL_GLOBAL_ALL); + + share = curl_share_init(); + if(!share) { + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + shresult = curl_share_setopt(share, CURLSHOPT_SHARE, + CURL_LOCK_DATA_CONNECT); + if(shresult != CURLSHE_OK) { + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_init(easy_a); + easy_setopt(easy_a, CURLOPT_URL, URL); + easy_setopt(easy_a, CURLOPT_CONNECT_ONLY, 1L); + result = curl_easy_perform(easy_a); + if(result) + goto test_cleanup; + result = curl_easy_getinfo(easy_a, CURLINFO_ACTIVESOCKET, &socket_a); + if(result) + goto test_cleanup; + + easy_init(easy_b); + easy_setopt(easy_b, CURLOPT_SHARE, share); + easy_setopt(easy_b, CURLOPT_URL, URL); + easy_setopt(easy_b, CURLOPT_CONNECT_ONLY, 1L); + result = curl_easy_perform(easy_b); + if(result) + goto test_cleanup; + result = curl_easy_getinfo(easy_b, CURLINFO_ACTIVESOCKET, &socket_b); + if(result) + goto test_cleanup; + + if(socket_a == CURL_SOCKET_BAD || socket_b == CURL_SOCKET_BAD || + socket_a == socket_b) { + curl_mfprintf(stderr, "failed to create two distinct connections\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + easy_setopt(easy_a, CURLOPT_SHARE, share); + result = curl_easy_getinfo(easy_a, CURLINFO_CONN_ID, &conn_id); + if(result) + goto test_cleanup; + result = curl_easy_getinfo(easy_a, CURLINFO_ACTIVESOCKET, &socket_after); + if(result) + goto test_cleanup; + + if(conn_id != -1 || socket_after != CURL_SOCKET_BAD) { + curl_mfprintf(stderr, "connection survived a connection pool change\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + result = curl_easy_send(easy_a, "x", 1, &sent); + if(result != CURLE_UNSUPPORTED_PROTOCOL || sent) { + curl_mfprintf(stderr, "raw send used a connection from the new pool\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + result = CURLE_OK; + +test_cleanup: + curl_easy_cleanup(easy_a); + curl_easy_cleanup(easy_b); + curl_share_cleanup(share); + curl_global_cleanup(); + return result; +} diff --git a/tests/libtest/lib3595.c b/tests/libtest/lib3595.c new file mode 100644 index 000000000000..1f343f503eef --- /dev/null +++ b/tests/libtest/lib3595.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "unitcheck.h" + +static CURLcode test_lib3595(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#if !defined(CURL_DISABLE_MIME) && \ + (!defined(CURL_DISABLE_HTTP) || \ + !defined(CURL_DISABLE_SMTP) || \ + !defined(CURL_DISABLE_IMAP)) + + curl_mime *root = NULL; + curl_mime *current = NULL; + curl_mime *sub = NULL; + curl_mimepart *part = NULL; + int i; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + /* Create a deeply nested mime structure to verify that non-recursive + curl_mime_free works properly. */ + (void)arg; + root = curl_mime_init(NULL); + fail_unless(root != NULL, "curl_mime_init root failed"); + current = root; + + for(i = 0; i < 50; i++) { + part = curl_mime_addpart(current); + fail_unless(part != NULL, "curl_mime_addpart failed"); + sub = curl_mime_init(NULL); + fail_unless(sub != NULL, "curl_mime_init sub failed"); + curl_mime_subparts(part, sub); + current = sub; + } + + /* Free deep structure */ + curl_mime_free(root); + curl_global_cleanup(); +#else + (void)arg; +#endif + + UNITTEST_END_SIMPLE +} diff --git a/tests/libtest/lib3596.c b/tests/libtest/lib3596.c new file mode 100644 index 000000000000..61b8f65f6d02 --- /dev/null +++ b/tests/libtest/lib3596.c @@ -0,0 +1,133 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "unitcheck.h" + +#if !defined(CURL_DISABLE_MIME) && !defined(CURL_DISABLE_HTTP) +static int callback_free_count = 0; + +static void test_cb_free(void *ptr) +{ + (void)ptr; + callback_free_count++; +} + +static size_t test_cb_read(char *buffer, size_t size, size_t nitems, void *arg) +{ + (void)buffer; + (void)size; + (void)nitems; + (void)arg; + return 0; +} +#endif + +static CURLcode test_lib3596(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#if !defined(CURL_DISABLE_MIME) && !defined(CURL_DISABLE_HTTP) + + CURL *curl = NULL; + CURLcode result = CURLE_OK; + curl_mime *root = NULL; + curl_mime *current = NULL; + curl_mime *sub = NULL; + curl_mimepart *part = NULL; + int i; + + callback_free_count = 0; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + fail_unless(curl != NULL, "curl_easy_init() failed"); + + /* Create a deeper than MAX_MIME_LEVELS nested mime structure to verify + that header preparation bounds recursion instead of exhausting the + stack. */ + root = curl_mime_init(curl); + fail_unless(root != NULL, "curl_mime_init root failed"); + + /* Add a sibling part with a custom free callback before the deep chain + to verify that preflight depth rejection does not duplicate callbacks + or invoke them during error rollback. */ + part = curl_mime_addpart(root); + fail_unless(part != NULL, "curl_mime_addpart sibling failed"); + result = curl_mime_data_cb(part, 0, test_cb_read, NULL, test_cb_free, NULL); + fail_unless(result == CURLE_OK, "curl_mime_data_cb failed"); + + current = root; + + for(i = 0; i < 50; i++) { + part = curl_mime_addpart(current); + fail_unless(part != NULL, "curl_mime_addpart failed"); + sub = curl_mime_init(curl); + fail_unless(sub != NULL, "curl_mime_init sub failed"); + curl_mime_subparts(part, sub); + current = sub; + } + + result = curl_easy_setopt(curl, CURLOPT_URL, arg); + fail_unless(result == CURLE_OK, "CURLOPT_URL failed"); + if(!result) + result = curl_easy_setopt(curl, CURLOPT_MIMEPOST, root); + fail_unless(result == CURLE_OK, "CURLOPT_MIMEPOST failed"); + + /* Deep nesting must fail gracefully (CURLE_TOO_LARGE), not crash, and + must not invoke copied user free callbacks during error rollback. */ + if(!result) { + CURL *dup = curl_easy_duphandle(curl); + fail_unless(dup == NULL, + "curl_easy_duphandle unexpectedly succeeded for nested mime"); + fail_unless(callback_free_count == 0, + "free callback invoked during failed duphandle rollback"); + } + + if(!result) + result = curl_easy_perform(curl); + fail_unless(result == CURLE_TOO_LARGE, + "deeply nested mime did not fail with CURLE_TOO_LARGE"); + + { + CURL *dup = curl_easy_duphandle(curl); + fail_unless(dup == NULL, + "curl_easy_duphandle unexpectedly succeeded after perform"); + fail_unless(callback_free_count == 0, + "free callback invoked after perform duphandle"); + } + + curl_easy_cleanup(curl); + curl_mime_free(root); + fail_unless(callback_free_count == 1, + "free callback not invoked during root cleanup"); + curl_global_cleanup(); +#else + (void)arg; +#endif + + UNITTEST_END_SIMPLE +} diff --git a/tests/libtest/lib464.c b/tests/libtest/lib464.c new file mode 100644 index 000000000000..0bb5d3910c1c --- /dev/null +++ b/tests/libtest/lib464.c @@ -0,0 +1,179 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 + * + ***************************************************************************/ + +/* + * Verify that the resolver-start callback (CURLOPT_RESOLVER_START_FUNCTION) + * is announced, and can veto the resolve, before the HTTPS RR side-query is + * sent out on the wire. This binds a UDP socket of its own, points the + * c-ares HTTPS RR channel at it via the CURL_DNS_SERVER debug hook, and + * makes the callback abort the moment it sees the HTTPS RR resolver state. + * If the query had already been dispatched, this local "DNS server" would + * have received it regardless of the callback's veto. + */ + +#include "first.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + +static int t464_cb_count = 0; +static int t464_https_seen = 0; + +static int t464_resolver_start_cb(void *resolver_state, void *reserved, + void *userdata) +{ + (void)reserved; + (void)userdata; + t464_cb_count++; + if(resolver_state) { + /* This is the HTTPS RR side-channel announce: veto it. */ + t464_https_seen = 1; + return 1; + } + /* This is the regular A/AAAA announce: let it proceed. */ + return 0; +} + +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK 0x7f000001 +#endif + +static CURLcode test_lib464(const char *URL) +{ + CURL *curl = NULL; + CURLcode result = CURLE_OK; + curl_socket_t dnssock = CURL_SOCKET_BAD; + struct sockaddr_in sa; + curl_socklen_t salen = sizeof(sa); + char envbuf[64]; + char inbuf[512]; + fd_set fds; + struct timeval tv; + int port; + int gotpacket = 0; + bool global_inited = FALSE; + + dnssock = CURL_SOCKET(AF_INET, SOCK_DGRAM, 0); + if(dnssock == CURL_SOCKET_BAD) { + curl_mfprintf(stderr, "socket creation error\n"); + return TEST_ERR_MAJOR_BAD; + } + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sa.sin_port = 0; + + if(bind(dnssock, (struct sockaddr *)&sa, sizeof(sa))) { + curl_mfprintf(stderr, "bind() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + if(getsockname(dnssock, (struct sockaddr *)&sa, &salen)) { + curl_mfprintf(stderr, "getsockname() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + port = ntohs(sa.sin_port); + curl_msnprintf(envbuf, sizeof(envbuf), "127.0.0.1:%d", port); +#ifdef _WIN32 + _putenv_s("CURL_DNS_SERVER", envbuf); +#else + setenv("CURL_DNS_SERVER", envbuf, 1); +#endif + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + global_inited = TRUE; + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + result = TEST_ERR_MAJOR_BAD; + goto test_cleanup; + } + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + easy_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, t464_resolver_start_cb); + + /* Vetoing the HTTPS RR side-query only drops that optional lookup, it + * does not fail the transfer: the plain A/AAAA resolve still succeeds + * and curl goes on to (fail to) connect, since nothing listens on the + * target port. */ + result = curl_easy_perform(curl); + if(result != CURLE_COULDNT_CONNECT) { + curl_mfprintf(stderr, "curl_easy_perform should have returned " + "CURLE_COULDNT_CONNECT but instead returned error %d\n", + (int)result); + if(result == CURLE_OK) + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + result = CURLE_OK; + + if(!t464_https_seen) { + curl_mfprintf(stderr, "the HTTPS RR resolver-start announce never " + "happened, this test needs asyn-rr support\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + if(t464_cb_count != 2) { + curl_mfprintf(stderr, "Unexpected number of callbacks: %d\n", + t464_cb_count); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + FD_ZERO(&fds); + FD_SET(dnssock, &fds); + tv.tv_sec = 0; + tv.tv_usec = 200000; + if(select((int)dnssock + 1, &fds, NULL, NULL, &tv) > 0) + gotpacket = (recvfrom(dnssock, inbuf, sizeof(inbuf), 0, NULL, NULL) > 0); + + if(gotpacket) { + curl_mfprintf(stderr, "the HTTPS RR query was sent on the wire before " + "the resolver-start callback could veto it\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + +test_cleanup: + curl_easy_cleanup(curl); + if(global_inited) + curl_global_cleanup(); + if(dnssock != CURL_SOCKET_BAD) + sclose(dnssock); + + return result; +} diff --git a/tests/libtest/lib517.c b/tests/libtest/lib517.c index 609a44a06b2b..aa15af6c9d61 100644 --- a/tests/libtest/lib517.c +++ b/tests/libtest/lib517.c @@ -31,6 +31,11 @@ static CURLcode test_lib517(const char *URL) }; static const struct dcheck dates[] = { + { "Sat May 5 GMT 11:56:27 2035", 2061978987 }, + { "Sat, May 5 GMT 11:56:27 2035", 2061978987 }, + { "Saturday, May 5 GMT 11:56:27 2035", 2061978987 }, + { "Saturdax, May 5 GMT 11:56:27 2035", -1 }, + { "Saturda, May 5 GMT 11:56:27 2035", -1 }, { "Sun, 06 Nov 1994 08:49:37 GMT", 784111777 }, { "Sunday, 06-Nov-94 08:49:37 GMT", 784111777 }, { "Sun Nov 6 08:49:37 1994", 784111777 }, @@ -252,5 +257,10 @@ static CURLcode test_lib517(const char *URL) } } + if(curl_getdate(NULL, NULL) != (time_t)-1) { + curl_mprintf("WRONGLY parsed NULL string\n"); + error++; + } + return error == 0 ? CURLE_OK : TEST_ERR_FAILURE; } diff --git a/tests/libtest/lib530.c b/tests/libtest/lib530.c index 94ecb7adf297..a5d953eeaaf4 100644 --- a/tests/libtest/lib530.c +++ b/tests/libtest/lib530.c @@ -361,12 +361,11 @@ static CURLcode testone(const char *URL, int timer_fail_at, int socket_fail_at) } if(timeout.tv_sec != (time_t)-1 && - t530_getMicroSecondTimeout(&timeout) == 0) { - /* curl's timer has elapsed. */ - if(socket_action(multi, CURL_SOCKET_TIMEOUT, 0, "timeout")) { - result = TEST_ERR_BAD_TIMEOUT; - goto test_cleanup; - } + t530_getMicroSecondTimeout(&timeout) == 0 && + /* curl's timer has elapsed. */ + socket_action(multi, CURL_SOCKET_TIMEOUT, 0, "timeout")) { + result = TEST_ERR_BAD_TIMEOUT; + goto test_cleanup; } abort_on_test_timeout(); diff --git a/tests/libtest/lib544.c b/tests/libtest/lib544.c index 013c2ab32b30..42fa8dd51815 100644 --- a/tests/libtest/lib544.c +++ b/tests/libtest/lib544.c @@ -73,7 +73,7 @@ static CURLcode test_lib544(const char *URL) curl = curl2; } - /* Now, this is a POST request with binary 0 embedded in POST data. */ + /* Now, this is a POST request with NUL byte embedded in POST data. */ result = curl_easy_perform(curl); test_cleanup: diff --git a/tests/libtest/lib556.c b/tests/libtest/lib556.c index 1bc4638b9b29..d03396d1f910 100644 --- a/tests/libtest/lib556.c +++ b/tests/libtest/lib556.c @@ -27,6 +27,7 @@ static CURLcode test_lib556(const char *URL) { CURLcode result; CURL *curl; + size_t ncheck = 0; int transfers = 0; if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { @@ -45,6 +46,15 @@ static CURLcode test_lib556(const char *URL) easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L); easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if(curl_easy_recv(NULL, NULL, 0, NULL) != CURLE_BAD_FUNCTION_ARGUMENT || + curl_easy_recv(curl, NULL, 10, &ncheck) != CURLE_BAD_FUNCTION_ARGUMENT || + curl_easy_send(NULL, NULL, 0, NULL) != CURLE_BAD_FUNCTION_ARGUMENT || + curl_easy_send(curl, NULL, 10, &ncheck) != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "curl_easy_recv/send argument validation failed\n"); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + again: result = curl_easy_perform(curl); @@ -76,15 +86,14 @@ static CURLcode test_lib556(const char *URL) /* busy-read like crazy */ result = curl_easy_recv(curl, buf, sizeof(buf), &nread); - if(nread) { - /* send received stuff to stdout */ - if((size_t)write(STDOUT_FILENO, buf, nread) != nread) { - char errbuf[STRERROR_LEN]; - curl_mfprintf(stderr, "write() failed: errno %d (%s)\n", - errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); - result = TEST_ERR_FAILURE; - break; - } + if(nread && + /* send received stuff to stdout */ + (size_t)write(STDOUT_FILENO, buf, nread) != nread) { + char errbuf[STRERROR_LEN]; + curl_mfprintf(stderr, "write() failed: errno %d (%s)\n", + errno, curlx_strerror(errno, errbuf, sizeof(errbuf))); + result = TEST_ERR_FAILURE; + break; } } while((result == CURLE_OK && nread) || (result == CURLE_AGAIN)); diff --git a/tests/libtest/lib557.c b/tests/libtest/lib557.c index 535e37d4acc8..24cb1bfd2554 100644 --- a/tests/libtest/lib557.c +++ b/tests/libtest/lib557.c @@ -1071,10 +1071,13 @@ static int double_check(void) struct dbcheck c[] = { { "%.17Lf", /* "1.23456789012345669" */ "1.23456789012345", "", }, +/* VS2013 and earlier CRT vsnprintf() return slightly different results */ +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) { "%.17Le", /* "1.23456789012345669e+00" */ "1.234567890123456", "e+00" }, { "%.17LE", /* "1.23456789012345669E+00" */ "1.234567890123456", "E+00" }, +#endif { "%.17Lg", /* "1.2345678901234567" */ "1.234567890123456", "" }, { "%.17LG", /* "1.2345678901234567" */ @@ -1085,21 +1088,19 @@ static int double_check(void) size_t len = curl_msnprintf(curl_out, sizeof(curl_out), c[i].fmt, val); if(strncmp(curl_out, c[i].prefix, strlen(c[i].prefix))) { - curl_mfprintf(stderr, - "MISMATCH (prefix): %s curl=%s libc=%s\n", - c[i].fmt, curl_out, c[i].prefix); + curl_mprintf("MISMATCH (prefix): %s curl=%s libc=%s\n", + c[i].fmt, curl_out, c[i].prefix); mismatches++; } if((len < strlen(c[i].suffix) || strncmp(curl_out + len - strlen(c[i].suffix), c[i].suffix, strlen(c[i].suffix)))) { - curl_mfprintf(stderr, - "MISMATCH (suffix): %s curl=%s libc=%s\n", - c[i].fmt, curl_out, c[i].suffix); + curl_mprintf("MISMATCH (suffix): %s curl=%s libc=%s\n", + c[i].fmt, curl_out, c[i].suffix); mismatches++; } } - curl_mfprintf(stderr, "mismatches=%d/5\n", mismatches); + curl_mfprintf(stderr, "mismatches=%d/%zu\n", mismatches, CURL_ARRAYSIZE(c)); return mismatches; } @@ -1113,8 +1114,11 @@ static int test_float_formatting(void) curl_msnprintf(buf, sizeof(buf), "%f", 9.0); errors += string_check(buf, "9.000000"); +/* VS2013 and earlier CRT vsnprintf() does not support this mask */ +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) curl_msnprintf(buf, sizeof(buf), "%F", 9.0); errors += string_check(buf, "9.000000"); +#endif curl_msnprintf(buf, sizeof(buf), "%.1f", 9.1); errors += string_check(buf, "9.1"); @@ -1280,17 +1284,17 @@ static int test_return_codes(void) if(rc != 5) return 1; - /* returns the length excluding the nul byte */ + /* returns the length excluding the NUL byte */ rc = curl_msnprintf(buf, 5, "%d", 99999); if(rc != 4) return 1; - /* returns the length excluding the nul byte */ + /* returns the length excluding the NUL byte */ rc = curl_msnprintf(buf, 5, "%s", "helloooooooo"); if(rc != 4) return 1; - /* returns the length excluding the nul byte */ + /* returns the length excluding the NUL byte */ rc = curl_msnprintf(buf, 6, "%s", "helloooooooo"); if(rc != 5) return 1; diff --git a/tests/libtest/lib567.c b/tests/libtest/lib567.c index dc17f53b0ba4..1421dfb157e4 100644 --- a/tests/libtest/lib567.c +++ b/tests/libtest/lib567.c @@ -53,6 +53,8 @@ static CURLcode test_lib567(const char *URL) easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); easy_setopt(curl, CURLOPT_USERAGENT, "test567"); + if(testnum == 1688) + easy_setopt(curl, CURLOPT_NOBODY, 1L); custom_headers = curl_slist_append(custom_headers, "Test-Number: 567"); easy_setopt(curl, CURLOPT_RTSPHEADER, custom_headers); diff --git a/tests/libtest/lib758.c b/tests/libtest/lib758.c index 794e4cce066d..379cc873faa3 100644 --- a/tests/libtest/lib758.c +++ b/tests/libtest/lib758.c @@ -455,12 +455,11 @@ static CURLcode t758_one(const char *URL, int timer_fail_at, } if(timeout.tv_sec != (time_t)-1 && - t758_getMicroSecondTimeout(&timeout) == 0) { - /* curl's timer has elapsed. */ - if(t758_saction(multi, CURL_SOCKET_TIMEOUT, 0, "timeout")) { - result = TEST_ERR_BAD_TIMEOUT; - goto test_cleanup; - } + t758_getMicroSecondTimeout(&timeout) == 0 && + /* curl's timer has elapsed. */ + t758_saction(multi, CURL_SOCKET_TIMEOUT, 0, "timeout")) { + result = TEST_ERR_BAD_TIMEOUT; + goto test_cleanup; } abort_on_test_timeout(); diff --git a/tests/libtest/test3500.pl b/tests/libtest/test3500.pl new file mode 100755 index 000000000000..d95f54cecfcb --- /dev/null +++ b/tests/libtest/test3500.pl @@ -0,0 +1,72 @@ +#!/usr/bin/env perl +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , 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 +# +########################################################################### +use strict; +use warnings; + +use Digest::SHA qw(hmac_sha1); +use MIME::Base64 qw(encode_base64); + +sub errout { + print $_[0] . "\n"; + exit 1; +} + +if(@ARGV != 3) { + errout "Usage: $0 host-public-key known-hosts-file target"; +} + +my ($hostkey_file, $knownhosts_file, $target) = @ARGV; +open(my $hostkey, "<", $hostkey_file) or errout "$!"; +my $hostkey_line = <$hostkey>; +close($hostkey) or errout "$!"; + +my ($hostkey_algo, $hostkey_data) = split(/\s+/, $hostkey_line); +if(!$hostkey_algo || !$hostkey_data) { + errout "Failed parsing SSH host public key"; +} + +my $rsa_key = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCgSyqrUP2xhqLqkyopVapoKeucYIp" . + "ZXeUqj+Q9WLWyXka5re62Y3R/lxZUXtkJjlpdD2LNMFenNlDQvPaXH5Utllt+c3z" . + "wOkOLy7RXX5S37SfQFm/0fPkZqoZfMDB6XDcwA4cakjCRn4oZyKs8dWAxoNaTtYc" . + "nKKMJ6rmc8PttykJw9fwiKAjPyYf21/BMvWIzu9req+TrDA/Jcd2UAug94CikJIX" . + "uwKmSYleAOo4eJWx8GyujgEgZn05eq+K1LSg9RqRVIxOdqjoE5K3abBfs0rmUuLd" . + "MFh7nHseTFXYbKzpz46V7iZMfFuoyURrzVsqGUm+/m7dxBM98In12kOm3"; +my $ed25519_key = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAqmXRY8k+W9GtpWxYru6ELAXYHdXq" . + "jf33ziITGTiJUN"; +my $other_key = $hostkey_algo eq "ssh-ed25519" ? $rsa_key : $ed25519_key; + +my $salt = "curl-knownhosts-test"; +my $salt64 = encode_base64($salt, ""); +my $hash64 = encode_base64(hmac_sha1("unrelated.invalid", $salt), ""); +my $target_hash64 = encode_base64(hmac_sha1($target, $salt), ""); + +open(my $knownhosts, ">", $knownhosts_file) or errout "$!"; +print $knownhosts "|1|$salt64|$hash64 $other_key\n"; +print $knownhosts "|1|$salt64|$target_hash64 $hostkey_algo $hostkey_data\n"; +close($knownhosts) or errout "$!"; + +exit 0; diff --git a/tests/memanalyzer.pm b/tests/memanalyzer.pm index 110b39d24111..c8fe3025ace2 100644 --- a/tests/memanalyzer.pm +++ b/tests/memanalyzer.pm @@ -138,7 +138,7 @@ sub memanalyze { elsif(!exists $sizeataddr{$addr}) { push @res, "FREE ERROR: No memory allocated: $line\n"; } - elsif(-1 == $sizeataddr{$addr}) { + elsif($sizeataddr{$addr} == -1) { push @res, "FREE ERROR: Memory freed twice: $line\n"; push @res, "FREE ERROR: Previously freed at: $getmem{$addr}\n"; } diff --git a/tests/negtelnetserver.py b/tests/negtelnetserver.py index 44ae2eeb75c8..4d7b49604aba 100755 --- a/tests/negtelnetserver.py +++ b/tests/negtelnetserver.py @@ -117,11 +117,11 @@ def __init__(self, tcp): self.tcp = tcp self.state = self.NO_NEG - def recv(self, bytes): + def recv(self, nbytes): """ Read bytes from TCP, handling negotiation sequences. - :param bytes: Number of bytes to read + :param nbytes: Number of bytes to read :return: a buffer of bytes """ buffer = bytearray() @@ -130,7 +130,7 @@ def recv(self, bytes): # Keep looping while we can, and until we have something to give back # to the caller. while len(buffer) == 0: - data = self.tcp.recv(bytes) + data = self.tcp.recv(nbytes) if not data: # TCP failed to give us any data. Break out. break diff --git a/tests/perf/Makefile.inc b/tests/perf/Makefile.inc index 9660c9cb1c59..93fe64030d95 100644 --- a/tests/perf/Makefile.inc +++ b/tests/perf/Makefile.inc @@ -35,7 +35,7 @@ UTILS_H = CURLX_C = \ ../../lib/curlx/base64.c \ - ../../lib/curlx/fopen.c \ + ../../lib/curlx/win32-fopen.c \ ../../lib/curlx/multibyte.c \ ../../lib/curlx/strparse.c \ ../../lib/curlx/timeval.c \ @@ -43,7 +43,13 @@ CURLX_C = \ # All performance test programs TESTS_C = \ - base64.c \ - percent.c \ + base64dec.c \ + base64enc.c \ + dateparser.c \ + maprintf.c \ + numparser.c \ + simpleformat.c \ snprintf.c \ + urldecode.c \ + urlencode.c \ urlparser.c diff --git a/tests/perf/README.md b/tests/perf/README.md index def7bf91b2f4..97af37ceaec7 100644 --- a/tests/perf/README.md +++ b/tests/perf/README.md @@ -16,21 +16,78 @@ before? Ideally these tests run without using any servers. -## `urlparser` +## `base64enc` -Provide this test with a list of many URLs and it times how fast it can parse -them: `./perf urlparser URLs.txt [loops]` +This test base64 encodes a 256-byte buffer that has every different byte octet +represented. Repeatedly in a loop the provided number of times: -A test sample of 100K URLs can be found -[here](https://raw.githubusercontent.com/ada-url/url-various-datasets/refs/heads/main/top100/top100.txt) + ./perf base64enc [loops] + +## `base64dec` + +This test base64 decodes a big encoded string. Repeatedly in a loop the +provided number of times: + + ./perf base64dec [loops] + +## `dateparser` + +Runs through 180+ date strings and parses them (converts them to epoch time) +`loops` number of times: + + ./perf dateparser [loops] + +## `maprintf` + +This test repeatedly formats a request header block with `curl_maprintf()`, +which allocates the result, and measures the time per call. It exercises the +allocating output path and a `curl_off_t` conversion, which the `snprintf` +test does not: + + ./perf maprintf [loops] + +## `numparser` + +This test repeatedly converts over a hundred different numbers from a string +to binary. Decimal, hexadecimal and octal numbers, each with a set cap. Many +numbers hit the cap, some numbers are not syntactically correct. + + ./perf numparser [loops] -## `base64` +## `simpleformat` -This test first base64 encodes a 256-byte buffer that has every different -byte octet represented. It then decodes that string. Repeatedly in a loop the -provided number of times: `./perf base64 [loops]` +This test repeatedly formats a commonly used simple message with +`curl_msnprintf()`: literal text plus `%s`, a `size_t` and a `curl_off_t`, +with no width or precision. Where `snprintf` measures an elaborate format, +this measures the common one: + + ./perf simpleformat [loops] ## `snprintf` This test repeatedly formats a representative string and measures the time per -call: `./perf snprintf [loops]` +call: + + ./perf snprintf [loops] + +## `urlencode` + +This test URL (percent) encodes a 256-byte buffer that has every different +byte octet represented. Repeatedly in a loop the provided number of times: + + ./perf urlencode [loops] + +## `urldecode` + +This test URL (percent) decodes a 600+ byte buffer. Repeatedly in a loop the +provided number of times: + + ./perf urldecode [loops] + +## `urlparser` + +Provide this test with a list of many URLs and it times how fast it can parse +them: `./perf urlparser URLs.txt [loops]` + +A test sample of 100K URLs can be found +[here](https://raw.githubusercontent.com/ada-url/url-various-datasets/refs/heads/main/top100/top100.txt) diff --git a/tests/perf/base64dec.c b/tests/perf/base64dec.c new file mode 100644 index 000000000000..f023eb3d1866 --- /dev/null +++ b/tests/perf/base64dec.c @@ -0,0 +1,72 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +/* this is 256 bytes using the octets 0-255, base64 encoded */ +#define BLOB64 \ + "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMD" \ + "EyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFi" \ + "Y2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5" \ + "SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TF" \ + "xsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19v" \ + "f4+fr7/P3+/w==" + +static int test_base64dec(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 10000000, loop; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + unsigned char *recoded = NULL; + size_t reclen; + CURLcode result = curlx_base64_decode(BLOB64, &recoded, &reclen); + curlx_free(recoded); + if(result) { + curl_mfprintf(stderr, "unexpected coding error: %d\n", (int)result); + return 1; + } + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%02lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/base64.c b/tests/perf/base64enc.c similarity index 87% rename from tests/perf/base64.c rename to tests/perf/base64enc.c index 585fa1d39962..622f956b4986 100644 --- a/tests/perf/base64.c +++ b/tests/perf/base64enc.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "first.h" -static int test_base64(int argc, const char **argv) +static int test_base64enc(int argc, const char **argv) { struct curltime start; struct curltime end; @@ -52,13 +52,6 @@ static int test_base64(int argc, const char **argv) size_t enclen; CURLcode result = curlx_base64_encode(array, sizeof(array), &encoded, &enclen); - if(!result) { - unsigned char *recoded = NULL; - size_t reclen; - /* now decode it again */ - result = curlx_base64_decode(encoded, &recoded, &reclen); - curlx_free(recoded); - } if(result) { curl_mfprintf(stderr, "unexpected coding error: %d\n", (int)result); return 1; @@ -70,7 +63,7 @@ static int test_base64(int argc, const char **argv) hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/dateparser.c b/tests/perf/dateparser.c new file mode 100644 index 000000000000..f2438160a034 --- /dev/null +++ b/tests/perf/dateparser.c @@ -0,0 +1,270 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +static const char *dates[] = { + "Sun, 06 Nov 1994 08:49:37 GMT", + "Sunday, 06-Nov-94 08:49:37 GMT", + "Sun Nov 6 08:49:37 1994", + "Sun Nov 6 8:49:37 1994", + "Sun Nov 6 8:9:37 1994", + "Sun Nov 6 008:09:37 1994", + "Nov Sun 6 8:9:7 1994", + "06 Nov 1994 08:49:37 GMT", + "06-Nov-94 08:49:37 GMT", + "Nov 6 08:49:37 1994", + "06 Nov 1994 08:49:37", + "06-Nov-94 08:49:37", + "1994 Nov 6 08:49:37", + "GMT 08:49:37 06-Nov-94 Sunday", + "94 6 Nov 08:49:37", + "1994 Nov 6", + "06-Nov-94", + "Sun Nov 6 94", + "1994.Nov.6", + "Sun/Nov/6/94/GMT", + "Sun, 06 Nov 1994 08:49:37 CET", + "Sun, 06 Nov 1994 08:49:37 cet", + "Sun, 06 Nov 1994 08:49:37 utc", + "Sun, 06 Nov 1994 08:49:37 gmt", + "06 Nov 1994 08:49:37 EST", + "Sun, 06 Nov 1994 08:49:37 UT", + "Sun, 12 Sep 2004 15:05:58 -0700", + "Sat, 11 Sep 2004 21:32:11 +0200", + "20040912 15:05:58 -0700", + "20040911 +0200", + "Thu, 01-Jan-1970 00:59:59 GMT", + "Thu, 01-Jan-1970 01:00:00 GMT", + "Sat, 15-Apr-17 21:01:22 GMT", + "Thu, 19-Apr-2007 16:00:00 GMT", + "Wed, 25 Apr 2007 21:02:13 GMT", + "Thu, 19/Apr\\2007 16:00:00 GMT", + "Fri, 1 Jan 2010 01:01:50 GMT", + "Wednesday, 1-Jan-2003 00:00:00 GMT", + "", + "-", + "a", + "1-Jan-2003 00:00:00 GMT", + "1-Jan-2003 00:00:00 GMT", + "Wed,18-Apr-07 22:50:12 GMT", + "BillyWonka , 18-Apr-07 22:50:12 GMT", + "BillyWonka , 18-Apr-07 22:50:12", + "BillyWonka , 18-apr-07 22:50:12", + "Mon, 18-Apr-1977 22:50:13 GMT", + "Mon, 18-Apr-77 22:50:13 GMT", + "Sat, 15-Apr-17\"21:01:22\"GMT", + "Partyday, 18- April-07 22:50:12", + "Partyday, 18 - Apri-07 22:50:12", + "Wednes, 1-Januar-2003 00:00:00 GMT", + "Sat, 15-Apr-17 21:01:22", + "Sat, 15-Apr-17 21:01:22 GMT-2", + "Sat, 15-Apr-17 21:01:22 GMT BLAH", + "Sat, 15-Apr-17 21:01:22 GMT-0400", + "Sat, 15-Apr-17 21:01:22 GMT-0400 (EDT)", + "Sat, 15-Apr-17 21:01:22 DST", + "Sat, 15-Apr-17 21:01:22 -0400", + "Sat, 15-Apr-17 21:01:22 (hello there)", + "Sat, 15-Apr-17 21:01:22 11:22:33", + "Sat, 15-Apr-17 ::00 21:01:22", + "Sat, 15-Apr-17 boink:z 21:01:22", + "Sat, 15-Apr-17 91:22:33 21:01:22", + "Thu Apr 18 22:50:12 2007 GMT", + "22:50:12 Thu Apr 18 2007 GMT", + "Thu 22:50:12 Apr 18 2007 GMT", + "Thu Apr 22:50:12 18 2007 GMT", + "Thu Apr 18 22:50:12 2007 GMT", + "Thu Apr 18 2007 22:50:12 GMT", + "Thu Apr 18 2007 GMT 22:50:12", + "\"Thu Apr 18 22:50:12 2007 GMT\"", + "-\"22:50:12 Thu Apr 18 2007 GMT\"", + "*\"Thu 22:50:12 Apr 18 2007 GMT\"", + ";\"Thu Apr 22:50:12 18 2007 GMT\"", + ".\"Thu Apr 18 22:50:12 2007 GMT\"", + "\"Thu Apr 18 2007 22:50:12 GMT\"", + "\"Thu Apr 18 2007 GMT 22:50:12\"", + "Sat, 15-Apr-17 21:01:22 GMT", + "15-Sat, Apr-17 21:01:22 GMT", + "15-Sat, Apr 21:01:22 GMT 17", + "15-Sat, Apr 21:01:22 GMT 2017", + "15 Apr 21:01:22 2017", + "15 17 Apr 21:01:22", + "Apr 15 17 21:01:22", + "Apr 15 21:01:22 17", + "2017 April 15 21:01:22", + "15 April 2017 21:01:22", + "98 April 17 21:01:22", + "Thu, 012-Aug-2008 20:49:07 GMT", + "Thu, 999999999999-Aug-2007 20:49:07 GMT", + "Thu, 12-Aug-2007 20:61:99999999999 GMT", + "IAintNoDateFool", + "Thu Apr 18 22:50 2007 GMT", + "20110623 12:34:56", + "20110023 12:34:56", + "20110632 12:34:56", + "20110623 56:34:56", + "20111323 12:34:56", + "20110623 12:34:79", + "Wed, 31 Dec 2008 23:59:60 GMT", + "Wed, 31 Dec 2008 23:59:61 GMT", + "Wed, 31 Dec 2008 24:00:00 GMT", + "Wed, 31 Dec 2008 23:60:59 GMT", + "20110623 12:3", + "20110623 1:3", + "20110623 1:30", + "20110623 12:12:3", + "20110623 01:12:3", + "20110623 01:99:30", + "Thu, 01-Jan-1970 00:00:00 GMT", + "Thu, 31-Dec-1969 23:59:58 GMT", + "Thu, 31-Dec-1969 23:59:59 GMT", + "Sun, 06 Nov 2044 08:49:37 GMT", + "Sun, 06 Nov 3144 08:49:37 GMT", + "Sun, 06 Nov 1900 08:49:37 GMT", + "Sun, 06 Nov 1800 08:49:37 GMT", + "Thu, 01-Jan-1583 00:00:00 GMT", + "Thu, 01-Jan-1499 00:00:00 GMT", + "Sun, 06 Nov 1968 08:49:37 GMT", + "2094 Nov 6 08:49:37", + "01 Jan 2001 8:0:0", + "01 Jan 2001 8:00:0", + /* Out-of-range day-of-month Cases */ + "29 Feb 2023 12:00:00 GMT", + "31 Apr 2024 12:00:00 GMT", + "30 Feb 2024 12:00:00 GMT", + "01-13-2024", + "32 Jan 2024", + "31 Jan 2024", + "32 Feb 2024", + "32 Mar 2024", + "32 Apr 2024", + "32 May 2024", + "32 Jun 2024", + "32 Jul 2024", + "32 Aug 2024", + "32 Sep 2024", + "32 Oct 2024", + "32 Nov 2024", + "32 Dec 2024", + /* Timezone Offsets */ + "Sun, 06 Nov 1994 08:49:37 +0530", + "Sun, 06 Nov 1994 08:49:37 +0545", + "06 Nov 1994 08:49:37 Z", + "06 Nov 1994 08:49:37 A", + "06 Nov 1994 08:49:37 B", + "06 Nov 1994 08:49:37 C", + "06 Nov 1994 08:49:37 D", + "06 Nov 1994 08:49:37 E", + "06 Nov 1994 08:49:37 F", + "06 Nov 1994 08:49:37 G", + "06 Nov 1994 08:49:37 H", + "06 Nov 1994 08:49:37 I", + "06 Nov 1994 08:49:37 J", + "06 Nov 1994 08:49:37 K", + "06 Nov 1994 08:49:37 L", + "06 Nov 1994 08:49:37 M", + "06 Nov 1994 08:49:37 N", + "06 Nov 1994 08:49:37 O", + "06 Nov 1994 08:49:37 P", + "06 Nov 1994 08:49:37 Q", + "06 Nov 1994 08:49:37 R", + "06 Nov 1994 08:49:37 S", + "06 Nov 1994 08:49:37 T", + "06 Nov 1994 08:49:37 U", + "06 Nov 1994 08:49:37 V", + "06 Nov 1994 08:49:37 W", + "06 Nov 1994 08:49:37 X", + "06 Nov 1994 08:49:37 Y", + "GMT+05:30", + "GMT-08:00", + /* ISO 8601 & Variations - not supported */ + "1994-11-06T08:49:37Z", + "1994-11-06 08:49:37.123 GMT", + "19941106T084937Z", + /* Y2K38 & Historical Boundaries */ + "19 Jan 2038 03:14:07 GMT", + "19 Jan 2038 03:14:08 GMT", + "01 Jan 69 00:00:00 GMT", + "01 Jan 1500 00:00:00 GMT", + /* Formatting & Malformed Junk */ + "Sun, 06-Nov/1994 08:49:37", + "Sun, 06 Nov 1994 08:49:37 GMT", + " Sun, 06 Nov 1994 08:49:37 GMT ", + "Date: Sun, 06 Nov 1994 08:49:37 GMT", + /* wrong day name is ignored */ + "Monday, 06 Nov 1994 08:49:37 GMT" +}; + +/* + * Parse the dates + */ +static int test_dateparser(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 100000, loop; + curl_off_t count = 0; + curl_off_t ecount = 0; /* errors */ + size_t i; + size_t ndates = CURL_ARRAYSIZE(dates); + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, INT_MAX) && !*ptr) + loops = num; + } + + curl_mprintf("Found %zu dates to test\n", ndates); + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + for(i = 0; i < ndates; i++) { + time_t out = curl_getdate(dates[i], NULL); + count++; + if(out == (time_t)-1) { +#if 0 + curl_mfprintf(stderr, "Failed [%u]: %s\n", (int)hcode, buffer); +#endif + ecount++; + } + } + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = count ? us * 100000 / count : 0; /* 100 times too big */ + curl_mprintf("Dates: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/date: %lld.%02lld ns\n" + "Dates/sec: %lld\n" + "Errors: %" CURL_FORMAT_CURL_OFF_T "\n", + count, + (long long)us, + (long long)hn / 100, + (long long)hn % 100, + us ? (long long)(count * 1000000) / us : 0, + ecount); + return 0; +} diff --git a/tests/perf/first.h b/tests/perf/first.h index 58990646a07b..5c6bd4582dd8 100644 --- a/tests/perf/first.h +++ b/tests/perf/first.h @@ -33,7 +33,7 @@ #include "curl_setup.h" #include "curlx/base64.h" /* for curlx_base64* */ -#include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/win32-fopen.h" /* for curlx_f*() */ #include "curlx/strparse.h" /* for curlx_str_* parsing functions */ #include "curlx/timeval.h" /* for curlx_now type and related functions */ diff --git a/tests/perf/maprintf.c b/tests/perf/maprintf.c new file mode 100644 index 000000000000..36c3eaa57f21 --- /dev/null +++ b/tests/perf/maprintf.c @@ -0,0 +1,75 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +/* snprintf.c times curl_msnprintf() into a caller's buffer. This test times + curl_maprintf(), which allocates and returns the result. libcurl builds + request lines and headers with curlx_dyn_addf(), which formats through the + same allocating code (curl_mvaprintf), so this exercises that path and its + per-byte cost, which differs from the fixed-buffer one. The format is a + request-header-like block: literal text, %s, and a curl_off_t. The + allocation and the free are inside the timed loop on purpose, because a + caller pays them. */ + +static int test_maprintf(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + + curl_off_t loops = 10000000, loop; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + char *s = curl_maprintf("%s %s HTTP/1.1\r\n" + "Host: %s\r\n" + "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", + "POST", "/path/to/resource", "example.com", + (curl_off_t)4096); + if(!s) { + curl_mfprintf(stderr, "curl_maprintf() failed\n"); + return 1; + } + curl_free(s); + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loops ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%02lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/numparser.c b/tests/perf/numparser.c new file mode 100644 index 000000000000..da3ae34e0b63 --- /dev/null +++ b/tests/perf/numparser.c @@ -0,0 +1,239 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +struct num { + const char *str; + int base; + curl_off_t max; +}; + +static const struct num nums[] = { + { "9223372036854775807", 10, 9223372036854775807LL }, + { "123456789123456123A", 10, 9223372036854775807LL }, + { "123456789123456123B", 10, 9223372036854775807LL }, + { "123456789123456123C", 10, 9223372036854775807LL }, + { "123456789123456123D", 10, 9223372036854775807LL }, + { "1234567891234561234", 10, 9223372036854775807LL }, + { "123456789123456123", 10, 9223372036854775807LL }, + { "12345678912345612", 10, 9223372036854775807LL }, + { "1234567891234561", 10, 9223372036854775807LL }, + { "123456789123456", 10, 9223372036854775807LL }, + { "12345678912345", 10, 9223372036854775807LL }, + { "1234567891234", 10, 9223372036854775807LL }, + { "123456789123", 10, 9223372036854775807LL }, + { "12345678912", 10, 9223372036854775807LL }, + { "1234567891", 10, 9223372036854775807LL }, + { "123456789", 10, 9223372036854775807LL }, + { "12345678", 10, 9223372036854775807LL }, + { "1234567", 10, 9223372036854775807LL }, + { "123456", 10, 9223372036854775807LL }, + { "12345", 10, 9223372036854775807LL }, + { "1234", 10, 9223372036854775807LL }, + { "123", 10, 9223372036854775807LL }, + { "12", 10, 9223372036854775807LL }, + { "1", 10, 9223372036854775807 }, + { "9223372036854775807", 10, 9223372036854775806LL }, + { "1234567891234561234", 10, 1234567891234561233LL }, + { "234567891234561234", 10, 234567891234561233LL }, + { "34567891234561234", 10, 34567891234561233LL }, + { "4567891234561234", 10, 4567891234561233LL }, + { "567891234561234", 10, 567891234561233LL }, + { "67891234561234", 10, 67891234561233LL }, + { "7891234561234", 10, 7891234561233LL }, + { "891234561234", 10, 891234561233LL }, + { "91234561234", 10, 91234561233LL }, + { "1234561234", 10, 1234561233LL }, + { "234561234", 10, 234561233LL }, + { "34561234", 10, 34561233LL }, + { "4561234", 10, 4561233LL }, + + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFELL }, + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFDLL }, + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFCLL }, + { "7FFFFFFFFFFFFFFF", 16, 0x7FFFFFFFFFFFFFFBLL }, + { "7FFFFFFFFFFFFFFE", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFD", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFC", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFB", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFA", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "7FFFFFFFFFFFFFFG", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "GFFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "HFFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "IFFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "KFFFFFFFFFFFFFF9", 16, 0x7FFFFFFFFFFFFFFFLL }, + { "1234567890123456", 16, 0x1234567890123456LL }, + { "123456789012345G", 16, 0x1234567890123456LL }, + { "123456789012345H", 16, 0x1234567890123456LL }, + { "123456789012345I", 16, 0x1234567890123456LL }, + { "123456789012345J", 16, 0x1234567890123456LL }, + { "123456789012345K", 16, 0x1234567890123456LL }, + { "234567890123456", 16, 0x1234567890123456LL }, + { "34567890123456", 16, 0x1234567890123456LL }, + { "4567890123456", 16, 0x1234567890123456LL }, + { "567890123456", 16, 0x1234567890123456LL }, + { "567890123456", 16, 0x1234567890123456LL }, + { "67890123456", 16, 0x1234567890123456LL }, + { "7890123456", 16, 0x1234567890123456LL }, + { "890123456", 16, 0x1234567890123456LL }, + { "90123456", 16, 0x1234567890123456LL }, + { "0123456", 16, 0x1234567890123456LL }, + { "123456", 16, 0x1234567890123456LL }, + { "23456", 16, 0x1234567890123456LL }, + { "3456", 16, 0x1234567890123456LL }, + { "456", 16, 0x1234567890123456LL }, + { "56", 16, 0x1234567890123456LL }, + { "6", 16, 0x1234567890123456LL }, + + { "1234567890123456", 16, 0x1234567890123455LL }, + { "234567890123456", 16, 0x234567890123455LL }, + { "34567890123456", 16, 0x34567890123455LL }, + { "4567890123456", 16, 0x4567890123455LL }, + { "67890123456", 16, 0x67890123455LL }, + { "7890123456", 16, 0x7890123455LL }, + { "890123456", 16, 0x890123455LL }, + { "90123456", 16, 0x90123455LL }, + { "0123456", 16, 0x0123455LL }, + { "123456", 16, 0x123455LL }, + { "23456", 16, 0x23455LL }, + + { "10", 10, 5 }, + { "9", 10, 5 }, + { "8", 10, 5 }, + { "7", 10, 5 }, + { "3", 10, 5 }, + { "2", 10, 5 }, + { "1", 10, 5 }, + + { "10", 16, 8 }, + { "9", 16, 8 }, + { "8", 16, 8 }, + { "7", 16, 6 }, + { "3", 16, 7 }, + { "2", 16, 5 }, + { "1", 16, 5 }, + + {"777777777777777777777", 8, 0777777777777777777777LL }, + {"777777777777777777776", 8, 0777777777777777777777LL }, + {"777777777777777777775", 8, 0777777777777777777777LL }, + {"777777777777777777774", 8, 0777777777777777777777LL }, + {"777777777777777777773", 8, 0777777777777777777777LL }, + {"777777777777777777772", 8, 0777777777777777777777LL }, + {"777777777777777777779", 8, 0777777777777777777777LL }, + {"123456701234567012345", 8, 0123456701234567012345LL }, + {"12345670123456701234", 8, 0123456701234567012345LL }, + {"1234567012345670123", 8, 0123456701234567012345LL }, + {"123456701234567012", 8, 0123456701234567012345LL }, + {"12345670123456701", 8, 0123456701234567012345LL }, + {"1234567012345670", 8, 0123456701234567012345LL }, + {"123456701234567", 8, 0123456701234567012345LL }, + {"12345670123456", 8, 0123456701234567012345LL }, + {"1234567012345", 8, 0123456701234567012345LL }, + {"123456701234", 8, 0123456701234567012345LL }, + {"12345670123", 8, 0123456701234567012345LL }, + {"1234567012", 8, 0123456701234567012345LL }, + {"123456701", 8, 0123456701234567012345LL }, + + {"123456701234567012345", 8, 0123456701234567012344LL }, + {"23456701234567012345", 8, 023456701234567012344LL }, + {"3456701234567012345", 8, 03456701234567012344LL }, + {"456701234567012345", 8, 0456701234567012344LL }, + {"56701234567012345", 8, 056701234567012344LL }, + {"6701234567012345", 8, 06701234567012344LL }, + {"701234567012345", 8, 0701234567012344LL }, + {"01234567012345", 8, 01234567012344LL }, + {"1234567012345", 8, 01234567012344LL }, + {"234567012345", 8, 0234567012344LL }, + {"34567012345", 8, 034567012344LL }, + {"4567012345", 8, 04567012344LL }, + {"567012345", 8, 0567012344LL }, + {"67012345", 8, 067012344LL }, + + {"123456701234567012348", 8, 0123456701234567012344LL }, + {"123456701234567012349", 8, 0123456701234567012344LL }, + {"12345670123456701234A", 8, 0123456701234567012344LL }, + {"12345670123456701234C", 8, 0123456701234567012344LL }, + {"12345670123456701234B", 8, 0123456701234567012344LL }, + +}; + +/* + * Parse the numbers + */ +static int test_numparser(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 500000, loop; + curl_off_t count = 0; + curl_off_t ecount = 0; /* errors */ + size_t i; + size_t nnumbers = CURL_ARRAYSIZE(nums); + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, INT_MAX) && !*ptr) + loops = num; + } + + curl_mprintf("Found %zu numbers to parse\n", nnumbers); + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + for(i = 0; i < nnumbers; i++) { + const char *p = nums[i].str; + curl_off_t val; + int err; + if(nums[i].base == 10) + err = curlx_str_number(&p, &val, nums[i].max); + else if(nums[i].base == 16) + err = curlx_str_hex(&p, &val, nums[i].max); + else + err = curlx_str_octal(&p, &val, nums[i].max); + if(err) + ecount++; + count++; + } + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = count ? us * 1000000 / count : 0; /* 1000 times too big */ + curl_mprintf("Numbers: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/num: %lld.%03lld ns\n" + "Nums/sec: %lld\n" + "Errors: %" CURL_FORMAT_CURL_OFF_T "\n", + count, + (long long)us, + (long long)hn / 1000, + (long long)hn % 1000, + us ? (long long)(count * 1000000) / us : 0, + ecount); + return 0; +} diff --git a/tests/perf/simpleformat.c b/tests/perf/simpleformat.c new file mode 100644 index 000000000000..a03a755ca4a3 --- /dev/null +++ b/tests/perf/simpleformat.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +/* snprintf.c times a format that uses widths and a '*' argument. This test + times a commonly used simple format instead: literal text plus %s, a size_t + and a curl_off_t, with no width or precision. It is the shape of the + messages curl builds with infof() and similar all over the library; it is + modeled on one in lib/imap.c rather than copied from it, so it measures the + common case rather than the elaborate one. The sink is a caller's buffer, + as in snprintf.c. */ + +static int test_simpleformat(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + + curl_off_t loops = 10000000, loop; + + char buffer[256]; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + curl_msnprintf(buffer, sizeof(buffer), + "%s: wrote %zu bytes, %" CURL_FORMAT_CURL_OFF_T + " bytes are left for transfer\n", + "upload", (size_t)4096, (curl_off_t)1048576); + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loops ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%02lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/snprintf.c b/tests/perf/snprintf.c index 22087e17ce73..12e8e1a2d2cc 100644 --- a/tests/perf/snprintf.c +++ b/tests/perf/snprintf.c @@ -58,7 +58,7 @@ static int test_snprintf(int argc, const char **argv) hn = loops ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/urldecode.c b/tests/perf/urldecode.c new file mode 100644 index 000000000000..c37cc8d5c1bf --- /dev/null +++ b/tests/perf/urldecode.c @@ -0,0 +1,85 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "first.h" + +/* every octet 0-255 URL encoded (or not). In two parts to avoid compilers + complaining */ +#define URLENCODED1 \ + "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15" \ + "%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B" \ + "%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83" \ + "%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99" + +#define URLENCODED2 \ + "%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF" \ + "%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5" \ + "%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB" \ + "%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1" \ + "%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF" + +static int test_urldecode(int argc, const char **argv) +{ + struct curltime start; + struct curltime end; + timediff_t us; + long long hn; + curl_off_t loops = 2500000, loop; + char buffer[1024]; + int len; + + if(argc > 1) { + const char *ptr = argv[1]; + curl_off_t num; + if(!curlx_str_number(&ptr, &num, CURL_OFF_T_MAX) && !*ptr) + loops = num; + } + + len = curl_msnprintf(buffer, sizeof(buffer), + "%s%s", URLENCODED1, URLENCODED2); + + start = curlx_now(); + for(loop = 0; loop < loops; loop++) { + char *unesc = NULL; + int unlen = 0; + /* now unescape it again */ + unesc = curl_easy_unescape(NULL, buffer, len, &unlen); + if(!unesc) { + curl_mfprintf(stderr, "unexpected unescape error\n"); + return 1; + } + curl_free(unesc); + } + end = curlx_now(); + us = curlx_timediff_us(end, start); /* how many microseconds */ + hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ + curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" + "Time: %lld usecs\n" + "Time/loop: %lld.%02lld ns\n", + loops, + (long long)us, + (long long)hn / 100, + (long long)hn % 100); + return 0; +} diff --git a/tests/perf/percent.c b/tests/perf/urlencode.c similarity index 83% rename from tests/perf/percent.c rename to tests/perf/urlencode.c index 345b78e1f122..71b9e5fc3df7 100644 --- a/tests/perf/percent.c +++ b/tests/perf/urlencode.c @@ -23,7 +23,7 @@ ***************************************************************************/ #include "first.h" -static int test_percent(int argc, const char **argv) +static int test_urlencode(int argc, const char **argv) { struct curltime start; struct curltime end; @@ -47,18 +47,7 @@ static int test_percent(int argc, const char **argv) for(loop = 0; loop < loops; loop++) { char *encoded = curl_easy_escape(NULL, (char *)array, (int)sizeof(array)); - if(encoded) { - char *unesc = NULL; - int unlen = 0; - /* now unescape it again */ - unesc = curl_easy_unescape(NULL, encoded, 0, &unlen); - if(!unesc) { - curl_mfprintf(stderr, "unexpected unescape error\n"); - return 1; - } - curl_free(unesc); - } - else { + if(!encoded) { curl_mfprintf(stderr, "unexpected escape error\n"); return 1; } @@ -69,7 +58,7 @@ static int test_percent(int argc, const char **argv) hn = loop ? us * 100000 / loops : 0; /* 100 times too big */ curl_mprintf("Loops: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/loop: %lld.%0lld ns\n", + "Time/loop: %lld.%02lld ns\n", loops, (long long)us, (long long)hn / 100, diff --git a/tests/perf/urlparser.c b/tests/perf/urlparser.c index 934e6770809d..594830a8cde0 100644 --- a/tests/perf/urlparser.c +++ b/tests/perf/urlparser.c @@ -1172,7 +1172,7 @@ static int test_urlparser(int argc, const char **argv) hn = count ? us * 100000 / count : 0; /* 100 times too big */ curl_mprintf("URLs: %" CURL_FORMAT_CURL_OFF_T "\n" "Time: %lld usecs\n" - "Time/URL: %lld.%0lld ns\n" + "Time/URL: %lld.%02lld ns\n" "URLs/sec: %lld\n" "Errors: %" CURL_FORMAT_CURL_OFF_T "\n", count, diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index abf6918bb334..000000000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (C) Daniel Stenberg, , et al. -# -# SPDX-License-Identifier: curl - -impacket==0.13.1 diff --git a/tests/runner.pm b/tests/runner.pm index 5187fc094f56..dd2dfc44dc43 100644 --- a/tests/runner.pm +++ b/tests/runner.pm @@ -64,8 +64,6 @@ BEGIN { $gdbxwin $shallow $tortalloc - $valgrind_logfile - $valgrind_tool ); # these are for debugging only @@ -115,8 +113,6 @@ use memanalyzer; # Global variables set elsewhere but used only by this package # These may only be set *before* runner_init is called our $DBGCURL = $CURL; #"../src/.libs/curl"; # alternative for debugging -our $valgrind_logfile = "--log-file"; # the option name for valgrind >=3 -our $valgrind_tool = "--tool=memcheck"; our $gdb = checktestcmd("gdb"); our $gdbthis = 0; # run test case with debugger (gdb or lldb) our $gdbxwin; # use windowed gdb when using gdb @@ -772,7 +768,7 @@ sub singletest_prepare { my @ldparts = split(/\//, $LOGDIR); my $nparts = @ldparts; my @parts = split(/\//, $path); - if(join("/", @parts[0..$nparts-1]) eq $LOGDIR) { + if(join("/", @parts[0..$nparts - 1]) eq $LOGDIR) { # the file is in $LOGDIR/ my $d = shift @parts; for(@parts) { @@ -862,7 +858,10 @@ sub singletest_run { $tool = $CMDLINE; $disablevalgrind = 1; } - elsif(!$tool && !$keywords{"unittest"}) { + elsif(!$tool && + !$keywords{"unittest"} && + !$keywords{"tunittest"} && + !$keywords{"libtest"}) { # run curl, add suitable command line options my $inc = ""; if((!$cmdhash{'option'}) || ($cmdhash{'option'} !~ /no-include/)) { @@ -907,8 +906,17 @@ sub singletest_run { $tool_name = "unit$testnum"; $tool = $tool_name . exe_ext('TOOL'); } + # Default the tool to a libtest with the same number as the test + elsif($keywords{"libtest"} && !$tool) { + $tool_name = "lib$testnum"; + $tool = $tool_name . exe_ext('TOOL'); + } + elsif($keywords{"tunittest"} && !$tool) { + $tool_name = "tool$testnum"; + $tool = $tool_name . exe_ext('TOOL'); + } - if($tool =~ /^lib/) { + if($tool =~ /^(lib|cli_)/) { $tool = "libtests" . exe_ext('TOOL'); $CMDLINE = $LIBDIR . $tool; } @@ -982,12 +990,12 @@ sub singletest_run { if(use_valgrind() && !$disablevalgrind) { my $valgrindcmd = "$valgrind "; - $valgrindcmd .= "$valgrind_tool " if($valgrind_tool); + $valgrindcmd .= "--tool=memcheck "; # valgrind 2.1.x+ $valgrindcmd .= "--quiet --leak-check=yes "; $valgrindcmd .= "--suppressions=$srcdir/valgrind.supp "; # $valgrindcmd .= "--gen-suppressions=all "; $valgrindcmd .= "--num-callers=16 "; - $valgrindcmd .= "${valgrind_logfile}=$LOGDIR/valgrind$testnum"; + $valgrindcmd .= "--log-file=$LOGDIR/valgrind$testnum"; # valgrind >=3 $CMDLINE = "$valgrindcmd $CMDLINE"; } diff --git a/tests/runtests.pl b/tests/runtests.pl index b5a2b4df9cd2..0d997f1ed4c1 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -130,6 +130,7 @@ BEGIN my $uname_release = qx(uname -r); my $is_wsl = $uname_release =~ /Microsoft$/; +my $uname_release_ver = $uname_release =~ /^(\d+)\.(\d+)\.(\d+)/ ? $1 * 10000 + $2 * 100 + $3 : 0; my $http_ipv6; # set if HTTP server has IPv6 support my $http_unix; # set if HTTP server has Unix sockets support @@ -173,6 +174,7 @@ BEGIN my %singletest_logs; # log messages while in singletest array ref by runner my $singletest_bufferedrunner; # runner ID which is buffering logs my %runnerids; # runner IDs by number +my %runnernums; # runner numbers by IDs (inverted %runnerids) my @runnersidle; # runner IDs idle and ready to execute a test my %countforrunner; # test count by runner ID my %runnersrunning; # tests currently running by runner ID @@ -269,11 +271,8 @@ sub catch_usr1 { print scalar(%runnersrunning) . " busy test runner(s) of " . scalar(keys %runnerids) . "\r\n"; foreach my $rid (sort(keys(%runnersrunning))) { my $runnernum = "unknown"; - foreach my $rnum (keys %runnerids) { - if($runnerids{$rnum} == $rid) { - $runnernum = $rnum; - last; - } + if(exists $runnernums{$rid}) { + $runnernum = $runnernums{$rid}; } print "Runner $runnernum (id $rid) running test $runnersrunning{$rid} in state $singletest_state{$rid}\r\n"; } @@ -571,6 +570,11 @@ sub checksystemfeatures { if($curl =~ /cygwin|msys/i) { $feature{"cygwin"} = 1; } + # Assume set but empty environment variables are supported, except + # by MSYS2 <3.4.2 (2022-12-16) processes starting non-Cygwin ones. + if(($^O ne 'cygwin' && $^O ne 'msys') || $curl =~ /cygwin|msys/i || $uname_release_ver >= 30605) { + $feature{"blank-env"} = 1; + } if($libcurl =~ /\sschannel\b/i) { $feature{"Schannel"} = 1; $feature{"SSLpinning"} = 1; @@ -1041,11 +1045,8 @@ sub getrunnerlogdir { if($jobs <= 1) { return $LOGDIR; } - # TODO: speed up this O(n) operation - for my $runnernum (keys %runnerids) { - if($runnerid eq $runnerids{$runnernum}) { - return "$LOGDIR/$runnernum"; - } + if(exists $runnernums{$runnerid}) { + return "$LOGDIR/$runnernums{$runnerid}"; } die "Internal error: runner ID $runnerid not found"; } @@ -1462,7 +1463,6 @@ sub singletest_check { if($res) { return -1; } - $ok .= "p"; } else { @@ -1623,7 +1623,6 @@ sub singletest_check { if($res) { return -1; } - $ok .= "P"; } else { @@ -2320,8 +2319,10 @@ sub createrunners { my $dir = getrunnernumlogdir($runnernum); cleardir($dir); mkdir($dir, 0777); - $runnerids{$runnernum} = runner_init($dir, $jobs); - runnerready($runnerids{$runnernum}); + my $id = runner_init($dir, $jobs); + $runnerids{$runnernum} = $id; + $runnernums{$id} = $runnernum; + runnerready($id); } } @@ -2695,14 +2696,6 @@ sub pickrunner { undef $valgrind; } else { - # since valgrind 2.1.x, '--tool' option is mandatory - # use it, if it is supported by the version installed on the system - # (this happened in 2003, so we could probably do not need to care about - # that old version any longer and delete this check) - runclient("valgrind --help 2>&1 | grep -- --tool >$dev_null 2>&1"); - if(($? >> 8)) { - $valgrind_tool = ""; - } open(my $curlh, "<", $CURL); my $l = <$curlh>; if($l =~ /^\#\!/) { @@ -2710,20 +2703,6 @@ sub pickrunner { $valgrind = "../libtool --mode=execute $valgrind"; } close($curlh); - - # valgrind 3 renamed the --logfile option to --log-file!!! - # (this happened in 2005, so we could probably do not need to care about - # that old version any longer and delete this check) - my $ver = join(' ', runclientoutput("valgrind --version")); - # cut off all but digits and dots - $ver =~ s/[^0-9.]//g; - - if($ver =~ /^(\d+)/) { - $ver = $1; - if($ver < 3) { - $valgrind_logfile = "--logfile"; - } - } } } @@ -2764,6 +2743,8 @@ sub pickrunner { $mintotal = $ENV{"CURL_TEST_MIN"}; if($useshares) { $mintotal /= $useshares; + # add safety margin to avoid potential underruns + $mintotal = $mintotal - 30 < 1 ? 1 : $mintotal - 30; } } diff --git a/tests/secureserver.pl b/tests/secureserver.pl index 9b12b4425a6c..2429d7bfb619 100755 --- a/tests/secureserver.pl +++ b/tests/secureserver.pl @@ -233,7 +233,7 @@ sub exit_signal_handler { } exit 1; } -$stunnel_version = (100*$ver_major) + $ver_minor; +$stunnel_version = (100 * $ver_major) + $ver_minor; #*************************************************************************** # Verify minimum stunnel required version diff --git a/tests/server/Makefile.inc b/tests/server/Makefile.inc index 33a578b8594a..e72ae5d75f54 100644 --- a/tests/server/Makefile.inc +++ b/tests/server/Makefile.inc @@ -35,7 +35,7 @@ UTILS_H = CURLX_C = \ ../../lib/curlx/base64.c \ - ../../lib/curlx/fopen.c \ + ../../lib/curlx/win32-fopen.c \ ../../lib/curlx/inet_ntop.c \ ../../lib/curlx/inet_pton.c \ ../../lib/curlx/multibyte.c \ diff --git a/tests/server/first.h b/tests/server/first.h index 1de9af74b793..bec2210a58eb 100644 --- a/tests/server/first.h +++ b/tests/server/first.h @@ -63,7 +63,7 @@ extern const struct entry_s s_entries[]; #endif #include "curlx/base64.h" /* for curlx_base64* */ -#include "curlx/fopen.h" /* for curlx_f*() */ +#include "curlx/win32-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/nonblock.h" /* for curlx_nonblock() */ @@ -181,6 +181,8 @@ static const char *server_unix_socket = NULL; static uint16_t server_port = 0; static const char *socket_type = "IPv4"; static int socket_domain = AF_INET; +/* file in which additional instructions may be found */ +extern const char *cmdfile; #define SERVERLOGS_LOCKDIR "lock" /* within logdir */ diff --git a/tests/server/getpart.c b/tests/server/getpart.c index 0cc95f633366..463e829497bd 100644 --- a/tests/server/getpart.c +++ b/tests/server/getpart.c @@ -37,7 +37,7 @@ * line_length() * * Counts the number of characters in a line including a new line. - * Unlike strlen() it does not stop at nul bytes. + * Unlike strlen() it does not stop at NUL bytes. * */ static size_t line_length(const char *buffer, int bytestocheck) @@ -122,7 +122,7 @@ static int readline(char **buffer, size_t *bufsize, size_t *length, * This appends data from a given source buffer to the end of the used part of * a destination buffer. Arguments relative to the destination buffer are, the * address of a pointer to the destination buffer 'dst_buf', the length of data - * in destination buffer excluding potential null string termination 'dst_len', + * in destination buffer excluding potential string null-terminator 'dst_len', * the allocated size of destination buffer 'dst_alloc'. All three destination * buffer arguments may be modified by this function. Arguments relative to the * source buffer are, a pointer to the source buffer 'src_buf' and indication diff --git a/tests/server/mqttd.c b/tests/server/mqttd.c index 766b86ca8a32..d0f9ea1603e8 100644 --- a/tests/server/mqttd.c +++ b/tests/server/mqttd.c @@ -166,9 +166,9 @@ static void logprotocol(mqttdir dir, optr += 2; left -= 2; } - fprintf(output, "%s %s %x %s\n", + fprintf(output, "%s %s %lx %s\n", dir == FROM_CLIENT ? "client" : "server", - prefix, (unsigned int)remlen, data); + prefix, (unsigned long)remlen, data); } /* return 0 on success */ @@ -413,6 +413,25 @@ static int publish(FILE *dump, static char topic[MAX_TOPIC_LENGTH + 1]; +static bool readall(curl_socket_t fd, unsigned char *buffer, size_t len) +{ + size_t nread = 0; + + while(nread < len) { + ssize_t rc = sread(fd, buffer + nread, len - nread); + if(rc <= 0) { + if(rc < 0 && SOCKERRNO == SOCKEINTR && !got_exit_signal) + continue; + logmsg("READ %zd bytes [SHORT!]", rc); + return FALSE; + } + logmsg("READ %zd bytes", rc); + loghex(buffer + nread, rc); + nread += (size_t)rc; + } + return TRUE; +} + static bool fixedheader(curl_socket_t fd, unsigned char *bytep, size_t *remaining_lengthp, @@ -421,23 +440,18 @@ static bool fixedheader(curl_socket_t fd, /* get the fixed header */ unsigned char buffer[10]; - /* get the first two bytes */ - ssize_t rc = sread(fd, buffer, 2); size_t i; - if(rc < 2) { - logmsg("READ %zd bytes [SHORT!]", rc); + + /* get the first two bytes */ + if(!readall(fd, buffer, 2)) return FALSE; /* fail */ - } - logmsg("READ %zd bytes", rc); - loghex(buffer, rc); *bytep = buffer[0]; /* if the length byte has the top bit set, get the next one too */ i = 1; while(buffer[i] & 0x80) { i++; - rc = sread(fd, &buffer[i], 1); - if(rc != 1) { + if(!readall(fd, &buffer[i], 1)) { logmsg("Remaining Length broken"); return FALSE; } @@ -513,14 +527,10 @@ static curl_socket_t mqttit(curl_socket_t fd) buffer = newbuffer; } - if(remaining_length) { - /* reading variable header and payload into buffer */ - rc = sread(fd, buffer, remaining_length); - if(rc > 0) { - logmsg("READ %zd bytes", rc); - loghex(buffer, rc); - } - } + /* Read the complete variable header and payload. */ + if(!readall(fd, buffer, remaining_length)) + goto end; + rc = (ssize_t)remaining_length; if(byte == MQTT_MSG_CONNECT) { logprotocol(FROM_CLIENT, "CONNECT", remaining_length, dump, buffer, rc); @@ -658,11 +668,9 @@ static curl_socket_t mqttit(curl_socket_t fd) #endif /* expect a disconnect here */ /* get the request */ - rc = sread(fd, &buffer[0], 2); - - logmsg("READ %zd bytes [DISCONNECT]", rc); - loghex(buffer, rc); - logprotocol(FROM_CLIENT, "DISCONNECT", 0, dump, buffer, rc); + if(!readall(fd, buffer, 2)) + goto end; + logprotocol(FROM_CLIENT, "DISCONNECT", 0, dump, buffer, 2); goto end; } else { diff --git a/tests/server/rtspd.c b/tests/server/rtspd.c index 44e4f68655a4..59db53cbd50e 100644 --- a/tests/server/rtspd.c +++ b/tests/server/rtspd.c @@ -141,6 +141,24 @@ static const char doc404_RTSP[] = "RTSP/1.0 404 Not Found\r\n" #define RTP_DATA_SIZE 12 static const char RTP_DATA[] = "$_1234\n\0Rsdf"; +/* parse the file on disk that might have a test number for us */ +static int rtspd_cmdfile(struct rtspd_httprequest *req) +{ + FILE *f = curlx_fopen(cmdfile, FOPEN_READTEXT); + if(f) { + int testnum = DOCNUMBER_NOTHING; + char buf[256]; + while(fgets(buf, sizeof(buf), f)) { + if(sscanf(buf, "Testnum %d", &testnum) == 1) { + logmsg("[%s] cmdfile says testnum %d", cmdfile, testnum); + req->testno = testnum; + } + } + curlx_fclose(f); + } + return 0; +} + static int rtspd_ProcessRequest(struct rtspd_httprequest *req) { const char *line = &req->reqbuf[req->checkindex]; @@ -388,8 +406,11 @@ static int rtspd_ProcessRequest(struct rtspd_httprequest *req) else req->testno = DOCNUMBER_CONNECT; } - else { - logmsg("Did not find test number in PATH"); + if(req->testno == DOCNUMBER_NOTHING) + /* might get the test number */ + rtspd_cmdfile(req); + if(req->testno == DOCNUMBER_NOTHING) { + logmsg("Did not find test number"); req->testno = DOCNUMBER_404; } } diff --git a/tests/server/socksd.c b/tests/server/socksd.c index 29fe56264e28..4b721e111040 100644 --- a/tests/server/socksd.c +++ b/tests/server/socksd.c @@ -49,6 +49,8 @@ * 0 - no auth * 1 - GSSAPI (not supported) * 2 - user + password + * "method2 [number]" - connect method to respond with on the second + * connection of the test, defaults to the "method" one * "response [number]" - the decimal number to respond to a connect * SOCKS5: 0 is OK, SOCKS4: 90 is ok * @@ -58,6 +60,8 @@ static const char *backendaddr = "127.0.0.1"; static uint16_t backendport = 0; /* default is use client's */ +static unsigned int connections; /* clients served in the current test */ +static int last_testnum = -1; /* the test the connections belong to */ struct socksd_configurable { unsigned char version; /* initial version byte in the request must match @@ -66,6 +70,8 @@ struct socksd_configurable { unsigned char nmethods_max; /* maximum number of nmethods to expect */ unsigned char responseversion; unsigned char responsemethod; + int responsemethod2; /* negative means unset */ + int testnum; unsigned char reqcmd; unsigned char connectrep; uint16_t port; /* backend port */ @@ -79,6 +85,7 @@ struct socksd_configurable { #define CONFIG_NMETHODS_MAX 3 #define CONFIG_RESPONSEVERSION CONFIG_VERSION #define CONFIG_RESPONSEMETHOD 0 /* no auth */ +#define CONFIG_RESPONSEMETHOD2 (-1) /* unset, use CONFIG_RESPONSEMETHOD */ #define CONFIG_REQCMD 1 /* CONNECT */ #define CONFIG_PORT backendport #define CONFIG_ADDR backendaddr @@ -96,6 +103,8 @@ static void socksd_resetdefaults(void) s_config.nmethods_max = CONFIG_NMETHODS_MAX; s_config.responseversion = CONFIG_RESPONSEVERSION; s_config.responsemethod = CONFIG_RESPONSEMETHOD; + s_config.responsemethod2 = CONFIG_RESPONSEMETHOD2; + s_config.testnum = 0; s_config.reqcmd = CONFIG_REQCMD; s_config.connectrep = CONFIG_CONNECTREP; s_config.port = CONFIG_PORT; @@ -175,6 +184,20 @@ static void socksd_getconfig(void) logmsg("method [%d] set", s_config.responsemethod); } } + else if(!strcmp(key, "method2")) { + pval = value; + if(!curlx_str_number(&pval, &num, 0xff)) { + s_config.responsemethod2 = (int)num; + logmsg("method2 [%d] set", s_config.responsemethod2); + } + } + else if(!strcmp(key, "Testnum")) { + pval = value; + if(!curlx_str_number(&pval, &num, INT_MAX)) { + s_config.testnum = (int)num; + logmsg("testnum = %d", s_config.testnum); + } + } else if(!strcmp(key, "response")) { pval = value; if(!curlx_str_number(&pval, &num, 0xff)) { @@ -303,6 +326,14 @@ static curl_socket_t sockit(curl_socket_t fd) socksd_getconfig(); + if(last_testnum != s_config.testnum) { + last_testnum = s_config.testnum; + connections = 0; + } + + if((++connections == 2) && (s_config.responsemethod2 >= 0)) + s_config.responsemethod = (unsigned char)s_config.responsemethod2; + rc = sread(fd, buffer, sizeof(buffer)); if(rc <= 0) { logmsg("SOCKS identifier message missing, recv returned %zd", rc); @@ -703,15 +734,13 @@ static bool socksd_incoming(curl_socket_t listenfd) } for(i = 0; i < 2; i++) { struct perclient *cp = &c[i]; - if(cp->used) { - if(tunnel(cp, &fds_read)) { - logmsg("SOCKS transfer completed. Bytes: < %zu > %zu", - cp->fromremote, cp->fromclient); - sclose(cp->clientfd); - sclose(cp->remotefd); - cp->used = FALSE; - clients--; - } + if(cp->used && tunnel(cp, &fds_read)) { + logmsg("SOCKS transfer completed. Bytes: < %zu > %zu", + cp->fromremote, cp->fromclient); + sclose(cp->clientfd); + sclose(cp->remotefd); + cp->used = FALSE; + clients--; } } } while(clients); diff --git a/tests/server/sws.c b/tests/server/sws.c index 5d7087927d9b..e41d3d5eb993 100644 --- a/tests/server/sws.c +++ b/tests/server/sws.c @@ -104,7 +104,7 @@ static size_t num_sockets = 0; (is_proxy ? REQUEST_PROXY_DUMP : REQUEST_DUMP) /* file in which additional instructions may be found */ -static const char *cmdfile = "log/server.cmd"; +const char *cmdfile = "log/server.cmd"; /* very-big-path support */ #define MAXDOCNAMELEN 140000 @@ -2131,6 +2131,17 @@ static int test_sws(int argc, const char *argv[]) goto sws_cleanup; if(rc < 0) { + if(req->testno == DOCNUMBER_NOTHING) { + /* The test number is unknown here, so connmon may be left over + from a previous test case, for example when a client closes + a connection without sending anything. Use the setting of + the test case in server.cmd, or disable it without one. */ + req->connmon = FALSE; + parse_cmdfile(req); + if(req->testno != DOCNUMBER_NOTHING) + sws_parse_servercmd(req); + } + logmsg("====> Client disconnect %d", req->connmon); if(req->connmon) { diff --git a/tests/server/tftpd.c b/tests/server/tftpd.c index 0ca6aca83e7d..3cb63ce708bb 100644 --- a/tests/server/tftpd.c +++ b/tests/server/tftpd.c @@ -196,7 +196,7 @@ static tftphdr_storage_t ackbuf; static curl_socket_t peer = CURL_SOCKET_BAD; static unsigned int timeout; -static unsigned int maxtimeout = 5 * TIMEOUT; +static unsigned int maxtimeout = TIMEOUT * 5; static int tftpd_wrotepidfile = 0; static int tftpd_wroteportfile = 0; @@ -984,7 +984,7 @@ static int do_tftp(struct testcase *test, struct tftphdr *tp, ssize_t size) recvtimeout = sizeof(recvtimeoutbak); getsockopt(peer, SOL_SOCKET, SO_RCVTIMEO, (char *)&recvtimeoutbak, (int *)&recvtimeout); - recvtimeout = TIMEOUT*1000; + recvtimeout = TIMEOUT * 1000; setsockopt(peer, SOL_SOCKET, SO_RCVTIMEO, (const char *)&recvtimeout, sizeof(recvtimeout)); #endif @@ -1175,7 +1175,7 @@ static int test_tftpd(int argc, const char **argv) } } - maxtimeout = 5 * TIMEOUT; + maxtimeout = TIMEOUT * 5; tp = &trsbuf.hdr; tp->th_opcode = ntohs(tp->th_opcode); diff --git a/tests/serverhelp.pm b/tests/serverhelp.pm index ac451cb04b7f..ccb9476ac630 100644 --- a/tests/serverhelp.pm +++ b/tests/serverhelp.pm @@ -123,7 +123,7 @@ sub servername_str { $proto = uc($proto) if($proto); die "unsupported protocol: '$proto'" unless($proto && - ($proto =~ /^(((DNS|FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|DICT|SMB|SMBS|TELNET|MQTT|MQTTS))$/)); + ($proto =~ /^(((DNS|FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|DICT|TELNET|MQTT|MQTTS))$/)); $ipver = (not $ipver) ? 'ipv4' : lc($ipver); die "unsupported IP version: '$ipver'" unless($ipver && diff --git a/tests/servers.pm b/tests/servers.pm index 99db988124e1..ebcacba239f6 100644 --- a/tests/servers.pm +++ b/tests/servers.pm @@ -175,11 +175,11 @@ sub checkcmd { # return the port number # sub getfreeport { - my ($ipnum) = @_; + my ($ipnum, $reuse) = @_; my $server = IO::Socket->new(LocalPort => 0, Domain => $ipnum == 6 ? AF_INET6 : AF_INET, Type => SOCK_STREAM, - Reuse => 1, + Reuse => $reuse, Listen => 10 ) or die "Could not create tcp server socket: $@\n"; @@ -235,7 +235,7 @@ sub init_serverpidfile_hash { } } for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', - 'dict', 'smb', 'smbs', 'telnet', 'mqtt', 'mqtts', + 'dict', 'telnet', 'mqtt', 'mqtts', 'https-mtls', 'dns')) { for my $ipvnum ((4, 6)) { for my $idnum ((1, 2)) { @@ -797,66 +797,6 @@ sub verifypid { return $pid; } -####################################################################### -# Verify that the server that runs on $ip, $port is our server. This also -# implies that we can speak with it, as there might be occasions when the -# server runs fine but we cannot talk to it ("Failed to connect to ::1: Cannot -# assign requested address") -# -sub verifysmb { - my ($proto, $ipvnum, $idnum, $ip, $port) = @_; - my $server = servername_id($proto, $ipvnum, $idnum); - my $time = time(); - my $extra = ""; - - my $verifylog = "$LOGDIR/". - servername_canon($proto, $ipvnum, $idnum) .'_verify.log'; - unlink($verifylog) if(-f $verifylog); - - my $flags = "--max-time $server_response_maxtime "; - $flags .= "--silent "; - $flags .= "--verbose "; - $flags .= "--globoff "; - $flags .= "-u 'curltest:curltest' "; - $flags .= $extra; - $flags .= "\"$proto://$ip:$port/SERVER/verifiedserver\""; - - my $cmd = exerunner() . "$VCURL $flags 2>$verifylog"; - - # check if this is our server running on this port: - logmsg "RUN: $cmd\n" if($verbose); - my @data = runclientoutput($cmd); - - my $res = $? >> 8; # rotate the result - if($res & 128) { - logmsg "RUN: curl command died with a coredump\n"; - return -1; - } - - my $pid = 0; - foreach my $line (@data) { - if($line =~ /WE ROOLZ: (\d+)/) { - # this is our test server with a known pid! - $pid = 0+$1; - last; - } - } - if($pid <= 0 && @data && $data[0]) { - # this is not a known server - logmsg "RUN: Unknown server on our $server port: $port\n"; - return 0; - } - # we can/should use the time it took to verify the server as a measure - # on how fast/slow this host is. - my $took = int(0.5+time()-$time); - - if($verbose) { - logmsg "RUN: Verifying our test $server server took $took seconds\n"; - } - - return $pid; -} - ####################################################################### # Verify that the server that runs on $ip, $port is our server. This also # implies that we can speak with it, as there might be occasions when the @@ -950,7 +890,6 @@ my %protofunc = ('http' => \&verifyhttp, 'socks5unix' => \&verifypid, 'gopher' => \&verifyhttp, 'dict' => \&verifyftp, - 'smb' => \&verifysmb, 'telnet' => \&verifytelnet); ####################################################################### @@ -1105,8 +1044,8 @@ sub runhttp2server { $flags .= "--connect $HOSTIP:" . protoport("http") . " "; $flags .= $verbose_flag if($debugprotocol); - my $port = getfreeport($ipvnum); - my $port2 = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 0); + my $port2 = getfreeport($ipvnum, 0); my $aflags = "--port $port --port2 $port2 $flags"; my $cmd = "$exe $aflags"; my ($http2pid, $pid2) = startnew($cmd, $pidfile, 15, 0); @@ -1167,7 +1106,7 @@ sub runhttp3server { $flags .= "--cert \"$cert\" " if($cert); $flags .= $verbose_flag if($debugprotocol); - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 0); my $aflags = "--port $port $flags"; my $cmd = "$exe $aflags"; my ($http3pid, $pid3) = startnew($cmd, $pidfile, 15, 0); @@ -1251,7 +1190,7 @@ sub runhttpsserver { $flags .= "--connect " . protoport("httpproxy"); } - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); my $options = "$flags --accept $port"; my $cmd = "$perl " . shell_quote("$srcdir/secureserver.pl") . " " . $options; my ($httpspid, $pid2) = startnew($cmd, $pidfile, 15, 0); @@ -1394,7 +1333,7 @@ sub runsecureserver { $flags .= "--stunnel \"$stunnel\" --srcdir \"$srcdir\" "; $flags .= "--connect $clearport"; - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); my $options = "$flags --accept $port"; my $cmd = "$perl " . shell_quote("$srcdir/secureserver.pl") . " " . $options; @@ -1697,7 +1636,7 @@ sub runsshserver { } my @tports; - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); push @tports, $port; @@ -1951,7 +1890,7 @@ sub rundictserver { $flags .= "--srcdir \"$srcdir\" "; $flags .= "--host $HOSTIP"; - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); my $aflags = "--port $port $flags"; my $cmd = "$srcdir/dictserver.py $aflags"; my ($dictpid, $pid2) = startnew($cmd, $pidfile, 15, 0); @@ -1973,67 +1912,6 @@ sub rundictserver { return (0+!$dictpid, $dictpid, $pid2, $port); } -####################################################################### -# start the SMB server -# -sub runsmbserver { - my ($verb, $alt) = @_; - my $proto = "smb"; - my $ip = $HOSTIP; - my $ipvnum = 4; - my $idnum = 1; - - if($alt eq "ipv6") { - # No IPv6 - } - - my $server = servername_id($proto, $ipvnum, $idnum); - - my $pidfile = $serverpidfile{$server}; - - # do not retry if the server does not work - if($doesntrun{$pidfile}) { - return (2, 0, 0, 0); - } - - my $pid = processexists($pidfile); - if($pid > 0) { - stopserver($server, $pid); - } - unlink($pidfile) if(-f $pidfile); - - my $srvrname = servername_str($proto, $ipvnum, $idnum); - my $logfile = server_logfilename($LOGDIR, $proto, $ipvnum, $idnum); - - my $flags = ""; - $flags .= "--verbose 1 " if($debugprotocol); - $flags .= "--pidfile \"$pidfile\" --logfile \"$logfile\" "; - $flags .= "--id $idnum " if($idnum > 1); - $flags .= "--srcdir \"$srcdir\" "; - $flags .= "--host $HOSTIP"; - - my $port = getfreeport($ipvnum); - my $aflags = "--port $port $flags"; - my $cmd = "$srcdir/smbserver.py $aflags"; - my ($smbpid, $pid2) = startnew($cmd, $pidfile, 15, 0); - - if($smbpid <= 0 || !pidexists($smbpid)) { - # it is NOT alive - stopserver($server, $pid2); - $doesntrun{$pidfile} = 1; - $smbpid = $pid2 = 0; - logmsg "RUN: failed to start the $srvrname server\n"; - return (3, 0, 0, 0); - } - $doesntrun{$pidfile} = 0; - - if($verb) { - logmsg "RUN: $srvrname server PID $smbpid port $port\n"; - } - - return (0+!$smbpid, $smbpid, $pid2, $port); -} - ####################################################################### # start the telnet server # @@ -2072,7 +1950,7 @@ sub runnegtelnetserver { $flags .= "--id $idnum " if($idnum > 1); $flags .= "--srcdir \"$srcdir\""; - my $port = getfreeport($ipvnum); + my $port = getfreeport($ipvnum, 1); my $aflags = "--port $port $flags"; my $cmd = "$srcdir/negtelnetserver.py $aflags"; my ($ntelpid, $pid2) = startnew($cmd, $pidfile, 15, 0); @@ -2817,17 +2695,6 @@ sub startservers { $run{'dict'} = "$pid $pid2"; } } - elsif($what eq "smb") { - if(!$run{'smb'}) { - ($serr, $pid, $pid2, $PORT{"smb"}) = runsmbserver($verbose, ""); - if($pid <= 0) { - return ("failed starting SMB server", $serr); - } - logmsg sprintf ("* pid SMB => %d %d\n", $pid, $pid2) - if($verbose); - $run{'smb'} = "$pid $pid2"; - } - } elsif($what eq "telnet") { if(!$run{'telnet'}) { ($serr, $pid, $pid2, $PORT{"telnet"}) = @@ -2930,7 +2797,6 @@ sub subvariables { 'NOLISTEN', 'POP3', 'POP36', 'POP3S', 'RTSP', 'RTSP6', - 'SMB', 'SMBS', 'SMTP', 'SMTP6', 'SMTPS', 'SOCKS', 'SSH', diff --git a/tests/smbserver.py b/tests/smbserver.py deleted file mode 100755 index d14e7ff1d3cd..000000000000 --- a/tests/smbserver.py +++ /dev/null @@ -1,440 +0,0 @@ -#!/usr/bin/env python3 -# -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) Daniel Stenberg, , 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 -# -"""Server for testing SMB.""" - -import argparse -import configparser -import importlib.util -import logging -import os -import signal -import sys -import tempfile -import threading - -# Import our curl test data helper -from util import ClosingFileHandler, TestData - -# impacket needs to be installed in the Python environment -if importlib.util.find_spec('impacket') is None: - sys.stderr.write( - 'Warning: Python package impacket is required for smb testing; ' - 'use pip or your package manager to install it\n') - sys.exit(1) - -from impacket import smb as imp_smb -from impacket import smbserver as imp_smbserver -from impacket.nt_errors import STATUS_ACCESS_DENIED, STATUS_NO_SUCH_FILE, STATUS_SUCCESS - -log = logging.getLogger(__name__) -SERVER_MAGIC = "SERVER_MAGIC" -TESTS_MAGIC = "TESTS_MAGIC" -VERIFIED_REQ = "verifiedserver" -VERIFIED_RSP = "WE ROOLZ: {pid}\n" - - -class ShutdownHandler(threading.Thread): - """ - Cleanly shut down the SMB server. - - This can only be done from another thread while the server is in - serve_forever(), so a thread is spawned here that waits for a shutdown - signal before doing its thing. Use in a with statement around the - serve_forever() call. - """ - - def __init__(self, server): - super().__init__() - self.server = server - self.shutdown_event = threading.Event() - - def __enter__(self): - self.start() - signal.signal(signal.SIGINT, self._sighandler) - signal.signal(signal.SIGTERM, self._sighandler) - - def __exit__(self, *_): - # Call for shutdown in case it was not done already - self.shutdown_event.set() - # Wait for thread, and therefore also the server, to finish - self.join() - # Uninstall our signal handlers - signal.signal(signal.SIGINT, signal.SIG_DFL) - signal.signal(signal.SIGTERM, signal.SIG_DFL) - # Delete any temporary files created by the server during its run - log.info("Deleting %d temporary file(s)", len(self.server.tmpfiles)) - for f in self.server.tmpfiles: - os.unlink(f) - - def _sighandler(self, _signum, _frame): - # Wake up the cleanup task - self.shutdown_event.set() - - def run(self): - # Wait for shutdown signal - self.shutdown_event.wait() - # Notify the server to shut down - self.server.shutdown() - - -def smbserver(options): - """Start up a TCP SMB server that serves forever.""" - if options.pidfile: - pid = os.getpid() - # see tests/server/util.c function write_pidfile - if os.name == "nt": - pid += 4194304 - with open(options.pidfile, "w") as f: - f.write(str(pid)) - - # Here we write a mini config for the server - smb_config = configparser.ConfigParser() - smb_config.add_section("global") - smb_config.set("global", "server_name", "SERVICE") - smb_config.set("global", "server_os", "UNIX") - smb_config.set("global", "server_domain", "WORKGROUP") - smb_config.set("global", "log_file", "None") - smb_config.set("global", "credentials_file", "") - - # We need a share which allows us to test that the server is running - smb_config.add_section("SERVER") - smb_config.set("SERVER", "comment", "server function") - smb_config.set("SERVER", "read only", "yes") - smb_config.set("SERVER", "share type", "0") - smb_config.set("SERVER", "path", SERVER_MAGIC) - - # Have a share for tests. These files are auto-generated from the - # test input. - smb_config.add_section("TESTS") - smb_config.set("TESTS", "comment", "tests") - smb_config.set("TESTS", "read only", "yes") - smb_config.set("TESTS", "share type", "0") - smb_config.set("TESTS", "path", TESTS_MAGIC) - - if not options.srcdir or not os.path.isdir(options.srcdir): - raise ScriptError("--srcdir is mandatory") - - test_data_dir = os.path.join(options.srcdir, "data") - - smb_server = TestSmbServer((options.host, options.port), - config_parser=smb_config, - test_data_directory=test_data_dir) - log.info("[SMB] setting up SMB server on port %s", options.port) - smb_server.processConfigFile() - - # Start a thread that cleanly shuts down the server on a signal - with ShutdownHandler(smb_server): - # This blocks until smb_server.shutdown() is called - smb_server.serve_forever() - - return 0 - - -class TestSmbServer(imp_smbserver.SMBSERVER): - """ - Test server for SMB. - - It subclasses the impacket SMBSERVER and provides test functionality. - """ - - def __init__(self, - address, - config_parser=None, - test_data_directory=None): - imp_smbserver.SMBSERVER.__init__(self, - address, - config_parser=config_parser) - self.tmpfiles = [] - - # Set up a test data object so we can get test data later. - self.ctd = TestData(test_data_directory) - - # Override smbComNtCreateAndX so we can pretend to have files which - # do not exist. - self.hookSmbCommand(imp_smb.SMB.SMB_COM_NT_CREATE_ANDX, - self.create_and_x) - - def create_and_x(self, conn_id, smb_server, smb_command, recv_packet): - """ - Our version of smbComNtCreateAndX. - - It looks for special test files and fools the rest of the framework - into opening them as if they were normal files. - """ - conn_data = smb_server.getConnectionData(conn_id) - - # Wrap processing in a try block which allows us to throw SmbError - # to control the flow. - try: - ncax_params = imp_smb.SMBNtCreateAndX_Parameters( - smb_command["Parameters"]) - - path = self.get_share_path(conn_data, - ncax_params["RootFid"], - recv_packet["Tid"]) - log.info("[SMB] Requested share path: %s", path) - - disposition = ncax_params["Disposition"] - log.debug("[SMB] Requested disposition: %s", disposition) - - # Currently we only support reading files. - if disposition != imp_smb.FILE_OPEN: - raise SmbError(STATUS_ACCESS_DENIED, "Only support reading files") - - # Check to see if the path we were given is actually a - # magic path which needs generating on the fly. - if path not in [SERVER_MAGIC, TESTS_MAGIC]: - # Pass the command onto the original handler. - return imp_smbserver.SMBCommands.smbComNtCreateAndX(conn_id, - smb_server, - smb_command, - recv_packet) - - flags2 = recv_packet["Flags2"] - ncax_data = imp_smb.SMBNtCreateAndX_Data(flags=flags2, - data=smb_command["Data"]) - requested_file = imp_smbserver.decodeSMBString( - flags2, - ncax_data["FileName"]) - log.debug("[SMB] User requested file '%s'", requested_file) - - if path == SERVER_MAGIC: - fid, full_path = self.get_server_path(requested_file) - else: - assert path == TESTS_MAGIC - fid, full_path = self.get_test_path(requested_file) - - self.tmpfiles.append(full_path) - - resp_params = imp_smb.SMBNtCreateAndXResponse_Parameters() - resp_data = "" - - # Simple way to generate a fid - if len(conn_data["OpenedFiles"]) == 0: - fakefid = 1 - else: - fakefid = max(conn_data["OpenedFiles"].keys()) + 1 - resp_params["Fid"] = fakefid - resp_params["CreateAction"] = disposition - - if os.path.isdir(path): - resp_params[ - "FileAttributes"] = imp_smb.SMB_FILE_ATTRIBUTE_DIRECTORY - resp_params["IsDirectory"] = 1 - else: - resp_params["IsDirectory"] = 0 - resp_params["FileAttributes"] = ncax_params["FileAttributes"] - - # Get this file's information - resp_info, error_code = imp_smbserver.queryPathInformation( - os.path.dirname(full_path), os.path.basename(full_path), - level=imp_smb.SMB_QUERY_FILE_ALL_INFO) - - if error_code != STATUS_SUCCESS: - raise SmbError(error_code, "Failed to query path info") - - resp_params["CreateTime"] = resp_info["CreationTime"] - resp_params["LastAccessTime"] = resp_info[ - "LastAccessTime"] - resp_params["LastWriteTime"] = resp_info["LastWriteTime"] - resp_params["LastChangeTime"] = resp_info[ - "LastChangeTime"] - resp_params["FileAttributes"] = resp_info[ - "ExtFileAttributes"] - resp_params["AllocationSize"] = resp_info[ - "AllocationSize"] - resp_params["EndOfFile"] = resp_info["EndOfFile"] - - # Let's store the fid for the connection - # smbServer.log("Create file %s, mode:0x%x" % (pathName, mode)) - conn_data["OpenedFiles"][fakefid] = {} - conn_data["OpenedFiles"][fakefid]["FileHandle"] = fid - conn_data["OpenedFiles"][fakefid]["FileName"] = path - conn_data["OpenedFiles"][fakefid]["DeleteOnClose"] = False - - except SmbError as s: - log.debug("[SMB] SmbError hit: %s", s) - error_code = s.error_code - resp_params = "" - resp_data = "" - - resp_cmd = imp_smb.SMBCommand(imp_smb.SMB.SMB_COM_NT_CREATE_ANDX) - resp_cmd["Parameters"] = resp_params - resp_cmd["Data"] = resp_data - smb_server.setConnectionData(conn_id, conn_data) - - return [resp_cmd], None, error_code - - def get_share_path(self, conn_data, root_fid, tid): - conn_shares = conn_data["ConnectedShares"] - - if tid in conn_shares: - if root_fid > 0: - # If we have a rootFid, the path is relative to that fid - path = conn_data["OpenedFiles"][root_fid]["FileName"] - log.debug(f'RootFid present {path}!') - return path - - if "path" in conn_shares[tid]: - return conn_shares[tid]["path"] - - raise SmbError(STATUS_ACCESS_DENIED, "Connection share had no path") - raise SmbError(imp_smbserver.STATUS_SMB_BAD_TID, "TID was invalid") - - def get_server_path(self, requested_filename): - log.debug("[SMB] Get server path '%s'", requested_filename) - - if requested_filename != VERIFIED_REQ: - raise SmbError(STATUS_NO_SUCH_FILE, "Could not find the file") - - fid, filename = tempfile.mkstemp() - log.debug("[SMB] Created %s (%d) for storing '%s'", - filename, fid, requested_filename) - - contents = b'' - - if requested_filename == VERIFIED_REQ: - log.debug("[SMB] Verifying server is alive") - pid = os.getpid() - # see tests/server/util.c function write_pidfile - if os.name == "nt": - pid += 4194304 - contents = VERIFIED_RSP.format(pid=pid).encode('utf-8') - - self.write_to_fid(fid, contents) - return fid, filename - - def write_to_fid(self, fid, contents): - # Write the contents to file descriptor - os.write(fid, contents) - os.fsync(fid) - - # Rewind the file to the beginning so a read gets us the contents - os.lseek(fid, 0, os.SEEK_SET) - - def get_test_path(self, requested_filename): - log.info("[SMB] Get reply data from 'test%s'", requested_filename) - - fid, filename = tempfile.mkstemp() - log.debug("[SMB] Created %s (%d) for storing test '%s'", - filename, fid, requested_filename) - - try: - contents = self.ctd.get_test_data(requested_filename).encode('utf-8') - self.write_to_fid(fid, contents) - return fid, filename - - except Exception as e: - log.exception("Failed to make test file") - raise SmbError(STATUS_NO_SUCH_FILE, "Failed to make test file") from e - - -class SmbError(Exception): - def __init__(self, error_code, error_message): - super().__init__(error_message) - self.error_code = error_code - - -class ScriptRC: - """Enum for script return codes.""" - - SUCCESS = 0 - FAILURE = 1 - EXCEPTION = 2 - - -class ScriptError(Exception): - pass - - -def get_options(): - parser = argparse.ArgumentParser() - - parser.add_argument("--port", action="store", default=9017, - type=int, help="port to listen on") - parser.add_argument("--host", action="store", default="127.0.0.1", - help="host to listen on") - parser.add_argument("--verbose", action="store", type=int, default=0, - help="verbose output") - parser.add_argument("--pidfile", action="store", - help="filename for the PID") - parser.add_argument("--logfile", action="store", - help="filename for the log") - parser.add_argument("--srcdir", action="store", help="test directory") - parser.add_argument("--id", action="store", help="server ID") - parser.add_argument("--ipv4", action="store_true", default=0, - help="IPv4 flag") - - return parser.parse_args() - - -def setup_logging(options): - """Set up logging from the command line options.""" - root_logger = logging.getLogger() - add_stdout = False - - formatter = logging.Formatter("%(asctime)s %(levelname)-5.5s %(message)s") - - # Write out to a logfile - if options.logfile: - handler = ClosingFileHandler(options.logfile) - handler.setFormatter(formatter) - handler.setLevel(logging.DEBUG) - root_logger.addHandler(handler) - else: - # The logfile was not specified. Add a stdout logger. - add_stdout = True - - if options.verbose: - # Add a stdout logger as well in verbose mode - root_logger.setLevel(logging.DEBUG) - add_stdout = True - else: - root_logger.setLevel(logging.WARNING) - - if add_stdout: - stdout_handler = logging.StreamHandler(sys.stdout) - stdout_handler.setFormatter(formatter) - stdout_handler.setLevel(logging.DEBUG) - root_logger.addHandler(stdout_handler) - - -if __name__ == '__main__': - # Get the options from the user. - options = get_options() - - # Setup logging using the user options - setup_logging(options) - - # Run main script. - try: - rc = smbserver(options) - except Exception: - log.exception('Error in SMB server') - rc = ScriptRC.EXCEPTION - - if options.pidfile and os.path.isfile(options.pidfile): - os.unlink(options.pidfile) - - log.info("[SMB] Returning %d", rc) - sys.exit(rc) diff --git a/tests/sshhelp.pm b/tests/sshhelp.pm index 2481de3278de..84c9a8513fba 100644 --- a/tests/sshhelp.pm +++ b/tests/sshhelp.pm @@ -383,7 +383,7 @@ sub sshversioninfo { $minor = $2; $patch = $4 ? $4 : 0; $sshid = 'OpenSSH'; - $versnum = (100*$major) + (10*$minor) + $patch; + $versnum = (100 * $major) + (10 * $minor) + $patch; $versstr = "$sshid $major.$minor.$patch"; $error = undef; last; @@ -393,7 +393,7 @@ sub sshversioninfo { $minor = $2; $patch = $4 ? $4 : 0; $sshid = 'OpenSSH-Windows'; - $versnum = (100*$major) + (10*$minor) + $patch; + $versnum = (100 * $major) + (10 * $minor) + $patch; $versstr = "$sshid $major.$minor.$patch"; $error = undef; last; @@ -403,7 +403,7 @@ sub sshversioninfo { $minor = $2; $patch = $4 ? $4 : 0; $sshid = 'SunSSH'; - $versnum = (100*$major) + (10*$minor) + $patch; + $versnum = (100 * $major) + (10 * $minor) + $patch; $versstr = "$sshid $major.$minor.$patch"; $error = undef; last; diff --git a/tests/sshserver.pl b/tests/sshserver.pl index 1aacbb53b766..0659ccb677a5 100755 --- a/tests/sshserver.pl +++ b/tests/sshserver.pl @@ -549,6 +549,7 @@ sub logmsg { # PermitRootLogin : OpenSSH 1.2.1 and later # PermitTunnel : OpenSSH 4.3.0 and later # PermitUserEnvironment : OpenSSH 3.5.0 and later +# PerSourcePenalties : OpenSSH 9.8.0 and later # PidFile : OpenSSH 2.1.0 and later # Port : OpenSSH 1.2.1 and later # PrintLastLog : OpenSSH 2.9.0 and later @@ -641,6 +642,9 @@ sub logmsg { push @cfgarr, 'PasswordAuthentication no'; push @cfgarr, 'PermitEmptyPasswords no'; push @cfgarr, 'PermitRootLogin no'; +if(($sshdid =~ /OpenSSH/) && ($sshdvernum >= 980)) { + push @cfgarr, 'PerSourcePenalties no'; +} push @cfgarr, 'PrintLastLog no'; push @cfgarr, 'PrintMotd no'; push @cfgarr, 'PubkeyAuthentication yes'; diff --git a/tests/test-linter.pl b/tests/test-linter.pl new file mode 100755 index 000000000000..9b7705c24dff --- /dev/null +++ b/tests/test-linter.pl @@ -0,0 +1,351 @@ +#!/usr/bin/env perl +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , 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 +# +########################################################################### + +# If the build is done out-of-source-tree, specify the build root dir as an +# argument to this script. Necessary to find the generated buildprotos.h +# header file. +my $root = ".."; + +if($ARGV[0]) { + $root = $ARGV[0]; +} + +# return all known test cases +sub alltests { + my @a; + open(M, "<./data/Makefile.am") || + print STDERR "can't open data/Makefile.am\n"; + while() { + if(/^ test(\d+)/) { + push @a, $1; # just the number + } + } + close(M); + return @a; +} + +sub libtests { + my @a; + open(M, "<./libtest/Makefile.inc") || + print STDERR "can't open Makefile.inc\n"; + while() { + if(/^ *lib(\d+)\.c/) { + push @a, $1; # just the number + } + } + close(M); + return @a; +} + +sub tunittests { + my @a; + open(M, "<./tunit/Makefile.inc") || + print STDERR "can't open Makefile.inc\n"; + while() { + if(/^ *tool(\d+)\.c/) { + push @a, $1; # just the number + } + } + close(M); + return @a; +} + +sub unittests { + my @a; + open(M, "<./unit/Makefile.inc") || + print STDERR "can't open Makefile.inc\n"; + while() { + if(/^ *unit(\d+)\.c/) { + push @a, $1; # just the number + } + } + close(M); + return @a; +} + +# provided a test number, return the keywords it uses +sub keywords { + my ($num) = @_; + open(my $fh, "<./data/test$num") || + print STDERR "can't open test$num\n"; + local $/; + my $c = <$fh>; + close($fh); + + # Match and extract the block + if ($c =~ /\n(.*?)<\/keywords>/s) { + return split(/\n/, $1); + } + return undef; +} + +# provided a test number, return the it uses +sub tool { + my ($num) = @_; + open(my $fh, "<./data/test$num") || + print STDERR "can't open test$num\n"; + local $/; + my $c = <$fh>; + close($fh); + + # Match and extract the block + if ($c =~ /\n(.*?)<\/tool>/s) { + return split(/\n/, $1); + } + return undef; +} + +# return all the function names that are prototyped here, as a hash +sub unitprotos { + my ($num) = @_; + my %protos; + open(my $fh, "<$root/lib/unitprotos.h") || + print STDERR "can't open unitprotos.h\n"; + while(<$fh>) { + if(/^UNITTEST .*[* ]([a-z0-9_]+)\(/i) { + $protos{$1} = 1; + } + } + close($fh); + return %protos; +} + +# check if this unit test actually uses any internal symbols +sub checkunit { + my ($num, $protref) = @_; + open(my $fh, "<./unit/unit$num.c") || + print STDERR "can't open unit$num.c\n"; + my $private = 0; + while(<$fh>) { + # check all function calls + while(/([a-z0-9_]+)\(/cig) { + my $f = $1; + #print "Check ($num) $f\n"; + if($f =~ /^(Curl_|curlx_)/) { + $private = 1; + last; + } + elsif($$protref{$f}) { + # it uses prototyped one + $private = 1; + last; + } + } + if(/struct (Curl_|connectdata)/) { + $private = 1; + last; + } + elsif(/\bCURLPROTO_(WS|WSS|MQTTS|SOCKS)\b/) { + $private = 1; + last; + } + } + close($fh); + if(!$private) { + print "Unit test $num uses no private functions or structs\n"; + } + return !$private; +} + +sub checkxml { + my ($num) = @_; + open(my $fh, "<./data/test$num") || + print STDERR "can't open test$num\n"; + my $bad = 1; + while(<$fh>) { + if(/^<\?xml/) { + $bad = 0; + last; + } + print "data/test$num lacks tag\n"; + last; + } + close($fh); + return $bad; +} + +my $error; +my @l = libtests(); + +print "- Tests with source code in libtests/ have 'libtest' as \n"; +for my $t (@l) { + my $lt = 0; + my @k = keywords($t); + for my $w (@k) { + if($w eq "libtest") { + $lt = 1; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'libtest' keyword\n"; + $error++; + } +} + +print "- Tests with source code in tunit/ have 'tunittest' as \n"; +my @l = tunittests(); +for my $t (@l) { + my $lt = 0; + my @k = keywords($t); + for my $w (@k) { + if($w eq "tunittest") { + $lt = 1; + } + elsif($w eq "unittest") { + print "Test $t in data/test$t wrongly has the 'unittest' keyword\n"; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'tunittest' keyword\n"; + $error++; + } +} + +print "- Tests with source code in unit/ have 'unittest' as \n"; +my @l = unittests(); +for my $t (@l) { + my $lt = 0; + my @k = keywords($t); + for my $w (@k) { + if($w eq "unittest") { + $lt = 1; + } + elsif($w eq "tunittest") { + print "Test $t in data/test$t wrongly has the 'tunittest' keyword\n"; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'unittest' keyword\n"; + $error++; + } +} + +my @a = alltests(); +print "- Tests that set lib* in have 'libtest' as \n"; +for my $t (@a) { + my @k = tool($t); + for my $w (@k) { + if($w =~ /lib%TESTNUMBER/) { + print "test $t invokes default tool\n"; + $error++; + } + elsif($w =~ /lib(\d+)/) { + my $num = $1; + if($num == $t) { + print "test $t invokes default tool\n"; + $error++; + } + my $lt = 0; + + # get the keywords for test $num + my @k = keywords($t); + for my $w (@k) { + if($w eq "libtest") { + $lt = 1; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'libtest' keyword\n"; + $error++; + } + } + } +} + +print "- Tests that set tool* in have 'tunittest' as \n"; +for my $t (@a) { + my @k = tool($t); + for my $w (@k) { + if($w =~ /tool%TESTNUMBER/) { + print "test $t invokes default tool\n"; + $error++; + } + elsif($w =~ /tool(\d+)/) { + my $num = $1; + if($num == $t) { + print "test $t invokes default tool\n"; + $error++; + } + my $lt = 0; + + # get the keywords for test $num + my @k = keywords($num); + for my $w (@k) { + if($w eq "tunittest") { + $lt = 1; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'tunittest' keyword\n"; + $error++; + } + } + } +} + +print "- Tests that set unit* in have 'unittest' as \n"; +for my $t (@a) { + my @k = tool($t); + for my $w (@k) { + if($w =~ /unit%TESTNUMBER/) { + print "test $t invokes default tool\n"; + $error++; + } + elsif($w =~ /unit(\d+)/) { + my $num = $1; + if($num == $t) { + print "test $t invokes default tool\n"; + $error++; + } + my $lt = 0; + + # get the keywords for test $num + my @k = keywords($num); + for my $w (@k) { + if($w eq "unittest") { + $lt = 1; + } + } + if(!$lt) { + print "Test $t in data/test$t lacks the 'unittest' keyword\n"; + $error++; + } + } + } +} + +print "- Unit tests actually use private symbols\n"; +my @l = unittests(); +my %protos = unitprotos(); +for my $t (@l) { + $error += checkunit($t, \%protos); +} + +print "- All tests have tag on first line\n"; +for my $t (@a) { + $error += checkxml($t); +} + +exit $error; diff --git a/tests/test1119.pl b/tests/test1119.pl index a1b09d059457..ae5d30621d43 100755 --- a/tests/test1119.pl +++ b/tests/test1119.pl @@ -66,6 +66,7 @@ # included by it, which *should* be all headers sub scanenum { my ($file) = @_; + local $ENV{'MSYS2_ARG_CONV_EXCL'} = '/D'; open(my $h_in, "-|", "$Cpreprocessor $i$file") or die "Cannot preprocess $file"; while(<$h_in>) { if(/enum\s+(\S+\s+)?{/ .. /}/) { diff --git a/tests/test1167.pl b/tests/test1167.pl index a0bdb385db91..db4fa1c37318 100755 --- a/tests/test1167.pl +++ b/tests/test1167.pl @@ -71,6 +71,7 @@ sub scanenums { my ($file) = @_; my $skipit = 0; + local $ENV{'MSYS2_ARG_CONV_EXCL'} = '/D'; open(H_IN, "-|", "$Cpreprocessor -DCURL_DISABLE_DEPRECATION $i$file") or die "Cannot preprocess $file"; diff --git a/tests/test1173.pl b/tests/test1173.pl index 14894c625bd0..917bd65ff797 100755 --- a/tests/test1173.pl +++ b/tests/test1173.pl @@ -342,7 +342,7 @@ sub scanmanpage { printf STDERR "$file:%u Got %s, when %s was expected\n", $shline{$finesh}, $finesh, - $order[$shused-1]; + $order[$shused - 1]; $errors++; return; } diff --git a/tests/testcurl.pl b/tests/testcurl.pl index 0c7068a0eedc..5e9e1de5fdad 100755 --- a/tests/testcurl.pl +++ b/tests/testcurl.pl @@ -152,7 +152,7 @@ # Do the platform-specific stuff here $confheader = 'curl_config.h'; $binext = ''; -$libext = '.la'; # .la since both libcurl and libcares are made with libtool +$libext = '.la'; # .la since libcurl is made with libtool if($^O eq 'MSWin32' || $targetos) { if(!$targetos) { # If no target defined on Windows, let's assume vc @@ -354,20 +354,14 @@ sub get_host_triplet { # off that path from all possible logs and error messages etc. $pwd = getcwd(); -my $have_embedded_ares = 0; - if(-d $CURLDIR) { if($git && -d "$CURLDIR/.git") { logit "$CURLDIR is verified to be a fine git source dir"; # remove the generated sources to force them to be re-generated each # time we run this test unlink "$CURLDIR/src/tool_hugehelp.c"; - # find out if curl source directory has an in-tree c-ares repo - $have_embedded_ares = 1 if(-f "$CURLDIR/ares/GIT-INFO"); } elsif(!$git && -f "$CURLDIR/tests/testcurl.pl") { logit "$CURLDIR is verified to be a fine daily source dir"; - # find out if curl source directory has an in-tree c-ares extracted tarball - $have_embedded_ares = 1 if(-f "$CURLDIR/ares/ares_build.h"); } else { mydie "$CURLDIR is not a daily source directory or checked out from git!" } @@ -425,32 +419,6 @@ sub get_host_triplet { logit " $_"; } - if(-d "ares/.git") { - chdir "ares"; - - if($nogitpull) { - logit "skipping git pull (--nogitpull) in ares"; - } else { - logit "run git pull in ares"; - system("git pull 2>&1"); - $gitstat += $?; - logit "failed to update from ares git ($?), continue anyway" if($?); - - # Set timestamp to the UTC the git update took place. - $timestamp = scalar(gmtime)." UTC" if(!$gitstat); - } - - # get the last 5 commits for show (even if no pull was made) - @commits = qx(git log --pretty=oneline --abbrev-commit -5); - logit "The most recent ares git commits:"; - for(@commits) { - chomp ($_); - logit " $_"; - } - - chdir $CURLDIR; - } - if($nobuildconf) { logit "told to not run autoreconf -fi"; } @@ -581,73 +549,6 @@ sub findinpath { } close($f); -if(($have_embedded_ares) && - (grepfile("^#define USE_ARES", "lib/$confheader"))) { - print "\n"; - logit "setup to build ares"; - - if(-f "./ares/libcares.pc") { - logit_spaced "display ares/libcares.pc"; - if(open($f, "<", "ares/libcares.pc")) { - while(<$f>) { - my $ll = $_; - print $ll if(($ll !~ /^ *#/) && ($ll !~ /^ *$/)); - } - close($f); - } - } - - if(-f "./ares/ares_build.h") { - logit_spaced "display ares/ares_build.h"; - if(open($f, "<", "ares/ares_build.h")) { - while(<$f>) { - my $ll = $_; - print $ll if(($ll =~ /^ *# *define *CARES_/) && ($ll !~ /__CARES_BUILD_H/)); - } - close($f); - } - } - else { - mydie "no ares_build.h created/found"; - } - - $confheader =~ s/curl/ares/; - logit_spaced "display ares/$confheader"; - if(open($f, "<", "ares/$confheader")) { - while(<$f>) { - print if /^ *#/; - } - close($f); - } - - print "\n"; - logit "build ares"; - chdir "ares"; - - if($targetos && !$configurebuild) { - logit "$make -f Makefile.$targetos"; - open($f, "-|", "$make -f Makefile.$targetos 2>&1") or die; - } - else { - logit $make; - open($f, "-|", "$make 2>&1") or die; - } - while(<$f>) { - s/$pwd//g; - print; - } - close($f); - - if(-f "libcares$libext") { - logit "ares is now built successfully (libcares$libext)"; - } else { - mydie "ares build failed (libcares$libext)"; - } - - # cd back to the curl build dir - chdir "$pwd/$build"; -} - my $mkcmd = "$make -i" . ($targetos && !$configurebuild ? " $targetos" : ""); logit $mkcmd; open($f, "-|", "$mkcmd 2>&1") or die; diff --git a/tests/tunit/CMakeLists.txt b/tests/tunit/CMakeLists.txt index c6ac279a660a..2e829c9a8ca0 100644 --- a/tests/tunit/CMakeLists.txt +++ b/tests/tunit/CMakeLists.txt @@ -45,7 +45,7 @@ target_include_directories(${BUNDLE} PRIVATE "${PROJECT_BINARY_DIR}/lib" # for "curl_config.h" "${PROJECT_SOURCE_DIR}/lib" # for "curl_setup.h", curlx "${PROJECT_SOURCE_DIR}/src" # for tool headers - "${PROJECT_SOURCE_DIR}/tests/libtest" # for "first.h", "unitcheck.h" + "${PROJECT_SOURCE_DIR}/tests/libtest" # for "first.c", "first.h", "unitcheck.h" "${CMAKE_CURRENT_SOURCE_DIR}" # for the generated bundle source to find included test sources ) target_compile_definitions(${BUNDLE} PRIVATE ${CURL_DEBUG_MACROS}) diff --git a/tests/tunit/tool1604.c b/tests/tunit/tool1604.c index f16b0bfe0a27..80050b35236e 100644 --- a/tests/tunit/tool1604.c +++ b/tests/tunit/tool1604.c @@ -126,6 +126,15 @@ static CURLcode test_tool1604(const char *arg) { "com1", 0, "_com1", SANITIZE_ERR_OK }, + { "com4", 0, + "_com4", SANITIZE_ERR_OK + }, + { "com6", 0, + "_com6", SANITIZE_ERR_OK + }, + { "com9", 0, + "_com9", SANITIZE_ERR_OK + }, { "com1", SANITIZE_ALLOW_RESERVED, "com1", SANITIZE_ERR_OK }, @@ -170,6 +179,45 @@ static CURLcode test_tool1604(const char *arg) { "COM56", 0, "COM56", SANITIZE_ERR_OK }, + { "COM\xc2\xb9", 0, + "_COM\xc2\xb9", SANITIZE_ERR_OK + }, + { "COM\xc2\xb2", 0, + "_COM\xc2\xb2", SANITIZE_ERR_OK + }, + { "COM\xc2\xb3", 0, + "_COM\xc2\xb3", SANITIZE_ERR_OK + }, + { "COM\xb9", 0, + "_COM\xb9", SANITIZE_ERR_OK + }, + { "COM\xb2", 0, + "_COM\xb2", SANITIZE_ERR_OK + }, + { "COM\xb3", 0, + "_COM\xb3", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb9", 0, + "_lpt\xc2\xb9", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb2", 0, + "_lpt\xc2\xb2", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb3", 0, + "_lpt\xc2\xb3", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb9.txt", 0, + "lpt\xc2\xb9_txt", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb2.txt", 0, + "lpt\xc2\xb2_txt", SANITIZE_ERR_OK + }, + { "lpt\xc2\xb3.txt", 0, + "lpt\xc2\xb3_txt", SANITIZE_ERR_OK + }, + { "COM\xc2\xb3", SANITIZE_ALLOW_RESERVED, + "COM\xc2\xb3", SANITIZE_ERR_OK + }, { NULL, 0, NULL, SANITIZE_ERR_BAD_ARGUMENT }, diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 46710ce1eaf1..3c24940b5807 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -45,7 +45,7 @@ target_link_libraries(${BUNDLE} curlu) target_include_directories(${BUNDLE} PRIVATE "${PROJECT_BINARY_DIR}/lib" # for "curl_config.h", "unitprotos.h" "${PROJECT_SOURCE_DIR}/lib" # for "curl_setup.h", curlx - "${PROJECT_SOURCE_DIR}/tests/libtest" # for "first.h", "unitcheck.h" + "${PROJECT_SOURCE_DIR}/tests/libtest" # for "first.c", "first.h", "unitcheck.h" "${CMAKE_CURRENT_SOURCE_DIR}" # for the generated bundle source to find included test sources ) # unit tests are small pretend-libcurl-programs, pass BUILDING_LIBCURL to reflect that diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc index e0effbcb9af2..52296b056606 100644 --- a/tests/unit/Makefile.inc +++ b/tests/unit/Makefile.inc @@ -46,17 +46,14 @@ TESTS_C = \ unit1601.c \ unit1602.c \ unit1603.c \ - unit1605.c \ unit1606.c \ unit1607.c \ unit1608.c \ - unit1609.c \ unit1610.c \ unit1611.c \ unit1612.c \ unit1614.c \ unit1615.c \ - unit1616.c \ unit1620.c \ unit1625.c \ unit1626.c \ @@ -82,6 +79,7 @@ TESTS_C = \ unit1674.c \ unit1675.c \ unit1676.c \ + unit1693.c \ unit1961.c \ unit1979.c \ unit1980.c \ @@ -102,10 +100,14 @@ TESTS_C = \ unit3216.c \ unit3219.c \ unit3227.c \ + unit3230.c \ + unit3271.c \ unit3300.c \ unit3301.c \ unit3302.c \ unit3303.c \ unit3304.c \ unit3306.c \ - unit3400.c + unit3400.c \ + unit4781.c \ + unit4782.c diff --git a/tests/unit/unit1300.c b/tests/unit/unit1300.c index 7f0ec0d0c74d..bd7be156bda7 100644 --- a/tests/unit/unit1300.c +++ b/tests/unit/unit1300.c @@ -73,7 +73,7 @@ static CURLcode test_unit1300(const char *arg) "list tail should initiate to NULL"); /** - * testing Curl_llist_insert_next + * testing llist_insert_next * case 1: * list is empty * @assumptions: @@ -82,8 +82,8 @@ static CURLcode test_unit1300(const char *arg) * 3: list tail is the same as list head */ - Curl_llist_insert_next(&llist, Curl_llist_head(&llist), &unusedData_case1, - &case1_list); + llist_insert_next(&llist, Curl_llist_head(&llist), &unusedData_case1, + &case1_list); fail_unless(Curl_llist_count(&llist) == 1, "List size should be 1 after adding a new element"); @@ -95,7 +95,7 @@ static CURLcode test_unit1300(const char *arg) "tail and head should be the same"); /** - * testing Curl_llist_insert_next + * testing llist_insert_next * case 2: * list has 1 element, adding one element after the head * @assumptions: @@ -103,8 +103,8 @@ static CURLcode test_unit1300(const char *arg) * 2: the list tail should be our newly created element */ - Curl_llist_insert_next(&llist, Curl_llist_head(&llist), - &unusedData_case3, &case3_list); + llist_insert_next(&llist, Curl_llist_head(&llist), + &unusedData_case3, &case3_list); fail_unless(Curl_node_elem(Curl_node_next(Curl_llist_head(&llist))) == &unusedData_case3, "the node next to head is not getting set correctly"); @@ -112,7 +112,7 @@ static CURLcode test_unit1300(const char *arg) "the list tail is not getting set correctly"); /** - * testing Curl_llist_insert_next + * testing llist_insert_next * case 3: * list has >1 element, adding one element after "NULL" * @assumptions: @@ -120,8 +120,8 @@ static CURLcode test_unit1300(const char *arg) * 2: the list tail should different from newly created element */ - Curl_llist_insert_next(&llist, Curl_llist_head(&llist), - &unusedData_case2, &case2_list); + llist_insert_next(&llist, Curl_llist_head(&llist), + &unusedData_case2, &case2_list); fail_unless(Curl_node_elem(Curl_node_next(Curl_llist_head(&llist))) == &unusedData_case2, "the node next to head is not getting set correctly"); @@ -165,8 +165,8 @@ static CURLcode test_unit1300(const char *arg) * 2: element->previous->next is element->next * 3: element->next->previous is element->previous */ - Curl_llist_insert_next(&llist, Curl_llist_head(&llist), &unusedData_case3, - &case4_list); + llist_insert_next(&llist, Curl_llist_head(&llist), &unusedData_case3, + &case4_list); llist_size = Curl_llist_count(&llist); fail_unless(llist_size == 3, "should be 3 list members"); diff --git a/tests/unit/unit1302.c b/tests/unit/unit1302.c index e840486511fc..8a0902ea6a4f 100644 --- a/tests/unit/unit1302.c +++ b/tests/unit/unit1302.c @@ -162,6 +162,30 @@ static CURLcode test_unit1302(const char *arg) curlx_safefree(decoded); } + { + unsigned char binary[256]; + size_t length; + for(i = 0; i < sizeof(binary); i++) + binary[i] = (unsigned char)i; + for(length = 1; length <= sizeof(binary); length++) { + char *encoded; + unsigned char *decoded; + size_t enclen; + size_t declen; + result = curlx_base64_encode(binary, length, &encoded, &enclen); + abort_unless(result == CURLE_OK, "binary encoding failed"); + fail_unless(enclen == (length + 2) / 3 * 4, "wrong encoded size"); + fail_unless(!encoded[enclen], "encoded data is not terminated"); + result = curlx_base64_decode(encoded, &decoded, &declen); + curlx_free(encoded); + abort_unless(result == CURLE_OK, "binary decoding failed"); + fail_unless(declen == length, "wrong decoded size"); + fail_unless(!memcmp(decoded, binary, length), "wrong decoded data"); + fail_unless(!decoded[declen], "decoded data is not terminated"); + curlx_free(decoded); + } + } + for(i = 0; i < CURL_ARRAYSIZE(url); i++) { const struct etest *e = &url[i]; char *out; diff --git a/tests/unit/unit1304.c b/tests/unit/unit1304.c index 0c788818a5cf..6f9526b491b7 100644 --- a/tests/unit/unit1304.c +++ b/tests/unit/unit1304.c @@ -56,6 +56,61 @@ static bool t1304_no_passwd(struct Curl_creds *creds) return !creds || !creds->passwd[0]; } +static void t1304_empty_literals(struct Curl_easy *data, const char *filename) +{ + static const struct { + const char *content; + NETRCcode result; + const char *login; + const char *passwd; + } tests[] = { + { "default login \"\"", NETRC_OK, "", "" }, + { "default password \"\"", NETRC_OK, "", "" }, + { "default login \"\" password secret", NETRC_OK, "", "secret" }, + { "default password \"\" login admin", NETRC_OK, "admin", "" }, + { "default login \"\" password \"\"", NETRC_OK, "", "" }, + { "machine \"\" login ignored password ignored", + NETRC_NO_MATCH, NULL, NULL }, + { "machine \"\" login ignored password ignored\n" + "machine example.com login admin password passwd", + NETRC_OK, "admin", "passwd" }, + { "machine example.com login \"\" password \"\"", NETRC_OK, "", "" } + }; + size_t i; + + for(i = 0; i < CURL_ARRAYSIZE(tests); ++i) { + struct store_netrc store; + struct Curl_creds *creds = NULL; + CURLcode result; + NETRCcode res; + + /* Each cached file starts a fresh lexer, so an empty first literal + * cannot rely on an allocation from a previous nonempty literal. */ + Curl_netrc_init(&store); + result = curlx_dyn_add(&store.filebuf, tests[i].content); + fail_unless(result == CURLE_OK, "could not store netrc content"); + store.filename = curlx_strdup(filename); + fail_unless(store.filename, "could not store netrc filename"); + if(!result && store.filename) { + store.loaded = TRUE; + res = Curl_netrc_scan(data, &store, "example.com", NULL, + filename, &creds); + fail_unless(res == tests[i].result, "unexpected netrc result"); + if(tests[i].result == NETRC_OK) { + fail_unless(creds, "credentials should have been found"); + fail_unless(!strcmp(Curl_creds_user(creds), tests[i].login), + "unexpected login"); + fail_unless(!strcmp(Curl_creds_passwd(creds), tests[i].passwd), + "unexpected password"); + } + else + fail_unless(!creds, "credentials should not have been found"); + } + Curl_creds_unlink(&creds); + Curl_netrc_cleanup(&store); + } +} + static CURLcode test_unit1304(const char *arg) { struct Curl_creds *cr_out = NULL; @@ -152,6 +207,8 @@ static CURLcode test_unit1304(const char *arg) Curl_creds_unlink(&cr_out); + t1304_empty_literals(data, arg); + UNITTEST_END(t1304_stop(data)) } diff --git a/tests/unit/unit1307.c b/tests/unit/unit1307.c index c45dc81c770e..afd479c7deec 100644 --- a/tests/unit/unit1307.c +++ b/tests/unit/unit1307.c @@ -43,8 +43,8 @@ #define MAC_DIFFER 0x40 #define MAC_SHIFT 16 -#if 0 #define MAC_MATCH ((CURL_FNMATCH_MATCH << MAC_SHIFT) | MAC_DIFFER) +#if 0 #define MAC_NOMATCH ((CURL_FNMATCH_NOMATCH << MAC_SHIFT) | MAC_DIFFER) #endif #define MAC_FAIL ((CURL_FNMATCH_FAIL << MAC_SHIFT) | MAC_DIFFER) @@ -219,6 +219,9 @@ static CURLcode test_unit1307(const char *arg) { "***.txt", "t.txt", MATCH }, { "****.txt", ".txt", MATCH }, + /* custom matcher limits wildcard star groups */ + { "*a*b*c", "abc", NOMATCH|LINUX_MATCH|MAC_MATCH }, + /* empty string or pattern */ { "", "", MATCH }, { "", "hello", NOMATCH }, @@ -259,7 +262,7 @@ static CURLcode test_unit1307(const char *arg) { "?*?*?.?", "abcdef.c", MATCH }, { "?*?*?.?", "abcdef.cd", NOMATCH }, - /* https://codepoints.net/U+00E4 Latin Small Letter A with Diaeresis */ + /* U+00E4 Latin Small Letter A with Diaeresis */ { "Lindm\xc3\xa4tarv", "Lindm\xc3\xa4tarv", MATCH }, { "", "", MATCH}, diff --git a/tests/unit/unit1309.c b/tests/unit/unit1309.c index 388c7d1415f3..95034d6adf22 100644 --- a/tests/unit/unit1309.c +++ b/tests/unit/unit1309.c @@ -54,6 +54,49 @@ static void splayprint(struct Curl_tree *t, int d, char output) splayprint(t->smaller, d + 1, output); } +static void test_timeouts_nonzero_usec_base(void) +{ + struct Curl_timeouts timeouts; + struct Curl_tree node; + struct curltime base; + struct curltime deadline; + struct curltime now; + timediff_t original_deadline_offset; + timediff_t original_now_offset; + timediff_t deadline_offset; + timediff_t now_offset; + timediff_t expire_offset; + uint32_t mid; + int timeout_ms; + + base.tv_sec = 10; + base.tv_usec = 750000; + deadline.tv_sec = 12; + deadline.tv_usec = 250000; + now.tv_sec = 11; + now.tv_usec = 100000; + + original_deadline_offset = curlx_ptimediff_us(&deadline, &base); + original_now_offset = curlx_ptimediff_us(&now, &base); + + Curl_timeouts_init(&timeouts, &base); + deadline_offset = Curl_timeouts_offset_us(&timeouts, &deadline); + now_offset = Curl_timeouts_offset_us(&timeouts, &now); + + fail_unless(deadline_offset - original_deadline_offset == base.tv_usec, + "deadline offset did not shift by the base microseconds"); + fail_unless(now_offset - original_now_offset == base.tv_usec, + "now offset did not shift by the base microseconds"); + + memset(&node, 0, sizeof(node)); + timeouts.tree = splayinsert(deadline_offset, timeouts.tree, &node, 42); + timeout_ms = Curl_timeouts_next_ms(&timeouts, &now, &expire_offset, &mid); + + fail_unless(timeout_ms == 1150, "timeout changed with whole-second base"); + fail_unless(expire_offset == deadline_offset, "wrong expiry offset"); + fail_unless(mid == 42, "wrong timeout id"); +} + static CURLcode test_unit1309(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -73,7 +116,7 @@ static CURLcode test_unit1309(const char *arg) timediff_t key; key = (541 * i) % 1023; - root = Curl_splayinsert(key, root, &nodes[i], (uint32_t)key); + root = splayinsert(key, root, &nodes[i], (uint32_t)key); fail_unless(nodes[i].registered, "node should have been registered"); } @@ -86,14 +129,14 @@ static CURLcode test_unit1309(const char *arg) splayprint(root, 0, 1); curl_mprintf("remove node %d, payload %u\n", (int)rem, Curl_splayget(&nodes[rem])); - rc = Curl_splayremove(root, &nodes[rem], &root); + rc = splayremove(root, &nodes[rem], &root); if(rc) { /* failed! */ curl_mprintf("remove %d failed!\n", (int)rem); fail("remove"); } fail_unless(!nodes[rem].registered, "node should not be registered"); - rc = Curl_splayremove(root, &nodes[rem], &root); + rc = splayremove(root, &nodes[rem], &root); if(!rc) { /* failed! */ curl_mprintf("double remove %d did not fail!\n", (int)rem); @@ -111,8 +154,8 @@ static CURLcode test_unit1309(const char *arg) /* add some nodes with the same key */ for(j = 0; j <= i % 3; j++) { - root = Curl_splayinsert(key, root, &nodes[(i * 3) + j], - (uint32_t)(key * 10 + j)); + root = splayinsert(key, root, &nodes[(i * 3) + j], + (uint32_t)(key * 10 + j)); } } @@ -120,12 +163,12 @@ static CURLcode test_unit1309(const char *arg) for(i = 0; i <= 1100; i += 100) { curl_mprintf("Removing nodes not larger than %d\n", (int)i); tv_now = i; - root = Curl_splaygetbest(tv_now, root, &removed); + root = splaygetbest(tv_now, root, &removed); while(removed) { curl_mprintf("removed payload %u[%u]\n", Curl_splayget(removed) / 10, Curl_splayget(removed) % 10); - root = Curl_splaygetbest(tv_now, root, &removed); + root = splaygetbest(tv_now, root, &removed); } } @@ -134,14 +177,14 @@ static CURLcode test_unit1309(const char *arg) /* rebuild tree with duplicate values */ for(i = 0; i < NUM_NODES; i++) { timediff_t key = (541 * i) % 128; - root = Curl_splayinsert(key, root, &nodes[i], (uint32_t)i); + root = splayinsert(key, root, &nodes[i], (uint32_t)i); } removed = NULL; timeout_last = -1; for(i = 0; i <= 128; i += 32) { curl_mprintf("Removing nodes not larger than %d\n", (int)i); - root = Curl_splaygetbest(i, root, &removed); + root = splaygetbest(i, root, &removed); while(removed) { curl_mprintf("removed payload %u[timeout=%d]\n", Curl_splayget(removed), (int)removed->key); @@ -152,11 +195,13 @@ static CURLcode test_unit1309(const char *arg) fail("wrong timeout order"); } timeout_last = removed->key; - root = Curl_splaygetbest(i, root, &removed); + root = splaygetbest(i, root, &removed); } } fail_unless(!root, "tree not empty when it should be"); + test_timeouts_nonzero_usec_base(); + UNITTEST_END_SIMPLE } diff --git a/tests/unit/unit1397.c b/tests/unit/unit1397.c index b133f2cc995b..18a1a4468090 100644 --- a/tests/unit/unit1397.c +++ b/tests/unit/unit1397.c @@ -22,6 +22,7 @@ * ***************************************************************************/ #include "unitcheck.h" +#include "curlx/strdup.h" #include "vtls/hostcheck.h" static CURLcode test_unit1397(const char *arg) @@ -45,6 +46,7 @@ static CURLcode test_unit1397(const char *arg) { "", "b", FALSE }, { "a", "b", FALSE }, { "aa", "bb", FALSE }, + { "a", "*", FALSE }, { "\xff", "\xff", TRUE }, { "aa.aa.aa", "aa.aa.bb", FALSE }, { "aa.aa.aa", "aa.aa.aa", TRUE }, @@ -94,6 +96,8 @@ static CURLcode test_unit1397(const char *arg) int i; for(i = 0; tests[i].host; i++) { + size_t pattern_len; + char *pattern; if(tests[i].match != Curl_cert_hostcheck(tests[i].pattern, strlen(tests[i].pattern), tests[i].host, @@ -107,6 +111,38 @@ static CURLcode test_unit1397(const char *arg) tests[i].match ? "NOT " : ""); unitfail++; } + + /* Curl_cert_hostcheck() should not rely on null-terminators in the + * pattern. The underlying TLS library might not make a null-terminated + * copy of the pattern. Copy it to a new buffer so ASan and valgrind can + * flag invalid accesses. */ + pattern_len = strlen(tests[i].pattern); + pattern = pattern_len ? curlx_memdup(tests[i].pattern, pattern_len) : NULL; + abort_unless(pattern_len == 0 || pattern, "Out of memory"); + if(tests[i].match != Curl_cert_hostcheck(pattern, + pattern_len, + tests[i].host, + strlen(tests[i].host))) { + curl_mfprintf(stderr, + "HOST: %s\n" + "PTRN: %s\n" + "did %sMATCH\n", + tests[i].host, + tests[i].pattern, + tests[i].match ? "NOT " : ""); + unitfail++; + } + curlx_free(pattern); + } + + /* Embedded NUL bytes in the pattern should not be misinterpreted. */ + if(Curl_cert_hostcheck("foo\0bar", 7, "foo", 3)) { + curl_mfprintf(stderr, "foo\\0bar incorrectly matched foo\n"); + unitfail++; + } + if(Curl_cert_hostcheck("foo\0bar", 7, "foo.bar", 7)) { + curl_mfprintf(stderr, "foo\\0bar incorrectly matched foo.bar\n"); + unitfail++; } #endif diff --git a/tests/unit/unit1399.c b/tests/unit/unit1399.c index a2fb21acec69..159ce17b8cca 100644 --- a/tests/unit/unit1399.c +++ b/tests/unit/unit1399.c @@ -44,86 +44,85 @@ static void t1399_stop(struct Curl_easy *easy) curl_global_cleanup(); } -/* - * Invoke Curl_pgrsTime for TIMER_STARTSINGLE to trigger the behavior that - * manages startransfer, but fake the startsingle_us time for purposes - * of the test. - */ -static void fake_t_startsingle_time(struct Curl_easy *data, - int seconds_offset) +static bool usec_matches(timediff_t time_usec, timediff_t expected_usec) { - Curl_pgrsTime(data, TIMER_STARTSINGLE); - data->progress.delta.startsingle_us = (seconds_offset * 1000 * 1000); -} - -static bool usec_matches_seconds(timediff_t time_usec, int expected_seconds) -{ - static int usec_magnitude = 1000000; - - int time_sec = (int)(time_usec / usec_magnitude); - bool same = (time_sec == expected_seconds); - curl_mfprintf(stderr, "is %d us same as %d seconds? %s\n", - (int)time_usec, expected_seconds, - same ? "Yes" : "No"); + bool same = (time_usec == expected_usec); + curl_mfprintf(stderr, "is %" FMT_TIMEDIFF_T + " us same as %" FMT_TIMEDIFF_T " us? %s\n", + time_usec, expected_usec, same ? "Yes" : "No"); return same; } -static void expect_timer_seconds(struct Curl_easy *data, int seconds) +static void expect_timer_microseconds(struct Curl_easy *data, + timediff_t microseconds) { struct Progress *p = &data->progress; char msg[64]; - curl_msnprintf(msg, sizeof(msg), "about %d seconds should have passed", - seconds); - fail_unless(usec_matches_seconds(p->total.nslookup_us, seconds), msg); - fail_unless(usec_matches_seconds(p->total.connect_us, seconds), msg); - fail_unless(usec_matches_seconds(p->total.appconnect_us, seconds), msg); - fail_unless(usec_matches_seconds(p->total.pretransfer_us, seconds), msg); - fail_unless(usec_matches_seconds(p->total.starttransfer_us, seconds), msg); + curl_msnprintf(msg, sizeof(msg), + "%" FMT_TIMEDIFF_T " microseconds should have passed", + microseconds); + fail_unless(usec_matches(p->total.nslookup_us, microseconds), msg); + fail_unless(usec_matches(p->total.connect_us, microseconds), msg); + fail_unless(usec_matches(p->total.appconnect_us, microseconds), msg); + fail_unless(usec_matches(p->total.pretransfer_us, microseconds), msg); + fail_unless(usec_matches(p->total.starttransfer_us, microseconds), msg); } /* Scenario: simulate a redirect. When a redirect occurs, t_nslookup, * t_connect, t_appconnect, t_pretransfer, and t_starttransfer are additive. - * E.g., if t_starttransfer took 2 seconds initially and took another 1 - * second for the redirect request, then the resulting t_starttransfer should - * be 3 seconds. */ + * E.g., if t_starttransfer took 2.25 seconds initially and took another 1.5 + * seconds for the redirect request, then the resulting t_starttransfer should + * be 3.75 seconds. */ static CURLcode test_unit1399(const char *arg) { struct Curl_easy *data; - struct curltime now = curlx_now(); + struct curltime now; UNITTEST_BEGIN(t1399_setup(&data)) data->multi = NULL; + now.tv_sec = 12345678; + now.tv_usec = 0; data->progress.now = now; - memset(&data->progress.delta, 0, sizeof(data->progress.delta)); - memset(&data->progress.total, 0, sizeof(data->progress.total)); - - data->progress.start.tv_sec = now.tv_sec - 2; - data->progress.start.tv_usec = now.tv_usec; - fake_t_startsingle_time(data, 0); - - Curl_pgrsTime(data, TIMER_NAMELOOKUP); - Curl_pgrsTime(data, TIMER_CONNECT); - Curl_pgrsTime(data, TIMER_APPCONNECT); - Curl_pgrsTime(data, TIMER_PRETRANSFER); - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - - expect_timer_seconds(data, 2); - - /* now simulate the redirect */ - data->progress.delta.startredirect_us = 1 * 1000 * 1000; - fake_t_startsingle_time(data, 1); - - Curl_pgrsTime(data, TIMER_NAMELOOKUP); - Curl_pgrsTime(data, TIMER_CONNECT); - Curl_pgrsTime(data, TIMER_APPCONNECT); - Curl_pgrsTime(data, TIMER_PRETRANSFER); + Curl_pgrsStart(data, &now); + + /* Record the first request start */ + Curl_pgrsTimeWas(data, TIMER_STARTSINGLE, now); + /* Let 2.25 seconds pass */ + now.tv_sec += 2; + now.tv_usec = 250000; + Curl_pgrsTimeWas(data, TIMER_NAMELOOKUP, now); + Curl_pgrsTimeWas(data, TIMER_CONNECT, now); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, now); + Curl_pgrsTimeWas(data, TIMER_PRETRANSFER, now); + Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); + + expect_timer_microseconds(data, 2250000); + + /* now simulate the redirect after one second + * and subsequent follow request start */ + now.tv_sec += 1; + Curl_pgrsTimeWas(data, TIMER_REDIRECT, now); + Curl_pgrsTimeWas(data, TIMER_STARTSINGLE, now); + + /* Let 1.5 seconds pass */ + now.tv_sec += 1; + now.tv_usec = 750000; + Curl_pgrsTimeWas(data, TIMER_NAMELOOKUP, now); + Curl_pgrsTimeWas(data, TIMER_CONNECT, now); + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, now); + Curl_pgrsTimeWas(data, TIMER_PRETRANSFER, now); + Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); /* ensure t_starttransfer is only set on the first invocation by attempting * to set it twice */ - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - - expect_timer_seconds(data, 3); + now.tv_sec += 1; + Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); + Curl_pgrsTimeWas(data, TIMER_STARTTRANSFER, now); + + /* Accumulated times are now 3.75 seconds: + * - 2.25 after 1st request start + * - 1.5 after 2nd request start */ + expect_timer_microseconds(data, 3750000); UNITTEST_END(t1399_stop(data)) } diff --git a/tests/unit/unit1602.c b/tests/unit/unit1602.c index cca3667a0dbe..cc1ccf9bbb7e 100644 --- a/tests/unit/unit1602.c +++ b/tests/unit/unit1602.c @@ -32,8 +32,7 @@ static void t1602_mydtor(void *p) static CURLcode t1602_setup(struct Curl_hash *hash) { - Curl_hash_init(hash, 7, Curl_hash_str, - curlx_str_key_compare, t1602_mydtor); + Curl_hash_init(hash, 7, CURL_HASH_TYPE_BYTES, t1602_mydtor); return CURLE_OK; } diff --git a/tests/unit/unit1603.c b/tests/unit/unit1603.c index 6ba89999c02a..121654f001ea 100644 --- a/tests/unit/unit1603.c +++ b/tests/unit/unit1603.c @@ -34,7 +34,7 @@ static void t1603_mydtor(void *p) static size_t elem_dtor_calls; -static void my_elem_dtor(void *key, size_t key_len, void *p) +static void my_elem_dtor(const void *key, size_t key_len, void *p) { (void)p; (void)key; @@ -44,8 +44,7 @@ static void my_elem_dtor(void *key, size_t key_len, void *p) static CURLcode t1603_setup(struct Curl_hash *hash_static) { - Curl_hash_init(hash_static, slots, Curl_hash_str, - curlx_str_key_compare, t1603_mydtor); + Curl_hash_init(hash_static, slots, CURL_HASH_TYPE_BYTES, t1603_mydtor); return CURLE_OK; } @@ -54,6 +53,61 @@ static void t1603_stop(struct Curl_hash *hash_static) Curl_hash_destroy(hash_static); } +static void t1603_test_socket_type(void) +{ + struct Curl_hash hash; + curl_socket_t key = 1; + curl_socket_t collision = (curl_socket_t)(slots + 1); + int value = 1; + int collision_value = 2; + int replacement_value = 3; +#if SIZEOF_CURL_SOCKET_T > 4 + curl_socket_t high_key = ((curl_socket_t)1 << 32) + 1; + int high_value = 4; +#endif + + Curl_hash_init(&hash, slots, CURL_HASH_TYPE_SOCKET, t1603_mydtor); + fail_unless(!Curl_hash_add(&hash, &key, sizeof(key) - 1, &value), + "invalid socket key length was accepted"); + fail_unless(!hash.table, "invalid socket key allocated a hash table"); + fail_unless(!Curl_hash_pick(&hash, &key, sizeof(key) - 1), + "invalid socket key lookup succeeded"); + fail_unless(Curl_hash_delete(&hash, &key, sizeof(key) - 1), + "invalid socket key deletion succeeded"); + fail_unless(Curl_hash_add(&hash, &key, sizeof(key), &value) == &value, + "socket key insertion failed"); + fail_unless(hash.table && hash.table[1], + "socket key used the wrong hash slot"); + fail_unless(Curl_hash_add(&hash, &key, sizeof(key), &replacement_value) == + &replacement_value, "socket key replacement failed"); + fail_unless(Curl_hash_count(&hash) == 1, + "socket key replacement changed the hash count"); + fail_unless(Curl_hash_pick(&hash, &key, sizeof(key)) == &replacement_value, + "socket key replacement lookup failed"); + fail_unless(Curl_hash_add(&hash, &collision, sizeof(collision), + &collision_value) == &collision_value, + "colliding socket key insertion failed"); + fail_unless(Curl_hash_count(&hash) == 2, "wrong socket hash count"); + fail_unless(Curl_hash_pick(&hash, &collision, sizeof(collision)) == + &collision_value, "colliding socket key lookup failed"); +#if SIZEOF_CURL_SOCKET_T > 4 + fail_unless(Curl_hash_add(&hash, &high_key, sizeof(high_key), &high_value) == + &high_value, "wide socket key insertion failed"); + fail_unless(hash.table && hash.table[2], + "wide socket key used the wrong hash slot"); + fail_unless(Curl_hash_count(&hash) == 3, "wrong wide socket hash count"); + fail_unless(Curl_hash_pick(&hash, &high_key, sizeof(high_key)) == + &high_value, "wide socket key lookup failed"); +#endif + fail_unless(!Curl_hash_delete(&hash, &key, sizeof(key)), + "socket key deletion failed"); + fail_unless(!Curl_hash_pick(&hash, &key, sizeof(key)), + "deleted socket key was found"); + fail_unless(Curl_hash_pick(&hash, &collision, sizeof(collision)) == + &collision_value, "collision was lost after deletion"); + Curl_hash_destroy(&hash); +} + static CURLcode test_unit1603(const char *arg) { struct Curl_hash hash_static; @@ -68,13 +122,16 @@ static CURLcode test_unit1603(const char *arg) const char *nodep; int rc; + fail_unless(hash_str_seeded("a", 1, 97, 0x1234) == 95, + "seeded hash is not computed as expected"); + /* Ensure the key hashes are as expected in order to test both hash collisions and a full table. Unfortunately, the hashes can vary between architectures. */ - if(Curl_hash_str(key1, strlen(key1), slots) != 1 || - Curl_hash_str(key2, strlen(key2), slots) != 0 || - Curl_hash_str(key3, strlen(key3), slots) != 2 || - Curl_hash_str(key4, strlen(key4), slots) != 1) + if(hash_str(key1, strlen(key1), slots) != 1 || + hash_str(key2, strlen(key2), slots) != 0 || + hash_str(key3, strlen(key3), slots) != 2 || + hash_str(key4, strlen(key4), slots) != 1) curl_mfprintf(stderr, "Warning: hashes are not computed as expected on this " "architecture; test coverage is less comprehensive\n"); @@ -174,5 +231,7 @@ static CURLcode test_unit1603(const char *arg) /* Clean up */ Curl_hash_clean(&hash_static); + t1603_test_socket_type(); + UNITTEST_END(t1603_stop(&hash_static)) } diff --git a/tests/unit/unit1607.c b/tests/unit/unit1607.c index 72df3808ca67..1d1fedcb1c7d 100644 --- a/tests/unit/unit1607.c +++ b/tests/unit/unit1607.c @@ -23,6 +23,7 @@ ***************************************************************************/ #include "unitcheck.h" #include "urldata.h" +#include "strcase.h" #include "curl_addrinfo.h" static CURLcode t1607_setup(void) @@ -32,6 +33,27 @@ static CURLcode t1607_setup(void) return result; } +struct t1607_key { + uint8_t data[255 + 3]; + size_t len; +}; + +static void t1607_create_key(struct t1607_key *key, + const char *hostname, + uint16_t port, + uint8_t type) +{ + size_t namelen = strlen(hostname); + if(namelen > (sizeof(key->data) - 3)) + namelen = sizeof(key->data) - 3; + /* store and lower case the name */ + key->data[0] = type; + key->data[1] = (uint8_t)((port >> 8) & 0xff); + key->data[2] = (uint8_t)(port & 0xff); + Curl_strntolower((char *)key->data + 3, hostname, namelen); + key->len = namelen + 3; +} + static CURLcode test_unit1607(const char *arg) { /* In builds without IPv6 support CURLOPT_RESOLVE should skip over those @@ -108,7 +130,7 @@ static CURLcode test_unit1607(const char *arg) size_t addressnum = CURL_ARRAYSIZE(tests[i].address); struct Curl_addrinfo *addr; struct Curl_dns_entry *dns; - void *entry_id; + struct t1607_key entry_id; bool problem = FALSE; easy = curl_easy_init(); if(!easy) @@ -127,13 +149,9 @@ static CURLcode test_unit1607(const char *arg) Curl_loadhostpairs(easy); - entry_id = (void *)curl_maprintf("%c%s:%u", CURL_DNST_ADDR, - tests[i].host, tests[i].port); - if(!entry_id) - goto error; + t1607_create_key(&entry_id, tests[i].host, tests[i].port, CURL_DNST_ADDR); dns = Curl_hash_pick(&multi->dnscache.entries, - entry_id, strlen(entry_id) + 1); - curlx_safefree(entry_id); + entry_id.data, entry_id.len); addr = dns ? dns->addr : NULL; @@ -190,18 +208,18 @@ static CURLcode test_unit1607(const char *arg) break; } - if(dns->timestamp.tv_sec && tests[i].permanent) { + if(!dns->permanent && tests[i].permanent) { curl_mfprintf(stderr, - "%s:%d tests[%zu] failed. the timestamp is not zero " + "%s:%d tests[%zu] failed. the permanent bit is not set " "but tests[%zu].permanent is TRUE\n", __FILE__, __LINE__, i, i); problem = TRUE; break; } - if(dns->timestamp.tv_sec == 0 && !tests[i].permanent) { - curl_mfprintf(stderr, "%s:%d tests[%zu] failed. the timestamp is zero " - "but tests[%zu].permanent is FALSE\n", + if(dns->permanent && !tests[i].permanent) { + curl_mfprintf(stderr, "%s:%d tests[%zu] failed. the permanent bit " + "is set but tests[%zu].permanent is FALSE\n", __FILE__, __LINE__, i, i); problem = TRUE; break; diff --git a/tests/unit/unit1616.c b/tests/unit/unit1616.c deleted file mode 100644 index cd7d0cca54ea..000000000000 --- a/tests/unit/unit1616.c +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) Daniel Stenberg, , 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 "unitcheck.h" -#include "uint-hash.h" - -static void t1616_mydtor(uint32_t id, void *elem) -{ - int *ptr = (int *)elem; - (void)id; - curlx_free(ptr); -} - -static CURLcode t1616_setup(struct uint_hash *hash) -{ - Curl_uint32_hash_init(hash, 15, t1616_mydtor); - return CURLE_OK; -} - -static void t1616_stop(struct uint_hash *hash) -{ - Curl_uint32_hash_destroy(hash); -} - -static CURLcode test_unit1616(const char *arg) -{ - struct uint_hash hash; - - UNITTEST_BEGIN(t1616_setup(&hash)) - - int *value, *v; - int *value2; - bool ok; - - uint32_t key = 20; - uint32_t key2 = 25; - - value = curlx_malloc(sizeof(int)); - abort_unless(value, "Out of memory"); - *value = 199; - ok = Curl_uint32_hash_set(&hash, key, value); - if(!ok) - curlx_free(value); - abort_unless(ok, "insertion into hash failed"); - v = Curl_uint32_hash_get(&hash, key); - abort_unless(v == value, "lookup present entry failed"); - v = Curl_uint32_hash_get(&hash, key2); - abort_unless(!v, "lookup missing entry failed"); - uint_hash_clear(&hash); - - /* Attempt to add another key/value pair */ - value2 = curlx_malloc(sizeof(int)); - abort_unless(value2, "Out of memory"); - *value2 = 204; - ok = Curl_uint32_hash_set(&hash, key2, value2); - if(!ok) - curlx_free(value2); - abort_unless(ok, "insertion into hash failed"); - v = Curl_uint32_hash_get(&hash, key2); - abort_unless(v == value2, "lookup present entry failed"); - v = Curl_uint32_hash_get(&hash, key); - abort_unless(!v, "lookup missing entry failed"); - - UNITTEST_END(t1616_stop(&hash)) -} diff --git a/tests/unit/unit1627.c b/tests/unit/unit1627.c index cbdd3383086d..6f96c0a9003e 100644 --- a/tests/unit/unit1627.c +++ b/tests/unit/unit1627.c @@ -38,18 +38,15 @@ static CURLcode test_unit1627(const char *arg) /* all upper */ "DICT", "FILE", "FTP", "FTPS", "GOPHER", "GOPHERS", "HTTP", "HTTPS", "IMAP", "IMAPS", "LDAP", "LDAPS", "MQTT", "MQTTS", "POP3", "POP3S", - "RTSP", "SCP", "SFTP", "SMB", "SMBS", "SMTP", "SMTPS", - "TELNET", "TFTP", "WS", "WSS", + "RTSP", "SCP", "SFTP", "SMTP", "SMTPS", "TELNET", "TFTP", "WS", "WSS", /* all lower */ "dict", "file", "ftp", "ftps", "gopher", "gophers", "http", "https", "imap", "imaps", "ldap", "ldaps", "mqtt", "mqtts", "pop3", "pop3s", - "rtsp", "scp", "sftp", "smb", "smbs", "smtp", "smtps", - "telnet", "tftp", "ws", "wss", + "rtsp", "scp", "sftp", "smtp", "smtps", "telnet", "tftp", "ws", "wss", /* mixed */ "diCt", "fIle", "Ftp", "ftpS", "Gopher", "gOphers", "htTp", "httPs", "imAP", "imaPS", "LDap", "LDAps", "mQTT", "mqtTS", "pOP3", "pOP3s", - "RtsP", "ScP", "SFtP", "Smb", "smBS", "sMTP", "SMTPs", - "TELNEt", "tFTP", "Ws", "wSS", + "RtsP", "ScP", "SFtP", "sMTP", "SMTPs", "TELNEt", "tFTP", "Ws", "wSS", "SOCKS", "SOCKS4", "SOCKS5", "SOCKS4A", "SOCKS5H", "socks", "socks4", "socks5", "socks4a", "socks5h", @@ -128,11 +125,9 @@ static CURLcode test_unit1627(const char *arg) for(i = 0; i < CURL_ARRAYSIZE(okay); i++) { char buffer[32]; const struct Curl_scheme *get = Curl_get_scheme(okay[i]); - if(get) { - /* verify that we got the correct scheme */ - if(!curl_strequal(get->name, okay[i])) - get = NULL; - } + /* verify that we got the correct scheme */ + if(get && !curl_strequal(get->name, okay[i])) + get = NULL; if(!get) { curl_mprintf("Input: %s, expected okay\n", okay[i]); break; diff --git a/tests/unit/unit1650.c b/tests/unit/unit1650.c index 890027b19353..2a9726959c12 100644 --- a/tests/unit/unit1650.c +++ b/tests/unit/unit1650.c @@ -229,6 +229,36 @@ static CURLcode test_unit1650(const char *arg) } } +#ifdef USE_HTTPSRR + { + unsigned char httpsrr[] = { + 0x00, 0x00, 0x81, 0x80, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00 + }; + + for(i = 0; i <= 3; i++) { + struct dohentry d; + DOHcode rc; + DOHcode expected = i < 3 ? DOH_DNS_RDATA_LEN : DOH_OK; + httpsrr[22] = (unsigned char)i; + de_init(&d); + rc = doh_resp_decode(httpsrr, sizeof(httpsrr) - 3 + i, + CURL_DNS_TYPE_HTTPS, &d); + if(rc != expected) { + curl_mfprintf(stderr, + "HTTPS RR length %zu: Expected return code %d got %d\n", + i, (int)expected, (int)rc); + abort_if(rc != expected, "return code"); + } + fail_unless(d.numhttps_rrs == (i == 3 ? 1 : 0), + "unexpected number of HTTPS RRs"); + de_cleanup(&d); + } + } +#endif + /* pass all sizes into the decoder until full */ for(i = 0; i < CURL_CSTRLEN(full49); i++) { struct dohentry d; diff --git a/tests/unit/unit1651.c b/tests/unit/unit1651.c index fc84b3365768..a91b238deb96 100644 --- a/tests/unit/unit1651.c +++ b/tests/unit/unit1651.c @@ -32,7 +32,7 @@ static CURLcode test_unit1651(const char *arg) /* cert captured from gdb when connecting to curl.se on October 26 2018. */ - static unsigned char cert[] = { + static uint8_t cert[] = { 0x30, 0x82, 0x0F, 0x5B, 0x30, 0x82, 0x0E, 0x43, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x0C, 0x08, 0x77, 0x99, 0x2C, 0x6B, 0x67, 0xE1, 0x18, 0xD6, 0x66, 0x66, 0x9E, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, @@ -364,8 +364,8 @@ static CURLcode test_unit1651(const char *arg) }; CURLcode result; - const char *beg = (const char *)&cert[0]; - const char *end = (const char *)&cert[sizeof(cert)]; + const uint8_t *beg = &cert[0]; + const uint8_t *end = &cert[sizeof(cert)]; struct Curl_easy *data; int i; int byte; diff --git a/tests/unit/unit1657.c b/tests/unit/unit1657.c index 79f026bebad6..0413e3317346 100644 --- a/tests/unit/unit1657.c +++ b/tests/unit/unit1657.c @@ -23,7 +23,8 @@ ***************************************************************************/ #include "unitcheck.h" -#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_MBEDTLS) +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || \ + defined(USE_MBEDTLS) || defined(USE_RUSTLS) #include "vtls/x509asn1.h" struct test1657_spec { @@ -37,8 +38,8 @@ static CURLcode make1657_nested(const struct test1657_spec *spec, { CURLcode result; size_t i; - unsigned char open_undef[] = { 0x32, 0x80 }; - unsigned char close_undef[] = { 0x00, 0x00 }; + uint8_t open_undef[] = { 0x32, 0x80 }; + uint8_t close_undef[] = { 0x00, 0x00 }; for(i = 0; i < spec->n; ++i) { result = curlx_dyn_addn(buf, open_undef, sizeof(open_undef)); @@ -65,8 +66,8 @@ static bool do_test1657(const struct test1657_spec *spec, size_t i, { CURLcode result; struct Curl_asn1Element elem; - const char *in; - const char *ptr; + const uint8_t *in; + const uint8_t *ptr; memset(&elem, 0, sizeof(elem)); curlx_dyn_reset(buf); @@ -75,7 +76,7 @@ static bool do_test1657(const struct test1657_spec *spec, size_t i, curl_mfprintf(stderr, "test %zu: error setting buf %d\n", i, (int)result); return FALSE; } - in = curlx_dyn_ptr(buf); + in = curlx_dyn_uptr(buf); ptr = getASN1Element(&elem, in, in + curlx_dyn_len(buf)); result = ptr ? CURLE_OK : CURLE_BAD_FUNCTION_ARGUMENT; if(result != spec->result_exp) { @@ -86,6 +87,59 @@ static bool do_test1657(const struct test1657_spec *spec, size_t i, return TRUE; } +struct test1657_cert_spec { + const char * const pem; + bool exp_success; +}; + +static const struct test1657_cert_spec test1657_certs[] = { + { /* a valid certificate */ + "MIIBGTCBzKADAgECAhQ8Ssn8BaVsqlKKccv81NFuKY1vATAFBgMrZXAwADAeFw0y" + "NjA5MDIwNDU0MjZaFw0yNjEwMDIwNDU0MjZaMAAwKjAFBgMrZXADIQBeG0S343s9" + "5yRmIK8xZmGvUmiiYvRH5ZMcPq6rvHHKwqNYMFYwNQYDKgMEBC4wADAqMAUGAytl" + "cAMhAPP5TXSe2WHt/bebbP8eHPXrG3LNxYz8TnHNgpwTFsT7MB0GA1UdDgQWBBR6" + "aqL9LrZiIpgYCy0X4fNrhgFQsTAFBgMrZXADQQALU0OHGcIvfTwyin/+u9csJ9hZ" + "W0c+XdHJY5uDdCR1O92K4Vnozf/QCe7ITOJ8aUnCs7Jgf5xrDgyXZNdum04A", + TRUE + }, + { /* a certificate with "validity" element that has too long length */ + "MIIBGTCBzKADAgECAhQ8Ssn8BaVsqlKKccv81NFuKY1vATAFBgMrZXAwADBZFw0y" + "NjA5MDIwNDU0MjZaFw0yNjEwMDIwNDU0MjZaMAAwKjAFBgMrZXADIQBeG0S343s9" + "5yRmIK8xZmGvUmiiYvRH5ZMcPq6rvHHKwqNYMFYwNQYDKgMEBC4wADAqMAUGAytl" + "cAMhAPP5TXSe2WHt/bebbP8eHPXrG3LNxYz8TnHNgpwTFsT7MB0GA1UdDgQWBBR6" + "aqL9LrZiIpgYCy0X4fNrhgFQsTAFBgMrZXADQQALU0OHGcIvfTwyin/+u9csJ9hZ" + "W0c+XdHJY5uDdCR1O92K4Vnozf/QCe7ITOJ8aUnCs7Jgf5xrDgyXZNdum04A", + FALSE + } +}; + +static bool test1657_parse_cert(size_t i, + const struct test1657_cert_spec *spec) +{ + struct Curl_X509certificate cert; + CURLcode result; + uint8_t *cert_der; + size_t cert_der_len; + bool success; + + result = curlx_base64_decode(spec->pem, &cert_der, &cert_der_len); + if(result) { + curl_mfprintf(stderr, "cert %zu: base64 decoding failed: %d\n", + i, (int)result); + return FALSE; + } + + success = !Curl_parseX509(&cert, cert_der, cert_der + cert_der_len); + if(success != spec->exp_success) { + curl_mfprintf(stderr, "cert %zu: parsing DER %sfailed\n", i, + spec->exp_success ? "" : "should have "); + return FALSE; + } + + curlx_free(cert_der); + return TRUE; +} + static CURLcode test_unit1657(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -105,7 +159,14 @@ static CURLcode test_unit1657(const char *arg) if(!do_test1657(&test1657_specs[i], i, &dbuf)) all_ok = FALSE; } - fail_unless(all_ok, "some tests of getASN1Element() fails"); + fail_unless(all_ok, "some getASN1Element() tests failed"); + + all_ok = TRUE; + for(i = 0; i < CURL_ARRAYSIZE(test1657_certs); ++i) { + if(!test1657_parse_cert(i, &test1657_certs[i])) + all_ok = FALSE; + } + fail_unless(all_ok, "some certificate tests failed"); curlx_dyn_free(&dbuf); curl_global_cleanup(); diff --git a/tests/unit/unit1658.c b/tests/unit/unit1658.c index f8d2531dc05a..fa7e7cc01855 100644 --- a/tests/unit/unit1658.c +++ b/tests/unit/unit1658.c @@ -205,7 +205,7 @@ static CURLcode test_unit1658(const char *arg) "r:8|" }, { - "rname with null byte", + "rname with NUL byte", (const unsigned char *)"\x00\x00" /* 16-bit prio */ "\x04sa\x00e\x04some\x00", /* RNAME */ 13, diff --git a/tests/unit/unit1664.c b/tests/unit/unit1664.c index f4574d485c67..8fc58a37f3c6 100644 --- a/tests/unit/unit1664.c +++ b/tests/unit/unit1664.c @@ -155,27 +155,6 @@ static CURLcode test_unit1664(const char *arg) } } - { - static const char * const single[] = { - "a", - "aa", - "A", - "b", - "\\", - " ", - "", - NULL - }; - curl_mprintf("curlx_str_single\n"); - for(i = 0; single[i]; i++) { - const char *line = single[i]; - const char *orgline = line; - int rc = curlx_str_single(&line, 'a'); - curl_mprintf("%d: (\"%s\") %d, line %d\n", - i, orgline, rc, (int)(line - orgline)); - } - } - { static const char * const nums[] = { "1", @@ -367,7 +346,16 @@ static CURLcode test_unit1664(const char *arg) const char *orgline = line; int rc = curlx_str_newline(&line); curl_mprintf("%d: (%%%02x) %d, line %d\n", - i, (unsigned int)*orgline, rc, (int)(line - orgline)); +/* Workaround for the expression erroneously returning the byte from the next + address for inputs \n, \r and \r\n. Thus returning NUL, NUL, \n instead of + the expected \n, \r, \r. */ +#if defined(_MSC_VER) && _MSC_VER >= 1923 && _MSC_VER <= 1934 && \ + defined(_WIN64) + i, (unsigned int)newl[i][0], +#else + i, (unsigned int)*orgline, +#endif + rc, (int)(line - orgline)); } } diff --git a/tests/unit/unit1666.c b/tests/unit/unit1666.c index cf272da42fcc..28e53c09c872 100644 --- a/tests/unit/unit1666.c +++ b/tests/unit/unit1666.c @@ -29,20 +29,20 @@ #include "vtls/vtls.h" struct test_1666 { - const char *oid; + const uint8_t *oid; const size_t size; const char *dotted; CURLcode result_exp; }; /* the size of the object needs to deduct the null-terminator */ -#define OID(x) STRCONST(x) +#define OID(x) (const uint8_t *)STRCONST(x) static bool test1666(const struct test_1666 *spec, size_t i, struct dynbuf *dbuf) { CURLcode result; - const char *oid = spec->oid; + const uint8_t *oid = spec->oid; bool ok = TRUE; curlx_dyn_reset(dbuf); @@ -71,8 +71,8 @@ static CURLcode test_unit1666(const char *arg) UNITTEST_BEGIN_SIMPLE static const struct test_1666 test_specs[] = { - { "", 0, "", CURLE_BAD_FUNCTION_ARGUMENT }, - { "\x81", 0, "", CURLE_BAD_FUNCTION_ARGUMENT }, + { OID(""), "", CURLE_BAD_FUNCTION_ARGUMENT }, + { (const uint8_t *)"\x81", 0, "", CURLE_BAD_FUNCTION_ARGUMENT }, { OID("\x8F\xFF\xFF\xFF\x7F"), "2.4294967215", CURLE_OK }, { OID("\x90\x80\x80\x80\x00"), "", CURLE_BAD_FUNCTION_ARGUMENT }, { OID("\x88\x80\x80\x80\x4F"), "2.2147483647", CURLE_OK }, diff --git a/tests/unit/unit1667.c b/tests/unit/unit1667.c index 5fedfd2b40af..5e7325ce75b8 100644 --- a/tests/unit/unit1667.c +++ b/tests/unit/unit1667.c @@ -49,7 +49,7 @@ static bool test1667(const struct test_1667 *spec, size_t i, /* setup private struct for this invoke */ elem.tag = spec->tag; elem.header = NULL; - elem.beg = spec->asn1; + elem.beg = (const uint8_t *)spec->asn1; elem.end = elem.beg + spec->size; elem.eclass = 0; elem.constructed = 0; diff --git a/tests/unit/unit1675.c b/tests/unit/unit1675.c index 1d7c44d46120..32681a1ae06a 100644 --- a/tests/unit/unit1675.c +++ b/tests/unit/unit1675.c @@ -227,14 +227,13 @@ static CURLcode test_unit1675(const char *arg) uc ? "error" : hostname); fails++; } - if(!uc && tests[i].out_zone) { - if(!u.zoneid || strcmp(u.zoneid, tests[i].out_zone)) { - curl_mfprintf(stderr, "ipv6_parse('%s') zone failed:" - " expected '%s', got '%s'\n", - tests[i].in, tests[i].out_zone, - u.zoneid ? u.zoneid : "(null)"); - fails++; - } + if(!uc && tests[i].out_zone && + (!u.zoneid || strcmp(u.zoneid, tests[i].out_zone))) { + curl_mfprintf(stderr, "ipv6_parse('%s') zone failed:" + " expected '%s', got '%s'\n", + tests[i].in, tests[i].out_zone, + u.zoneid ? u.zoneid : "(null)"); + fails++; } } else if(!uc) { @@ -439,5 +438,73 @@ static CURLcode test_unit1675(const char *arg) abort_if(fails, "parse_hostname_login tests failed"); } + /* Test that CURLUPART_HOST normalizes legacy numeric IPv4 forms the + same way the full URL parser does */ + { + int fails = 0; + unsigned int i; + struct host_test { + const char *host; + const char *out; + }; + static const struct host_test tests[] = { + { "2130706433", "127.0.0.1" }, + { "0177.1", "127.0.0.1" }, + { "0x7f.1", "127.0.0.1" }, + { "0xc0a80001", "192.168.0.1" }, + { "127.0.0.1", "127.0.0.1" }, + { "example.com", "example.com" }, + { "0", "0.0.0.0" }, + { "1", "0.0.0.1" }, + { "7", "0.0.0.7" }, + { "9", "0.0.0.9" }, + { "00", "0.0.0.0" }, + }; + + for(i = 0; i < CURL_ARRAYSIZE(tests); i++) { + CURLU *viaurl = curl_url(); + CURLU *viahost = curl_url(); + CURLUcode uc1, uc2; + char *urlbuf = curl_maprintf("http://%s/", tests[i].host); + char *gothost1 = NULL; + char *gothost2 = NULL; + + if(!viaurl || !viahost || !urlbuf) { + curl_mfprintf(stderr, "%u: failed to allocate memory\n", i); + fails++; + goto host_loop_end; + } + + uc1 = curl_url_set(viaurl, CURLUPART_URL, urlbuf, 0); + if(!uc1) + uc1 = curl_url_get(viaurl, CURLUPART_HOST, &gothost1, 0); + + curl_url_set(viahost, CURLUPART_SCHEME, "http", 0); + uc2 = curl_url_set(viahost, CURLUPART_HOST, tests[i].host, 0); + if(!uc2) + uc2 = curl_url_get(viahost, CURLUPART_HOST, &gothost2, 0); + + if(uc1 || uc2 || + strcmp(gothost1, tests[i].out) || + strcmp(gothost2, tests[i].out)) { + curl_mfprintf(stderr, "host '%s' mismatch: full-URL -> '%s' (%d), " + "CURLUPART_HOST -> '%s' (%d), expected '%s'\n", + tests[i].host, + uc1 ? "error" : gothost1, (int)uc1, + uc2 ? "error" : gothost2, (int)uc2, + tests[i].out); + fails++; + } + +host_loop_end: + curl_free(gothost1); + curl_free(gothost2); + curl_free(urlbuf); + curl_url_cleanup(viaurl); + curl_url_cleanup(viahost); + } + abort_if(fails, "CURLUPART_HOST ipv4 normalization parity tests failed"); + } + UNITTEST_END_SIMPLE } diff --git a/tests/unit/unit1676.c b/tests/unit/unit1676.c index 5f33f9f2850d..e1d2d92f65a3 100644 --- a/tests/unit/unit1676.c +++ b/tests/unit/unit1676.c @@ -44,7 +44,7 @@ static CURLcode test_unit1676(const char *arg) * * OID 1.2.840.10046.2.1 = dhpublicnumber */ - static const unsigned char cert[] = { + static const uint8_t cert[] = { 0x30, 0x81, 0x85, 0x30, 0x72, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x30, 0x0B, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B, 0x30, 0x0F, 0x31, 0x0D, 0x30, 0x0B, 0x06, 0x03, 0x55, 0x04, @@ -60,8 +60,8 @@ static CURLcode test_unit1676(const char *arg) }; CURLcode result; - const char *beg = (const char *)&cert[0]; - const char *end = (const char *)&cert[sizeof(cert)]; + const uint8_t *beg = &cert[0]; + const uint8_t *end = &cert[sizeof(cert)]; struct Curl_easy *data; struct curl_slist *slist; const char *dhp_value = NULL; diff --git a/tests/unit/unit1693.c b/tests/unit/unit1693.c new file mode 100644 index 000000000000..f8bbb811cbde --- /dev/null +++ b/tests/unit/unit1693.c @@ -0,0 +1,236 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "unitcheck.h" +#include "urldata.h" +#include "mqtt.h" +#include "select.h" +#include "url.h" + +#ifndef CURL_DISABLE_MQTT + +enum { + T1693_CONNECT_LEN = 26, + T1693_PUBLISH_LEN = 15, + T1693_DISCONNECT_LEN = 2, + T1693_PUBLISH_END = T1693_CONNECT_LEN + T1693_PUBLISH_LEN, + T1693_WIRE_LEN = T1693_PUBLISH_END + T1693_DISCONNECT_LEN +}; + +struct t1693_send { + size_t n; + CURLcode result; +}; + +struct t1693_ctx { + const struct t1693_send *sends; + size_t nsends; + size_t send_index; + size_t recv_index; + size_t written; + size_t pending; + unsigned char expected[T1693_WIRE_LEN]; + unsigned char output[T1693_WIRE_LEN]; +}; + +static CURLcode t1693_send(struct Curl_easy *data, int8_t sockindex, + const uint8_t *buf, size_t len, bool eos, + size_t *pnwritten) +{ + struct t1693_ctx *ctx = data->set.private_data; + const struct t1693_send *send; + size_t remaining; + (void)sockindex; + (void)eos; + *pnwritten = 0; + + abort_unless(ctx->send_index < ctx->nsends, "unexpected send"); + send = &ctx->sends[ctx->send_index++]; + if(ctx->send_index == 1) { + /* The CONNECT client ID is random. Save the packet for retry checks. */ + abort_unless(len == T1693_CONNECT_LEN, "unexpected CONNECT size"); + memcpy(ctx->expected, buf, len); + fail_unless(buf[0] == 0x10 && buf[1] == 0x18, + "unexpected CONNECT header"); + } + + if(ctx->written < T1693_CONNECT_LEN) + remaining = T1693_CONNECT_LEN - ctx->written; + else if(ctx->written < T1693_PUBLISH_END) + remaining = T1693_PUBLISH_END - ctx->written; + else + remaining = sizeof(ctx->expected) - ctx->written; + abort_unless(len == remaining, "packet sent before previous packet drained"); + fail_unless(!memcmp(buf, ctx->expected + ctx->written, len), + "queued packet bytes changed"); + ctx->pending = len; + if(send->result) + return send->result; + + abort_unless(send->n <= len, "send script exceeds packet size"); + memcpy(ctx->output + ctx->written, buf, send->n); + ctx->written += send->n; + ctx->pending -= send->n; + *pnwritten = send->n; + return CURLE_OK; + +unit_test_abort: + return CURLE_SEND_ERROR; +} + +static CURLcode t1693_recv(struct Curl_easy *data, int8_t sockindex, + char *buf, size_t len, size_t *pnread) +{ + static const unsigned char connack[] = { 0x20, 0x02, 0x00, 0x00 }; + struct t1693_ctx *ctx = data->set.private_data; + size_t n = sizeof(connack) - ctx->recv_index; + (void)sockindex; + *pnread = 0; + abort_unless(ctx->written == T1693_CONNECT_LEN, + "read while an outgoing packet is queued"); + abort_unless(n, "unexpected read after CONNACK"); + if(n > len) + n = len; + memcpy(buf, connack + ctx->recv_index, n); + ctx->recv_index += n; + *pnread = n; + return CURLE_OK; + +unit_test_abort: + return CURLE_RECV_ERROR; +} + +static void t1693_meta_dtor(void *p) +{ + /* MQTT metadata supplies its own destructor. */ + (void)p; +} + +static void t1693_run(const struct t1693_send *sends, size_t nsends, + CURLcode expected_result) +{ + static const unsigned char post[] = { + 0x30, 0x0d, 0x00, 0x04, 't', 'e', 's', 't', + 'p', 'a', 'y', 'l', 'o', 'a', 'd', 0xe0, 0x00 + }; + struct t1693_ctx ctx; + struct Curl_easy *data = NULL; + struct connectdata *conn = NULL; + struct easy_pollset ps; + CURLcode result; + bool done = FALSE; + size_t i; + + Curl_pollset_init(&ps); + memset(&ctx, 0, sizeof(ctx)); + ctx.sends = sends; + ctx.nsends = nsends; + memcpy(ctx.expected + T1693_CONNECT_LEN, post, sizeof(post)); + + data = curl_easy_init(); + abort_unless(data, "easy handle allocation failed"); + conn = curlx_calloc(1, sizeof(*conn)); + abort_unless(conn, "connection allocation failed"); + Curl_hash_init(&conn->meta_hash, 23, CURL_HASH_TYPE_BYTES, t1693_meta_dtor); + data->conn = conn; + conn->sock[FIRSTSOCKET] = 42; + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; + conn->send[FIRSTSOCKET] = t1693_send; + conn->recv[FIRSTSOCKET] = t1693_recv; + data->set.private_data = &ctx; + data->set.postfields = CURL_UNCONST("payload"); + data->state.httpreq = HTTPREQ_POST; + data->state.up.path = curlx_strdup("/test"); + abort_unless(data->state.up.path, "path allocation failed"); + result = Curl_protocol_mqtt.setup_connection(data, conn); + abort_unless(!result, "MQTT setup failed"); + + result = Curl_protocol_mqtt.do_it(data, &done); + for(i = 0; !result && !done && i < nsends + 4; ++i) { + Curl_pollset_reset(&ps); + result = Curl_protocol_mqtt.doing_pollset(data, &ps); + abort_unless(!result, "MQTT pollset failed"); + abort_unless(ps.n == 1 && ps.sockets[0] == 42, + "unexpected pollset socket"); + fail_unless(ps.actions[0] == (ctx.pending ? CURL_POLL_OUT : CURL_POLL_IN), + "polling the wrong direction for queued output"); + result = Curl_protocol_mqtt.doing(data, &done); + fail_unless(!done || ctx.written == sizeof(ctx.output), + "transfer completed with unsent packet bytes"); + } + fail_unless(result == expected_result, "unexpected transfer result"); + fail_unless(ctx.send_index == nsends, "not all scripted sends were used"); + fail_unless(!memcmp(ctx.output, ctx.expected, ctx.written), + "packets were not sent in order"); + if(!expected_result) { + fail_unless(done, "transfer did not complete"); + fail_unless(ctx.written == sizeof(ctx.output), "missing packet bytes"); + } + else + fail_unless(!done, "send failure marked the transfer complete"); + +unit_test_abort: + Curl_pollset_cleanup(&ps); + if(conn) + Curl_conn_free(data, conn); + if(data) { + data->conn = NULL; + curl_easy_cleanup(data); + } +} + +#endif /* CURL_DISABLE_MQTT */ + +static CURLcode test_unit1693(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + +#ifndef CURL_DISABLE_MQTT + static const struct t1693_send full[] = { + { T1693_CONNECT_LEN, CURLE_OK }, { T1693_PUBLISH_LEN, CURLE_OK }, + { T1693_DISCONNECT_LEN, CURLE_OK } + }; + static const struct t1693_send partial[] = { + { 3, CURLE_OK }, { 0, CURLE_AGAIN }, { 4, CURLE_OK }, { 19, CURLE_OK }, + { 3, CURLE_OK }, { 4, CURLE_OK }, { 0, CURLE_AGAIN }, { 8, CURLE_OK }, + { 0, CURLE_AGAIN }, { 1, CURLE_OK }, { 0, CURLE_AGAIN }, { 1, CURLE_OK } + }; + static const struct t1693_send zero[] = { + { 0, CURLE_OK }, { T1693_CONNECT_LEN, CURLE_OK }, + { 0, CURLE_OK }, { T1693_PUBLISH_LEN, CURLE_OK }, + { 0, CURLE_OK }, { T1693_DISCONNECT_LEN, CURLE_OK } + }; + static const struct t1693_send send_error[] = { + { T1693_CONNECT_LEN, CURLE_OK }, { 4, CURLE_OK }, { 0, CURLE_SEND_ERROR } + }; + + abort_unless(!curl_global_init(CURL_GLOBAL_ALL), "global init failed"); + t1693_run(full, CURL_ARRAYSIZE(full), CURLE_OK); + t1693_run(partial, CURL_ARRAYSIZE(partial), CURLE_OK); + t1693_run(zero, CURL_ARRAYSIZE(zero), CURLE_OK); + t1693_run(send_error, CURL_ARRAYSIZE(send_error), CURLE_SEND_ERROR); + curl_global_cleanup(); +#endif + + UNITTEST_END_SIMPLE +} diff --git a/tests/unit/unit1961.c b/tests/unit/unit1961.c index 1aa28a25bf9f..ade713435f6e 100644 --- a/tests/unit/unit1961.c +++ b/tests/unit/unit1961.c @@ -144,12 +144,55 @@ static int test_pton(void) return 0; } +struct pton6_case { + const char *addr; + int valid; +}; + +static const struct pton6_case pton6_cases[] = { + { "1:2:3:4:5:6:7:8:", 0 }, /* AISLE-2026-0115-00325 */ + { "::1:", 0 }, /* AISLE-2026-0115-00290 */ + { "1:2:3:", 0 }, + { "1:", 0 }, + { ":1", 0 }, + { ":::", 0 }, + { "1:2:3:4:5:6:7:8::", 0 }, + { "1:2:3:4:5:6:7::8:9", 0 }, + { "", 0 }, + { "::", 1 }, + { "::1", 1 }, + { "1::", 1 }, + { "1::8", 1 }, + { "1:2:3:4:5:6:7:8", 1 }, + { "1:2:3:4:5:6::", 1 }, + { "1:2:3:4:5:6::8", 1 }, + { "fe80::1", 1 }, + { "::ffff:192.0.2.1", 1 }, + { "::127.0.0.1", 1 } +}; + +static int test_pton_cases(void) +{ + unsigned char ipv6a[16]; + size_t i; + + for(i = 0; i < CURL_ARRAYSIZE(pton6_cases); i++) { + int rc = curlx_inet_pton(AF_INET6, pton6_cases[i].addr, ipv6a); + int got_valid = (rc == 1); + if(got_valid != pton6_cases[i].valid) + return 1; /* fail */ + } + + return 0; +} + static CURLcode test_unit1961(const char *arg) { UNITTEST_BEGIN_SIMPLE fail_if(test_ntop(), "curlx_inet_ntop()"); fail_if(test_pton(), "curlx_inet_pton()"); + fail_if(test_pton_cases(), "curlx_inet_pton() IPv6 edge cases"); UNITTEST_END_SIMPLE } diff --git a/tests/unit/unit2413.c b/tests/unit/unit2413.c index 6a74943476ba..362936b40972 100644 --- a/tests/unit/unit2413.c +++ b/tests/unit/unit2413.c @@ -24,6 +24,17 @@ #include "unitcheck.h" #include "urldata.h" +#ifdef HAVE_NET_IF_H +#include +#endif + +#if defined(HAVE_IF_NAMETOINDEX) && defined(USE_WINSOCK) +#if defined(__MINGW32__) && (__MINGW64_VERSION_MAJOR <= 5) +#include /* workaround for old mingw-w64 missing to include it */ +#endif +#include +#endif + static CURLcode test_create2413(const char *name, CURL *curl, const struct Curl_scheme *scheme, @@ -31,7 +42,8 @@ static CURLcode test_create2413(const char *name, uint16_t port, const char *exp_hostname, bool exp_ipv6, - const char *exp_zoneid) + const char *exp_zoneid, + int exp_scopeid) { struct Curl_peer *peer = NULL; CURLcode result; @@ -44,6 +56,10 @@ static CURLcode test_create2413(const char *name, } result = CURLE_FAILED_INIT; +#ifndef USE_IPV6 + (void)exp_scopeid; +#endif + if(peer->scheme != scheme) { curl_mfprintf(stderr, "%s: has wrong scheme", name); } @@ -67,6 +83,11 @@ static CURLcode test_create2413(const char *name, else if(!exp_zoneid && peer->zoneid) curl_mfprintf(stderr, "%s: zoneid=%s, expected nothing", name, peer->zoneid); +#ifdef USE_IPV6 + else if((exp_scopeid >= 0) && (peer->scopeid != (uint32_t)exp_scopeid)) + curl_mfprintf(stderr, "%s: scopeid=%u, expected %d", name, + peer->scopeid, exp_scopeid); +#endif else result = CURLE_OK; @@ -76,6 +97,61 @@ static CURLcode test_create2413(const char *name, return result; } +static CURLcode t2413_cmp(const char *name, + CURL *curl, + const struct Curl_scheme *scheme1, + const char *hostname1, + uint16_t port1, + const struct Curl_scheme *scheme2, + const char *hostname2, + uint16_t port2, + bool exp_equal, + bool exp_same_dest) +{ + struct Curl_peer *peer1 = NULL; + struct Curl_peer *peer2 = NULL; + CURLcode result; + + result = Curl_peer_create((struct Curl_easy *)curl, + scheme1, hostname1, port1, &peer1); + if(result) { + curl_mfprintf(stderr, "%s: create peer1 failed %d", name, (int)result); + goto out; + } + result = Curl_peer_create((struct Curl_easy *)curl, + scheme2, hostname2, port2, &peer2); + if(result) { + curl_mfprintf(stderr, "%s: create peer2 failed %d", name, (int)result); + goto out; + } + + result = CURLE_FAILED_INIT; + if(exp_equal != Curl_peer_equal(peer1, peer2)) + curl_mfprintf(stderr, "%s: equal check failed, not %s", name, + exp_equal ? "equal" : "different"); + else if(exp_same_dest != Curl_peer_same_destination(peer1, peer2)) + curl_mfprintf(stderr, "%s: same destination, not %s", name, + exp_equal ? "the same" : "different"); + else + result = CURLE_OK; + +out: + Curl_peer_unlink(&peer1); + Curl_peer_unlink(&peer2); + fail_unless(!result, "check failed"); + return result; +} + +static uint32_t t2413_scopeid(const char *zone) +{ +#ifdef HAVE_IF_NAMETOINDEX + return (uint32_t)if_nametoindex(zone); +#else + (void)zone; + return 0; +#endif +} + static CURLcode test_unit2413(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -89,19 +165,42 @@ static CURLcode test_unit2413(const char *arg) } test_create2413("peer1", curl, &Curl_scheme_https, "test.curl.se", 1234, - "test.curl.se", FALSE, NULL); + "test.curl.se", FALSE, NULL, 0); test_create2413("peer2", curl, &Curl_scheme_https, "127.0.0.1", 1234, - "127.0.0.1", FALSE, NULL); + "127.0.0.1", FALSE, NULL, 0); test_create2413("peer3", curl, &Curl_scheme_https, "::1", 1234, - "::1", TRUE, NULL); + "::1", TRUE, NULL, 0); test_create2413("peer4", curl, &Curl_scheme_https, "[::1]", 1234, - "::1", TRUE, NULL); + "::1", TRUE, NULL, 0); test_create2413("peer5", curl, &Curl_scheme_https, "test.curl.se.", 1234, - "test.curl.se.", FALSE, NULL); + "test.curl.se.", FALSE, NULL, 0); test_create2413("peer6", curl, &Curl_scheme_https, "[::1%tada]", 1234, - "::1", TRUE, "tada"); + "::1", TRUE, "tada", t2413_scopeid("tada")); test_create2413("peer7", curl, &Curl_scheme_https, "::1%tada", 1234, - "::1", TRUE, "tada"); + "::1", TRUE, "tada", t2413_scopeid("tada")); + test_create2413("peer8", curl, &Curl_scheme_https, "::1%123", 1234, + "::1", TRUE, "123", 123); + test_create2413("peer9", curl, &Curl_scheme_https, "::1%123x", 1234, + "::1", TRUE, "123x", t2413_scopeid("123x")); + + t2413_cmp("cmp1", curl, + &Curl_scheme_https, "test.curl.se", 1234, + &Curl_scheme_https, "test.curl.se", 1234, TRUE, TRUE); + t2413_cmp("cmp2", curl, + &Curl_scheme_https, "Test.curl.se", 1234, + &Curl_scheme_https, "test.curl.se", 1234, TRUE, TRUE); + t2413_cmp("cmp3", curl, + &Curl_scheme_http, "test.curl.se", 1234, + &Curl_scheme_https, "test.curl.se", 1234, FALSE, TRUE); + t2413_cmp("cmp4", curl, + &Curl_scheme_https, "test.curl.se", 1234, + &Curl_scheme_https, "test.curl.se", 443, FALSE, FALSE); + t2413_cmp("cmp5", curl, + &Curl_scheme_https, "[::1%tada]", 443, + &Curl_scheme_https, "[::1%tada]", 443, TRUE, TRUE); + t2413_cmp("cmp6", curl, + &Curl_scheme_https, "[::1%tada]", 443, + &Curl_scheme_https, "[::1%Tada]", 443, FALSE, FALSE); curl_easy_cleanup(curl); curl_global_cleanup(); diff --git a/tests/unit/unit2602.c b/tests/unit/unit2602.c index 6da472787ec6..22f158d6b831 100644 --- a/tests/unit/unit2602.c +++ b/tests/unit/unit2602.c @@ -34,6 +34,7 @@ static CURLcode test_unit2602(const char *arg) struct dynbuf dbuf; CURLcode result; size_t i; + size_t nentries; /* add 1 more header than allowed */ Curl_dynhds_init(&hds, 2, 128); @@ -74,6 +75,10 @@ static CURLcode test_unit2602(const char *arg) fail_unless(dynhds_ccount_name(&hds, "TEST2") == 1, "should"); fail_unless(dynhds_ccontains(&hds, "TesT2"), "should"); fail_unless(dynhds_contains(&hds, "TeSt2", 5), "should"); + + /* Check overflow detection */ + fail_unless(Curl_dynhds_add(&hds, "test1", SIZE_MAX / 2, + "123", SIZE_MAX / 2), "overflow check failed"); Curl_dynhds_free(&hds); /* add header exceeding max overall length */ @@ -87,6 +92,10 @@ static CURLcode test_unit2602(const char *arg) Curl_dynhds_init(&hds, 128, 4 * 1024); fail_if(Curl_dynhds_add(&hds, "test1", 5, "123", 3), "add failed"); fail_if(Curl_dynhds_add(&hds, "test1", 5, "123", 3), "add failed"); + fail_if(Curl_dynhds_add(&hds, "test2", 5, NULL, 0), "add failed"); + fail_if(Curl_dynhds_cadd(&hds, "test2", NULL), "add failed"); + fail_unless(dynhds_ccontains(&hds, "test2"), "should"); + fail_unless(dynhds_ccount_name(&hds, "test2") == 2, "should"); fail_if(Curl_dynhds_cadd(&hds, "blablabla", "thingies"), "add failed"); fail_if(Curl_dynhds_h1_cadd_line(&hds, "blablabla: thingies"), "add failed"); fail_unless(dynhds_ccount_name(&hds, "blablabla") == 2, "should"); @@ -95,6 +104,12 @@ static CURLcode test_unit2602(const char *arg) result = Curl_dynhds_h1_cadd_line(&hds, "blablabla thingies"); fail_unless(result, "add should have failed"); + nentries = Curl_dynhds_count(&hds); + result = Curl_dynhds_h1_cadd_line(&hds, ": thingies"); + fail_unless(result == CURLE_BAD_FUNCTION_ARGUMENT, + "empty name should have failed"); + fail_unless(Curl_dynhds_count(&hds) == nentries, + "empty name should not have been added"); if(!result) { fail_unless(dynhds_ccount_name(&hds, "bLABlaBlA") == 0, "should"); fail_if(Curl_dynhds_cadd(&hds, "Bla-Bla", "thingies"), "add failed"); diff --git a/tests/unit/unit2603.c b/tests/unit/unit2603.c index f2ec4f6b9036..0780567da1fc 100644 --- a/tests/unit/unit2603.c +++ b/tests/unit/unit2603.c @@ -80,12 +80,10 @@ static void parse_success(const struct tcase *t) fail("error consuming"); } in_consumed += nread; - if(nread != buflen) { - if(!p.done) { - curl_mfprintf(stderr, "only %zu/%zu consumed for: '%s'\n", - nread, buflen, buf); - fail("not all consumed"); - } + if(nread != buflen && !p.done) { + curl_mfprintf(stderr, "only %zu/%zu consumed for: '%s'\n", + nread, buflen, buf); + fail("not all consumed"); } } diff --git a/tests/unit/unit2606.c b/tests/unit/unit2606.c index ebe8b275fd89..2c9d83ae4490 100644 --- a/tests/unit/unit2606.c +++ b/tests/unit/unit2606.c @@ -130,6 +130,10 @@ static void t2606_run(struct Curl_easy *data, const struct t2606_case *tc) conn = curlx_calloc(1, sizeof(*conn)); abort_if(!conn, "could not allocate a connection"); + conn->created = curlx_now(); + conn->shutdown.start_ms[FIRSTSOCKET] = + conn->shutdown.start_ms[SECONDARYSOCKET] = -1; + memset(&ctx0, 0, sizeof(ctx0)); memset(&ctx1, 0, sizeof(ctx1)); ctx0.sock = 42; @@ -142,7 +146,7 @@ static void t2606_run(struct Curl_easy *data, const struct t2606_case *tc) Curl_conn_cf_add(data, conn, FIRSTSOCKET, cf0); cf0->connected = tc->c0_connected; if(tc->c0_shutdown) - conn->shutdown.start[FIRSTSOCKET] = curlx_now(); + conn->shutdown.start_ms[FIRSTSOCKET] = 100; } if(tc->has_c1) { @@ -152,7 +156,7 @@ static void t2606_run(struct Curl_easy *data, const struct t2606_case *tc) Curl_conn_cf_add(data, conn, SECONDARYSOCKET, cf1); cf1->connected = tc->c1_connected; if(tc->c1_shutdown) - conn->shutdown.start[SECONDARYSOCKET] = curlx_now(); + conn->shutdown.start_ms[SECONDARYSOCKET] = 100; } } diff --git a/tests/unit/unit3211.c b/tests/unit/unit3211.c index 9fcbe85779a5..d10ad79401cc 100644 --- a/tests/unit/unit3211.c +++ b/tests/unit/unit3211.c @@ -24,7 +24,9 @@ #include "unitcheck.h" #include "urldata.h" #include "uint-bset.h" -#include "uint-hashset.h" +#include "u8_strset.h" +#include "u32_ptrset.h" +#include "ptrarray.h" #include "curl_trc.h" static void t3211_check_bset(const char *name, uint32_t capacity, @@ -130,7 +132,7 @@ static bool t3211_strcmp(const char *s1, const char *s2) return s1 == s2; } -static void t3211_check_strset1(void) +static void t3211_check_u8_strset(void) { struct u8_strset set; char buf[128]; @@ -141,11 +143,11 @@ static void t3211_check_strset1(void) Curl_u8_strset_init(&set); fail_unless(!Curl_u8_strset_count(&set), "initial strset not empty"); - result = Curl_u8_strset_set(&set, 0, "123"); + result = u8_strset_set(&set, 0, "123"); fail_unless(!result, "add1 failed"); fail_unless(Curl_u8_strset_get(&set, 0), "get failed"); fail_unless(!t3211_strcmp("123", Curl_u8_strset_get(&set, 0)), "wrong get1"); - result = Curl_u8_strset_set(&set, 0, "456"); + result = u8_strset_set(&set, 0, "456"); fail_unless(!result, "add2 failed"); fail_unless(!t3211_strcmp("456", Curl_u8_strset_get(&set, 0)), "wrong get2"); Curl_u8_strset_unset(&set, 0); @@ -155,7 +157,7 @@ static void t3211_check_strset1(void) for(i = 0; i < 8; ++i) { idx = (uint8_t)((8 * i) + 3); curl_msnprintf(buf, sizeof(buf), "str-%d", idx); - result = Curl_u8_strset_set(&set, idx, buf); + result = u8_strset_set(&set, idx, buf); fail_unless(!result, "loop4-add failed"); fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)), "wrong get loop4"); @@ -177,7 +179,7 @@ static void t3211_check_strset1(void) /* Add entry 2 again, check */ idx = (uint8_t)((8 * 2) + 3); curl_msnprintf(buf, sizeof(buf), "str-%d", idx); - result = Curl_u8_strset_set(&set, idx, buf); + result = u8_strset_set(&set, idx, buf); fail_unless(!result, "re-add 2 failed"); fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)), "wrong re-add 2 get"); @@ -192,7 +194,7 @@ static void t3211_check_strset1(void) fail_unless(Curl_u8_strset_count(&set) == 8, "wrong count pre add 5"); idx = (uint8_t)((9 * 4) + 3); curl_msnprintf(buf, sizeof(buf), "str-%d", idx); - result = Curl_u8_strset_set(&set, idx, buf); + result = u8_strset_set(&set, idx, buf); fail_unless(!result, "add4 failed"); fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)), "wrong get4"); @@ -202,7 +204,7 @@ static void t3211_check_strset1(void) fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, idx)), "wrong get loop5"); } - fail_unless(Curl_u8_strset_count(&set) == 9, "wrong count aftger add 5"); + fail_unless(Curl_u8_strset_count(&set) == 9, "wrong count after add 5"); Curl_u8_strset_clear(&set); @@ -210,7 +212,7 @@ static void t3211_check_strset1(void) for(j = 0; j <= UINT8_MAX; ++j) { i = (uint8_t)j; curl_msnprintf(buf, sizeof(buf), "str-%d", i); - result = Curl_u8_strset_set(&set, i, buf); + result = u8_strset_set(&set, i, buf); fail_unless(!result, "loop256-add failed"); fail_unless(!t3211_strcmp(buf, Curl_u8_strset_get(&set, i)), "wrong get loop256"); @@ -220,6 +222,101 @@ static void t3211_check_strset1(void) fail_unless(!Curl_u8_strset_count(&set), "cleared strset not empty"); } +static void t3211_check_u32_ptrset(void) +{ + struct u32_ptrset set; + CURLcode result; + uint32_t i, idx; + + Curl_u32_ptrset_init(&set, NULL); + fail_unless(!Curl_u32_ptrset_count(&set), "initial ptrset not empty"); + + result = Curl_u32_ptrset_set(&set, 0, CURL_UNCONST("123")); + fail_unless(!result, "ptrset add1 failed"); + fail_unless(Curl_u32_ptrset_get(&set, 0), "ptrset get failed"); + fail_unless(!t3211_strcmp("123", Curl_u32_ptrset_get(&set, 0)), + "ptrset wrong get1"); + result = Curl_u32_ptrset_set(&set, 0, CURL_UNCONST("456")); + fail_unless(!result, "add2 failed"); + fail_unless(!t3211_strcmp("456", Curl_u32_ptrset_get(&set, 0)), + "ptrset wrong get2"); + Curl_u32_ptrset_unset(&set, 0); + fail_unless(!Curl_u32_ptrset_get(&set, 0), "ptrset unset failed"); + + /* Initial size is 64, add 64 hash collisions */ + for(i = 0; i < 64; ++i) { + idx = (uint32_t)((64 * i) + 3); + result = Curl_u32_ptrset_set(&set, idx, CURL_UNCONST("loop64")); + fail_unless(!result, "ptrset loop64 add failed"); + fail_unless(!t3211_strcmp("loop64", Curl_u32_ptrset_get(&set, idx)), + "ptrset wrong get loop64"); + } + + /* Remove collided entry 2, check again */ + idx = (uint32_t)((64 * 2) + 3); + Curl_u32_ptrset_unset(&set, idx); + fail_unless(!Curl_u32_ptrset_get(&set, idx), "ptrset unset2 failed"); + + /* Add entry 2 again, check */ + idx = (uint8_t)((64 * 2) + 3); + result = Curl_u32_ptrset_set(&set, idx, CURL_UNCONST("loop64b")); + fail_unless(!result, "ptrset re-add 2 failed"); + fail_unless(!t3211_strcmp("loop64b", Curl_u32_ptrset_get(&set, idx)), + "ptrset wrong re-add 2 get"); + + /* Add a 65th, set grows */ + fail_unless(Curl_u32_ptrset_count(&set) == 64, + "ptrset wrong count pre add 5"); + idx = (uint32_t)((65 * 4) + 3); + result = Curl_u32_ptrset_set(&set, idx, CURL_UNCONST("add65")); + fail_unless(!result, "ptrset add65 failed"); + fail_unless(!t3211_strcmp("add65", Curl_u32_ptrset_get(&set, idx)), + "ptrset wrong result after add65"); + fail_unless(Curl_u32_ptrset_count(&set) == 65, "wrong count after add65"); + + Curl_u32_ptrset_clear(&set); + fail_unless(!Curl_u32_ptrset_count(&set), "ptrset cleared not empty"); +} + +static void t3211_check_ptrarray(void) +{ + struct ptrarray pa; + CURLcode result; + char d0 = '0', d1 = '1'; + uint32_t i; + + Curl_ptrarray_init(&pa); + fail_unless(!Curl_ptrarray_count(&pa), "ptrarray init not empty"); + + result = Curl_ptrarray_add(&pa, &d0); + fail_unless(!result, "ptrarray add0 failed"); + fail_unless(Curl_ptrarray_get(&pa, 0) == &d0, "ptrarray get0 failed"); + fail_unless(Curl_ptrarray_count(&pa) == 1, "ptrarray add0 count wrong"); + result = Curl_ptrarray_add(&pa, &d1); + fail_unless(!result, "ptrarray add1 failed"); + fail_unless(Curl_ptrarray_get(&pa, 1) == &d1, "ptrarray get1 failed"); + fail_unless(Curl_ptrarray_count(&pa) == 2, "ptrarray add1 count wrong"); + Curl_ptrarray_remove(&pa, 0); + fail_unless(Curl_ptrarray_count(&pa) == 1, "ptrarray rem count wrong"); + fail_unless(Curl_ptrarray_get(&pa, 1) == NULL, "ptrarray rem+get1 not NULL"); + fail_unless(Curl_ptrarray_get(&pa, 0) == &d1, "ptrarray rem+get0 failed"); + Curl_ptrarray_remove(&pa, 0); + fail_unless(Curl_ptrarray_count(&pa) == 0, "ptrarray rem2 count wrong"); + + for(i = 0; i < 300; ++i) { + result = Curl_ptrarray_add(&pa, (i & 0x1) ? &d1 : &d0); + fail_unless(!result, "ptrarray loop add failed"); + } + fail_unless(Curl_ptrarray_count(&pa) == 300, "ptrarray loop count wrong"); + for(i = 0; i < 300; ++i) { + fail_unless(Curl_ptrarray_get(&pa, i) == ((i & 0x1) ? &d1 : &d0), + "ptrarray loop get failed"); + } + + Curl_ptrarray_clear(&pa); + fail_unless(!Curl_ptrarray_count(&pa), "ptrarray clear not empty"); +} + static CURLcode test_unit3211(const char *arg) { UNITTEST_BEGIN_SIMPLE @@ -243,7 +340,9 @@ static CURLcode test_unit3211(const char *arg) t3211_check_bset("s1", 100, s1, CURL_ARRAYSIZE(s1)); t3211_check_bset("s2", 1000, s2, CURL_ARRAYSIZE(s2)); - t3211_check_strset1(); + t3211_check_u8_strset(); + t3211_check_u32_ptrset(); + t3211_check_ptrarray(); UNITTEST_END_SIMPLE } diff --git a/tests/unit/unit3212.c b/tests/unit/unit3212.c index 26a22097212a..a30830b0e94f 100644 --- a/tests/unit/unit3212.c +++ b/tests/unit/unit3212.c @@ -30,7 +30,7 @@ static CURLcode t3212_setup(struct uint32_tbl *tbl) { - Curl_uint32_tbl_init(tbl, NULL); + Curl_uint32_tbl_init(tbl); return Curl_uint32_tbl_resize(tbl, TBL_SIZE); } diff --git a/tests/unit/unit3230.c b/tests/unit/unit3230.c new file mode 100644 index 000000000000..4d2d72e55573 --- /dev/null +++ b/tests/unit/unit3230.c @@ -0,0 +1,140 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "unitcheck.h" +#include "urldata.h" +#include "multiif.h" + +#define T3230_TIMER_CALLS 6 + +struct t3230_timer_ctx { + long timeout_ms[T3230_TIMER_CALLS]; + size_t count; +}; + +static int t3230_timer_cb(CURLM *multi, long timeout_ms, void *userp) +{ + struct t3230_timer_ctx *ctx = userp; + (void)multi; + + if(ctx->count < CURL_ARRAYSIZE(ctx->timeout_ms)) + ctx->timeout_ms[ctx->count] = timeout_ms; + ++ctx->count; + return 0; +} + +static CURLcode t3230_setup(void) +{ + CURLcode result = CURLE_OK; + global_init(CURL_GLOBAL_ALL); + return result; +} + +static void t3230_test_timer_updates(void) +{ + CURLM *handle = curl_multi_init(); + struct Curl_multi *multi = handle; + struct t3230_timer_ctx ctx; + struct curltime now; + timediff_t expire_offset_us; + CURLMcode mresult; + int running; + + memset(&ctx, 0, sizeof(ctx)); + if(!multi) { + fail("multi handle creation failed"); + return; + } + + mresult = curl_multi_setopt(handle, CURLMOPT_TIMERDATA, &ctx); + fail_unless(mresult == CURLM_OK, "setting timer data failed"); + mresult = curl_multi_setopt(handle, CURLMOPT_TIMERFUNCTION, + t3230_timer_cb); + fail_unless(mresult == CURLM_OK, "setting timer callback failed"); + + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "initial timer update failed"); + fail_unless(!ctx.count, "callback invoked without a timeout"); + fail_unless(!multi->last_timeout_set, "initial timer marked as set"); + + now = curlx_now(); + Curl_expire_set(multi->admin, EXPIRE_TIMEOUT, 600000, &now); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "setting timer failed"); + fail_unless(ctx.count == 1, "setting timer did not invoke callback"); + fail_unless(multi->last_timeout_set, "timer not marked as set"); + + Curl_expire_set(multi->admin, EXPIRE_TIMEOUT, 1200000, &now); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "replacing timer failed"); + fail_unless(ctx.count == 2, "replacing timer did not invoke callback"); + + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "unchanged timer update failed"); + fail_unless(ctx.count == 2, "unchanged timer invoked callback"); + + expire_offset_us = multi->last_expire_offset_us; + mresult = curl_multi_socket_action(handle, CURL_SOCKET_TIMEOUT, 0, &running); + fail_unless(mresult == CURLM_OK, "forced timer refresh failed"); + fail_unless(ctx.count == 3, "timer refresh did not invoke callback"); + fail_unless(multi->last_expire_offset_us == expire_offset_us, + "timer refresh changed expiry offset"); + fail_unless(multi->last_timeout_set, "refreshed timer not marked as set"); + + Curl_expire_clear_all(multi->admin); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "timer clear update failed"); + fail_unless(ctx.count == 4, "clearing timer did not invoke callback"); + fail_unless(!multi->last_timeout_set, "cleared timer marked as set"); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "repeated timer clear update failed"); + + Curl_multi_mark_dirty(multi->admin); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "zero timer update failed"); + fail_unless(multi->last_timeout_set, "zero timer not marked as set"); + Curl_multi_clear_dirty(multi->admin); + fail_unless(Curl_update_timer(multi) == CURLM_OK, + "clearing zero timer failed"); + fail_unless(!multi->last_timeout_set, "zero timer clear left timer set"); + + fail_unless(ctx.count == T3230_TIMER_CALLS, "wrong timer callback count"); + fail_unless(ctx.timeout_ms[0] >= 0, "wrong initial timeout"); + fail_unless(ctx.timeout_ms[1] > ctx.timeout_ms[0], + "wrong replacement timeout"); + fail_unless(ctx.timeout_ms[2] >= 0, "wrong refreshed timeout"); + fail_unless(ctx.timeout_ms[3] == -1, "wrong cleared timeout"); + fail_unless(ctx.timeout_ms[4] == 0, "wrong zero timeout"); + fail_unless(ctx.timeout_ms[5] == -1, "wrong zero timeout clear"); + + curl_multi_cleanup(handle); +} + +static CURLcode test_unit3230(const char *arg) +{ + UNITTEST_BEGIN(t3230_setup()) + + t3230_test_timer_updates(); + + UNITTEST_END(curl_global_cleanup()) +} diff --git a/tests/unit/unit1609.c b/tests/unit/unit3271.c similarity index 74% rename from tests/unit/unit1609.c rename to tests/unit/unit3271.c index 076bf21499c5..abab4cc700f3 100644 --- a/tests/unit/unit1609.c +++ b/tests/unit/unit3271.c @@ -26,7 +26,7 @@ #include "connect.h" #include "curl_addrinfo.h" -static CURLcode t1609_setup(void) +static CURLcode t3271_setup(void) { CURLcode result = CURLE_OK; global_init(CURL_GLOBAL_ALL); @@ -66,9 +66,9 @@ static CURLcode t1609_setup(void) expected result: cached address has zero timestamp and new address */ -static CURLcode test_unit1609(const char *arg) +static CURLcode test_unit3271(const char *arg) { - UNITTEST_BEGIN(t1609_setup()) + UNITTEST_BEGIN(t3271_setup()) struct testcase { /* host:port:address[,address]... */ @@ -76,14 +76,14 @@ static CURLcode test_unit1609(const char *arg) /* lowercase host and port to retrieve the addresses from hostcache */ const char *host; - int port; + uint16_t port; /* 0 to 9 addresses expected from hostcache */ const char *address[10]; }; static const struct testcase tests[] = { - /* spaces are not allowed, for now */ + /* spaces are not allowed */ { "test.com:80:127.0.0.1", "test.com", 80, { "127.0.0.1", } }, @@ -96,18 +96,51 @@ static CURLcode test_unit1609(const char *arg) struct Curl_multi *multi = NULL; struct Curl_easy *easy = NULL; struct curl_slist *list = NULL; - - /* important: we setup cache outside of the loop - and also clean cache after the loop. In contrast, for example, - test 1607 sets up and cleans cache on each iteration. */ + char hostname[258]; + struct dnsc_id long_id; + struct Curl_dns_entry *long_dns; + + /* Check full hostname matching when the DNS cache key is truncated. */ + memset(hostname, 'a', sizeof(hostname) - 1); + hostname[sizeof(hostname) - 1] = 0; + long_dns = curlx_calloc(1, sizeof(*long_dns) + sizeof(hostname)); + abort_unless(long_dns, "could not allocate cache entry"); + hostname[255] = 0; + memcpy(long_dns->hostname, hostname, 256); + hostname[255] = 'a'; + long_dns->hostlen = 255; + long_dns->port = 80; + long_id.port = 80; + curlx_str_assign(&long_id.name, hostname, strlen(hostname)); + fail_unless(!dnsc_id_matches_entry(&long_id, long_dns), + "long query must not match shorter cached hostname"); + memcpy(long_dns->hostname, hostname, sizeof(hostname)); + long_dns->hostlen = (uint16_t)strlen(hostname); + fail_unless(dnsc_id_matches_entry(&long_id, long_dns), + "identical long hostname must match"); + hostname[0] = 'A'; + fail_unless(dnsc_id_matches_entry(&long_id, long_dns), + "long hostname lookup must remain case insensitive"); + hostname[255] = 0; + curlx_str_assign(&long_id.name, hostname, strlen(hostname)); + fail_unless(!dnsc_id_matches_entry(&long_id, long_dns), + "short query must not match longer cached hostname"); + curlx_free(long_dns); + + /* important: we setup cache outside of the loop and also clean cache after + the loop. In contrast, for example, test 1607 sets up and cleans cache on + each iteration. */ for(i = 0; i < CURL_ARRAYSIZE(tests); ++i) { size_t j; size_t addressnum = CURL_ARRAYSIZE(tests[i].address); struct Curl_addrinfo *addr; struct Curl_dns_entry *dns; - void *entry_id; + struct dnsc_id id; + struct dnsc_key key; + const char type = CURL_DNST_ADDR; bool problem = FALSE; + struct Curl_peer *peer = NULL; easy = curl_easy_init(); if(!easy) { curl_global_cleanup(); @@ -129,13 +162,13 @@ static CURLcode test_unit1609(const char *arg) if(Curl_loadhostpairs(easy)) goto error; - entry_id = (void *)curl_maprintf("%s:%d", tests[i].host, tests[i].port); - if(!entry_id) + if(Curl_peer_create(easy, &Curl_scheme_http, + tests[i].host, tests[i].port, &peer)) goto error; - dns = Curl_hash_pick(&multi->dnscache.entries, - entry_id, strlen(entry_id) + 1); - curlx_safefree(entry_id); + dnsc_peer2id(&id, type, peer); + dnsc_id2key(&key, &id); + dns = Curl_hash_pick(&multi->dnscache.entries, key.data, key.len); addr = dns ? dns->addr : NULL; @@ -194,6 +227,7 @@ static CURLcode test_unit1609(const char *arg) addr = addr->ai_next; } + Curl_peer_unlink(&peer); curl_easy_cleanup(easy); easy = NULL; diff --git a/tests/unit/unit3303.c b/tests/unit/unit3303.c index 56f52ebf5730..a5ef56895f9d 100644 --- a/tests/unit/unit3303.c +++ b/tests/unit/unit3303.c @@ -34,14 +34,16 @@ static CURLcode test_unit3303(const char *arg) #ifdef USE_SSL CURL *curl; + struct Curl_easy *data = NULL; struct connectdata *conn; - struct ssl_primary_config *primary; char *saved; static char alt_passwd[] = "wrong"; static char alt_key[] = "other.key"; static char alt_ktype[] = "DER"; static char alt_ctype[] = "P12"; struct Curl_peer *origin = NULL; + struct ssl_filter_config ssl_config; + struct ssl_filter_config proxy_ssl_config; CURLcode result; curl_global_init(CURL_GLOBAL_ALL); @@ -50,16 +52,16 @@ static CURLcode test_unit3303(const char *arg) curl_global_cleanup(); goto unit_test_abort; } + data = (struct Curl_easy *)curl; - result = Curl_peer_create((struct Curl_easy *)curl, - &Curl_scheme_https, + result = Curl_peer_create(data, &Curl_scheme_https, "test.curl.se", 1234, &origin); if(result) { curl_easy_cleanup(curl); curl_global_cleanup(); goto unit_test_abort; } - Curl_peer_link(&((struct Curl_easy *)curl)->state.initial_origin, origin); + Curl_peer_link(&data->state.initial_origin, origin); curl_easy_setopt(curl, CURLOPT_SSLCERT, "client.pem"); curl_easy_setopt(curl, CURLOPT_SSLKEY, "client.key"); @@ -67,7 +69,10 @@ static CURLcode test_unit3303(const char *arg) curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM"); curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM"); - if(Curl_ssl_easy_config_complete((struct Curl_easy *)curl, origin)) { + memset(&ssl_config, 0, sizeof(ssl_config)); + memset(&proxy_ssl_config, 0, sizeof(proxy_ssl_config)); + if(Curl_ssl_filter_config_tmp_init(data, origin, &ssl_config, + &proxy_ssl_config)) { Curl_peer_unlink(&origin); curl_easy_cleanup(curl); curl_global_cleanup(); @@ -75,7 +80,8 @@ static CURLcode test_unit3303(const char *arg) } conn = curlx_calloc(1, sizeof(*conn)); - if(!conn || Curl_ssl_conn_config_init((struct Curl_easy *)curl, conn)) { + if(!conn || + Curl_ssl_conn_config_clone(&ssl_config, NULL, conn)) { if(conn) Curl_ssl_conn_config_cleanup(conn); curlx_free(conn); @@ -86,49 +92,42 @@ static CURLcode test_unit3303(const char *arg) } /* Baseline: identical config must match. */ - fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + fail_unless(Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "identical mTLS config should match"); - primary = &((struct Curl_easy *)curl)->set.ssl.primary; - /* Different key_passwd must not match. */ - saved = primary->key_passwd; - primary->key_passwd = alt_passwd; - fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + saved = ssl_config.key_passwd; + ssl_config.key_passwd = alt_passwd; + fail_unless(!Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "different key_passwd must not reuse conn"); - primary->key_passwd = saved; + ssl_config.key_passwd = saved; /* Different key path must not match. */ - saved = primary->key; - primary->key = alt_key; - fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + saved = ssl_config.key; + ssl_config.key = alt_key; + fail_unless(!Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "different key must not reuse conn"); - primary->key = saved; + ssl_config.key = saved; /* Different key type must not match. */ - saved = primary->key_type; - primary->key_type = alt_ktype; - fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + saved = ssl_config.key_type; + ssl_config.key_type = alt_ktype; + fail_unless(!Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "different key_type must not reuse conn"); - primary->key_type = saved; + ssl_config.key_type = saved; /* Different cert type must not match. */ - saved = primary->cert_type; - primary->cert_type = alt_ctype; - fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + saved = ssl_config.cert_type; + ssl_config.cert_type = alt_ctype; + fail_unless(!Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "different cert_type must not reuse conn"); - primary->cert_type = saved; + ssl_config.cert_type = saved; /* All fields restored: must match again. */ - fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn, - FALSE), + fail_unless(Curl_ssl_conn_config_match(data, &ssl_config, conn, FALSE), "restored mTLS config should match"); + Curl_ssl_config_cleanup(&ssl_config); Curl_ssl_conn_config_cleanup(conn); curlx_free(conn); curl_easy_cleanup(curl); diff --git a/tests/unit/unit3304.c b/tests/unit/unit3304.c index 7336aa731bda..1d5546a13ee9 100644 --- a/tests/unit/unit3304.c +++ b/tests/unit/unit3304.c @@ -45,7 +45,7 @@ static CURLcode test_unit3304(const char *arg) #ifdef USE_SSL struct Curl_peer origin; struct ssl_peer peer; - struct ssl_primary_config ssl; + struct ssl_filter_config ssl; char *key1 = NULL; char *key2 = NULL; static char base_hostname[] = "example.com"; diff --git a/tests/unit/unit3400.c b/tests/unit/unit3400.c index 364a2b540f11..539f16f84d87 100644 --- a/tests/unit/unit3400.c +++ b/tests/unit/unit3400.c @@ -241,6 +241,41 @@ static void test_capsule_decode_paths(void) Curl_bufq_free(&q); } + +static void test_capsule_full_buffer_reject(void) +{ + /* A capsule whose payload can never fit the bounded bufq must fail + instead of asking for more data forever. */ + struct bufq q; + unsigned char out[256]; + unsigned char cap[128]; + CURLcode err = CURLE_OK; + size_t nread; + /* type 0x00, length 201 as a 2-byte varint, context 0x00 */ + static const unsigned char hdr[] = { 0x00, 0x40, 0xC9, 0x00 }; + + Curl_bufq_init2(&q, 32, 4, BUFQ_OPT_NONE); + + memset(cap, 0x5A, sizeof(cap)); + memcpy(cap, hdr, sizeof(hdr)); + + /* the queue can still grow, so wait for the rest */ + queue_bytes(&q, cap, 64); + nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out), &err); + fail_unless(err == CURLE_AGAIN, "partial capsule should return AGAIN"); + fail_unless(nread == 0, "partial capsule should read zero bytes"); + + /* the queue is full and the capsule is still short, it never completes */ + queue_bytes(&q, cap + 64, sizeof(cap) - 64); + fail_unless(Curl_bufq_is_full(&q), "queue should be full"); + nread = Curl_capsule_process_udp_raw(NULL, NULL, &q, out, sizeof(out), &err); + fail_unless(err == CURLE_RECV_ERROR, + "unbufferable capsule should return RECV_ERROR"); + fail_unless(nread == 0, "unbufferable capsule should read zero bytes"); + + Curl_bufq_free(&q); +} + #endif /* USE_NGTCP2 && !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */ static CURLcode test_unit3400(const char *arg) @@ -257,6 +292,7 @@ static CURLcode test_unit3400(const char *arg) test_capsule_encode_decode_roundtrip(); test_capsule_decode_paths(); test_capsule_sequential_decode(); + test_capsule_full_buffer_reject(); #endif UNITTEST_END_SIMPLE diff --git a/tests/unit/unit4781.c b/tests/unit/unit4781.c new file mode 100644 index 000000000000..962352bb3e63 --- /dev/null +++ b/tests/unit/unit4781.c @@ -0,0 +1,283 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "unitcheck.h" + +static CURLcode test_unit4781(const char *arg) +{ +#ifndef CURL_DISABLE_PARSEDATE + unsigned int i; + int errors = 0; + struct day { + const char *name; + int val; + }; + + /* three-letter version or full version, nothing else */ + static const struct day days[] = { + { "wedmesday", -1 }, + { "monray", -1 }, + { "mon", 0 }, + { "tue", 1 }, + { "wed", 2 }, + { "thu", 3 }, + { "fri", 4 }, + { "sat", 5 }, + { "sun", 6 }, + { "monday", 0 }, + { "tuesday", 1 }, + { "wednesday", 2 }, + { "thursday", 3 }, + { "friday", 4 }, + { "saturday", 5 }, + { "sunday", 6 }, + { "monDAY", 0 }, + { "tueSDAY", 1 }, + { "wedneSDAY", 2 }, + { "thursdAY", 3 }, + { "frIDAY", 4 }, + { "satURDAY", 5 }, + { "suNDAY", 6 }, + { "monda", -1 }, + { "tuesda", -1 }, + { "wednesda", -1 }, + { "thursda", -1 }, + { "frida", -1 }, + { "saturda", -1 }, + { "sunda", -1 }, + { "MON", 0 }, + { "TUE", 1 }, + { "WED", 2 }, + { "THU", 3 }, + { "FRI", 4 }, + { "SAT", 5 }, + { "SUN", 6 }, + { "mo", -1 }, + { "tu", -1 }, + { "we", -1 }, + { "th", -1 }, + { "fr", -1 }, + { "sa", -1 }, + { "su", -1 }, + { "mom", -1 }, + { "tuf", -1 }, + { "wef", -1 }, + { "thv", -1 }, + { "frj", -1 }, + { "sau", -1 }, + { "sum", -1 }, + { "abc", -1 }, + { " monday", -1 }, + { " tuesday", -1 }, + { " wednesday", -1 }, + { " thursday", -1 }, + { " friday", -1 }, + { " saturday", -1 }, + { " sunday", -1 }, + { "monday ", -1 }, + { "tuesday ", -1 }, + { "wednesday ", -1 }, + { "thursday ", -1 }, + { "friday ", -1 }, + { "saturday ", -1 }, + { "sunday ", -1 }, + }; + + /* three-letter month versions only */ + static const struct day months[] = { + { "monday", -1 }, + { "tuesday", -1 }, + { "wednesday", -1 }, + { "thursday", -1 }, + { "friday", -1 }, + { "saturday", -1 }, + { "jan", 0 }, + { "feb", 1 }, + { "mar", 2 }, + { "apr", 3 }, + { "may", 4 }, + { "jun", 5 }, + { "jul", 6 }, + { "aug", 7 }, + { "sep", 8 }, + { "oct", 9 }, + { "nov", 10 }, + { "dec", 11 }, + { "january", -1 }, + { "february", -1 }, + { "march", -1 }, + { "april", -1 }, + { "june", -1 }, + { "july", -1 }, + { "august", -1 }, + { "september", -1 }, + { "october", -1 }, + { "november", -1 }, + { "december", -1 }, + { "janu", -1 }, + { "febr", -1 }, + { "marc", -1 }, + { "apri", -1 }, + { "june", -1 }, + { "july", -1 }, + { "augu", -1 }, + { "sept", -1 }, + { "octo", -1 }, + { "nove", -1 }, + { "dece", -1 }, + { "JAN", 0 }, + { "FEB", 1 }, + { "MAR", 2 }, + { "APR", 3 }, + { "MAY", 4 }, + { "JUN", 5 }, + { "JUL", 6 }, + { "AUG", 7 }, + { "SEP", 8 }, + { "OCT", 9 }, + { "NOV", 10 }, + { "DEC", 11 }, + }; + + static const struct day timezones[] = { + { "A", -3600 }, + { "B", -7200 }, + { "a", -1 }, + { "b", -1 }, + { "C", -10800 }, + { "D", -14400 }, + { "E", -18000 }, + { "F", -21600 }, + { "G", -25200 }, + { "H", -28800 }, + { "I", -32400 }, + { "J", -1 }, + { "K", -36000 }, + { "L", -39600 }, + { "M", -43200 }, + { "N", 3600 }, + { "O", 7200 }, + { "P", 10800 }, + { "Q", 14400 }, + { "R", 18000 }, + { "S", 21600 }, + { "T", 25200 }, + { "U", 28800 }, + { "V", 32400 }, + { "X", 39600 }, + { "Y", 43200 }, + { "Z", 0 }, + { "z", -1 }, + { "@", -1 }, + + { "NT", 39600 }, + { "UT", 0 }, + { "nt", -1 }, + { "ut", -1 }, + { "PT", -1 }, + { "CT", -1 }, + + { "ADT", 10800 }, + { "AST", 14400 }, + { "BST", -3600 }, + { "CAT", 36000 }, + { "CCT", -28800 }, + { "CDT", 18000 }, + { "CET", -3600 }, + { "CST", 21600 }, + { "EDT", 14400 }, + { "EET", -7200 }, + { "EST", 18000 }, + { "FST", -7200 }, + { "FWT", -3600 }, + { "GMT", 0 }, + { "GST", -36000 }, + { "HDT", 32400 }, + { "HST", 36000 }, + { "JST", -32400 }, + { "MDT", 21600 }, + { "MET", -3600 }, + { "MST", 25200 }, + { "NZT", -43200 }, + { "PDT", 25200 }, + { "PST", 28800 }, + { "UTC", 0 }, + { "WAT", 3600 }, + { "WET", 0 }, + { "YDT", 28800 }, + { "YST", 32400 }, + { "yST", -1 }, + { "ZST", -1 }, + + { "AHST", 36000 }, + { "BHST", -1 }, + { "ahst", -1 }, + { "CEST", -7200 }, + { "cest", -1 }, + { "EADT", -39600 }, + { "EAST", -36000 }, + { "IDLE", -43200 }, + { "IDLW", 43200 }, + { "MEST", -7200 }, + { "MESZ", -7200 }, + { "MEWT", -3600 }, + { "NZDT", -46800 }, + { "NZST", -43200 }, + { "WADT", -28800 }, + { "WAST", -25200 } /* spellchecker:disable-line */ + }; + + (void)arg; + + for(i = 0; i < CURL_ARRAYSIZE(days); i++) { + int d = checkday(days[i].name, strlen(days[i].name)); + if(d != days[i].val) { + curl_mfprintf(stderr, "Day: %s returned %d, expected %d\n", + days[i].name, d, days[i].val); + errors++; + } + } + for(i = 0; i < CURL_ARRAYSIZE(months); i++) { + int d = checkmonth(months[i].name, strlen(months[i].name)); + if(d != months[i].val) { + curl_mfprintf(stderr, "Month: %s returned %d, expected %d\n", + months[i].name, d, months[i].val); + errors++; + } + } + for(i = 0; i < CURL_ARRAYSIZE(timezones); i++) { + int d = checktz(timezones[i].name, strlen(timezones[i].name)); + if(d != timezones[i].val) { + curl_mfprintf(stderr, "TZ: %s returned %d, expected %d\n", + timezones[i].name, d, timezones[i].val); + errors++; + } + } + if(errors) + return CURLE_BAD_FUNCTION_ARGUMENT; +#else /* CURL_DISABLE_PARSEDATE */ + /* nothing to do */ + (void)arg; +#endif + return CURLE_OK; +} diff --git a/tests/unit/unit4782.c b/tests/unit/unit4782.c new file mode 100644 index 000000000000..fe0afc4fb5d7 --- /dev/null +++ b/tests/unit/unit4782.c @@ -0,0 +1,283 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) David Bures + * + * 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 "unitcheck.h" + +#ifdef USE_SCHANNEL +#include "urldata.h" +#include "cfilters.h" +#include "curl_sspi.h" +#include "vtls/schannel.h" +#include "vtls/schannel_int.h" +#include "vtls/vtls_int.h" + +static const unsigned char t4782_token[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + +static struct { + unsigned int generated; + unsigned int freed; + unsigned int sends; + unsigned int blocks; + size_t accepted; + size_t first_write; + CURLcode send_error; + SECURITY_STATUS generate_error; +} t4782; + +static SECURITY_STATUS SEC_ENTRY t4782_apply(PCtxtHandle context, + PSecBufferDesc input_desc) +{ + (void)context; + fail_unless(input_desc->cBuffers == 1, "one shutdown input buffer"); + fail_unless(*(DWORD *)input_desc->pBuffers[0].pvBuffer == SCHANNEL_SHUTDOWN, + "shutdown token"); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY t4782_generate( + PCredHandle credential, PCtxtHandle context, TCHAR *target, + unsigned long flags, unsigned long reserved, unsigned long representation, + PSecBufferDesc input_desc, unsigned long reserved2, PCtxtHandle new_context, + PSecBufferDesc output_desc, unsigned long *attributes, PTimeStamp expiry) +{ + SecBuffer *token = &output_desc->pBuffers[0]; + (void)credential; + (void)context; + (void)target; + (void)flags; + (void)reserved; + (void)representation; + (void)input_desc; + (void)reserved2; + (void)new_context; + (void)attributes; + (void)expiry; + t4782.generated++; + token->pvBuffer = curlx_malloc(sizeof(t4782_token)); + if(!token->pvBuffer) + return SEC_E_INSUFFICIENT_MEMORY; + memcpy(token->pvBuffer, t4782_token, sizeof(t4782_token)); + token->cbBuffer = sizeof(t4782_token); + token->BufferType = SECBUFFER_TOKEN; + return t4782.generate_error; +} + +static SECURITY_STATUS SEC_ENTRY t4782_free(void *buffer) +{ + t4782.freed++; + curlx_free(buffer); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY t4782_delete(PCtxtHandle context) +{ + (void)context; + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY t4782_free_cred(PCredHandle credential) +{ + (void)credential; + return SEC_E_OK; +} + +static CURLcode t4782_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const uint8_t *buffer, size_t length, bool eos, + size_t *written) +{ + (void)cf; + (void)data; + (void)eos; + t4782.sends++; + *written = 0; + fail_unless(length == sizeof(t4782_token) - t4782.accepted, + "retry begins at the unsent offset"); + verify_memory(buffer, t4782_token + t4782.accepted, (int)length); + if(t4782.blocks) { + t4782.blocks--; + return CURLE_AGAIN; + } + if(t4782.send_error) + return t4782.send_error; + *written = t4782.first_write ? t4782.first_write : length; + t4782.first_write = 0; + t4782.accepted += *written; + return CURLE_OK; +} + +static CURLcode t4782_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buffer, size_t length, size_t *received) +{ + (void)cf; + (void)data; + (void)buffer; + (void)length; + *received = 0; + return CURLE_AGAIN; +} + +static void t4782_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + (void)cf; + (void)data; +} + +static const struct Curl_cftype t4782_filter = { + "TEST", + 0, + CURL_LOG_LVL_NONE, + t4782_destroy, + Curl_cf_def_connect, + Curl_cf_def_shutdown, + Curl_cf_def_adjust_pollset, + Curl_cf_def_data_pending, + t4782_send, + t4782_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode t4782_setup(CURL **easy) +{ + CURLcode result = CURLE_OK; + global_init(CURL_GLOBAL_ALL); + *easy = curl_easy_init(); + if(!*easy) { + curl_global_cleanup(); + return CURLE_OUT_OF_MEMORY; + } + return result; +} + +static void t4782_stop(CURL *easy) +{ + curl_easy_cleanup(easy); + curl_global_cleanup(); +} +#endif + +static CURLcode test_unit4782(const char *arg) +{ +#ifdef USE_SCHANNEL + CURL *easy = NULL; + PSecurityFunctionTable original; + SecurityFunctionTable functions; + static char hostname[] = "example.invalid"; + unsigned int scenario; + UNITTEST_BEGIN(t4782_setup(&easy)) + + original = Curl_pSecFn; + functions = *original; + functions.ApplyControlToken = t4782_apply; + functions.InitializeSecurityContext = t4782_generate; + functions.FreeContextBuffer = t4782_free; + functions.DeleteSecurityContext = t4782_delete; + functions.FreeCredentialsHandle = t4782_free_cred; + Curl_pSecFn = &functions; + + for(scenario = 0; scenario < 6; scenario++) { + struct schannel_ssl_backend_data backend; + struct ssl_connect_data ssl; + struct connectdata connection; + struct Curl_peer origin; + struct Curl_cfilter cf; + struct Curl_cfilter lower; + CURLcode result; + bool done; + unsigned int attempts; + + memset(&t4782, 0, sizeof(t4782)); + memset(&backend, 0, sizeof(backend)); + memset(&ssl, 0, sizeof(ssl)); + memset(&connection, 0, sizeof(connection)); + memset(&origin, 0, sizeof(origin)); + memset(&cf, 0, sizeof(cf)); + memset(&lower, 0, sizeof(lower)); + backend.cred = curlx_calloc(1, sizeof(*backend.cred)); + backend.ctxt = curlx_calloc(1, sizeof(*backend.ctxt)); + if(!backend.cred || !backend.ctxt) { + curlx_free(backend.cred); + curlx_free(backend.ctxt); + fail("allocation failed"); + break; + } + backend.cred->refcount = 1; + backend.recv_sspi_close_notify = TRUE; + ssl.backend = &backend; + ssl.peer.origin = &origin; + origin.hostname = hostname; + origin.port = 443; + cf.ctx = &ssl; + cf.conn = &connection; + cf.next = &lower; + lower.cft = &t4782_filter; + + if(scenario == 0 || scenario == 2) + t4782.blocks = 2; + if(scenario == 1) + t4782.first_write = 1; + if(scenario == 3 || scenario == 4) + t4782.send_error = CURLE_SEND_ERROR; + if(scenario == 4) + backend.recv_connection_closed = TRUE; + if(scenario == 5) + t4782.generate_error = SEC_E_INTERNAL_ERROR; + + result = Curl_ssl_schannel.shut_down(&cf, easy, TRUE, &done); + if(scenario < 3) { + fail_unless(result == CURLE_OK && !done && !cf.shutdown, + "incomplete send keeps shutdown pending"); + fail_unless(t4782.generated == 1 && t4782.freed == 0, + "pending token remains owned"); + fail_unless(ssl.io_need == CURL_SSL_IO_NEED_SEND, + "pending shutdown waits for writability"); + if(scenario != 2) { + for(attempts = 0; !done && attempts < 3; attempts++) + result = Curl_ssl_schannel.shut_down(&cf, easy, TRUE, &done); + fail_unless(result == CURLE_OK && done && cf.shutdown, + "shutdown completed"); + fail_unless(t4782.generated == 1 && t4782.freed == 1, + "retry reuses and then frees one token"); + fail_unless(t4782.accepted == sizeof(t4782_token), + "complete record delivered once"); + } + } + else { + fail_unless(result == (scenario == 4 ? CURLE_OK : CURLE_SEND_ERROR), + "send/generation failure retains existing error semantics"); + fail_unless(cf.shutdown, "failed shutdown does not retry"); + } + Curl_ssl_schannel.close(&cf, easy); + fail_unless(t4782.generated == 1 && t4782.freed == 1, + "close releases the token exactly once"); + } + + Curl_pSecFn = original; + UNITTEST_END(t4782_stop(easy)) +#else + (void)arg; + return CURLE_OK; +#endif +}